From 49d60871c981b39dce1e7726b4691af63ab290bc Mon Sep 17 00:00:00 2001 From: clfreville2 Date: Fri, 7 Jun 2024 17:49:12 +0200 Subject: [PATCH] Add post and species forms Squashed commit of the following: Author: Hugo PRADIER Author: bastien ollier Author: clfreville2 Reviewed on #7 --- composer.json | 2 +- composer.lock | 29 ++--- phpstan.dist.neon | 1 - src/Controller/PostController.php | 68 ++++++++++- src/Controller/RegistrationController.php | 6 +- src/Controller/SpeciesController.php | 72 ++++++++++-- src/DataFixtures/AppFixtures.php | 14 ++- src/Entity/Post.php | 3 + src/Entity/Species.php | 4 + src/Form/PostType.php | 38 ++++++ src/Form/SpeciesType.php | 27 +++++ src/Repository/UserRepository.php | 10 ++ templates/post/_delete_form.html.twig | 4 + templates/post/_form.html.twig | 4 + templates/post/edit.html.twig | 13 +++ templates/post/index.html.twig | 11 -- templates/post/new.html.twig | 11 ++ templates/post/show.html.twig | 46 ++++++++ templates/post/table.html.twig | 45 +++++++ templates/species/_delete_form.html.twig | 4 + templates/species/_form.html.twig | 4 + templates/species/detail.html.twig | 43 ------- templates/species/edit.html.twig | 13 +++ templates/species/index.html.twig | 11 +- templates/species/new.html.twig | 11 ++ templates/species/show.html.twig | 36 ++++++ templates/species/table.html.twig | 39 +++++++ tests/Controller/PostControllerTest.php | 128 ++++++++++++++++++++ tests/Controller/SpeciesControllerTest.php | 129 +++++++++++++++++++++ 29 files changed, 740 insertions(+), 86 deletions(-) create mode 100644 src/Form/PostType.php create mode 100644 src/Form/SpeciesType.php create mode 100644 templates/post/_delete_form.html.twig create mode 100644 templates/post/_form.html.twig create mode 100644 templates/post/edit.html.twig delete mode 100644 templates/post/index.html.twig create mode 100644 templates/post/new.html.twig create mode 100644 templates/post/show.html.twig create mode 100644 templates/post/table.html.twig create mode 100644 templates/species/_delete_form.html.twig create mode 100644 templates/species/_form.html.twig delete mode 100644 templates/species/detail.html.twig create mode 100644 templates/species/edit.html.twig create mode 100644 templates/species/new.html.twig create mode 100644 templates/species/show.html.twig create mode 100644 templates/species/table.html.twig create mode 100644 tests/Controller/PostControllerTest.php create mode 100644 tests/Controller/SpeciesControllerTest.php diff --git a/composer.json b/composer.json index 0a5a255..c02ecbd 100644 --- a/composer.json +++ b/composer.json @@ -109,7 +109,7 @@ "symfony/browser-kit": "7.0.*", "symfony/css-selector": "7.0.*", "symfony/debug-bundle": "7.0.*", - "symfony/maker-bundle": "^1.0", + "symfony/maker-bundle": "^1.59", "symfony/phpunit-bridge": "^7.0", "symfony/stopwatch": "7.0.*", "symfony/web-profiler-bundle": "7.0.*" diff --git a/composer.lock b/composer.lock index a528b05..12a1306 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "cce9cbfaf4a49449e6431a8515f9d9eb", + "content-hash": "9df70d112adfdfa4fbcde08b504d0bb9", "packages": [ { "name": "api-platform/core", @@ -3757,16 +3757,16 @@ }, { "name": "symfony/form", - "version": "v7.0.7", + "version": "v7.0.8", "source": { "type": "git", "url": "https://github.com/symfony/form.git", - "reference": "b4df6a399a2b03782a0163807239db342659f54f" + "reference": "1d0128e2f7e80c346ec51fa4d1ce4fec0d435eeb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/form/zipball/b4df6a399a2b03782a0163807239db342659f54f", - "reference": "b4df6a399a2b03782a0163807239db342659f54f", + "url": "https://api.github.com/repos/symfony/form/zipball/1d0128e2f7e80c346ec51fa4d1ce4fec0d435eeb", + "reference": "1d0128e2f7e80c346ec51fa4d1ce4fec0d435eeb", "shasum": "" }, "require": { @@ -3833,7 +3833,7 @@ "description": "Allows to easily create, process and reuse HTML forms", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/form/tree/v7.0.7" + "source": "https://github.com/symfony/form/tree/v7.0.8" }, "funding": [ { @@ -3849,7 +3849,7 @@ "type": "tidelift" } ], - "time": "2024-04-28T11:44:19+00:00" + "time": "2024-05-31T14:55:39+00:00" }, { "name": "symfony/framework-bundle", @@ -7137,16 +7137,16 @@ }, { "name": "symfony/validator", - "version": "v7.0.7", + "version": "v7.0.8", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "ab4e75b9d23ba70e78480aecbe4d8da15adf10eb" + "reference": "23af65dff1f4dfee9aab3a0123a243e40fa3d9cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/ab4e75b9d23ba70e78480aecbe4d8da15adf10eb", - "reference": "ab4e75b9d23ba70e78480aecbe4d8da15adf10eb", + "url": "https://api.github.com/repos/symfony/validator/zipball/23af65dff1f4dfee9aab3a0123a243e40fa3d9cf", + "reference": "23af65dff1f4dfee9aab3a0123a243e40fa3d9cf", "shasum": "" }, "require": { @@ -7191,7 +7191,8 @@ "Symfony\\Component\\Validator\\": "" }, "exclude-from-classmap": [ - "/Tests/" + "/Tests/", + "/Resources/bin/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -7211,7 +7212,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v7.0.7" + "source": "https://github.com/symfony/validator/tree/v7.0.8" }, "funding": [ { @@ -7227,7 +7228,7 @@ "type": "tidelift" } ], - "time": "2024-04-28T11:44:19+00:00" + "time": "2024-06-02T15:49:03+00:00" }, { "name": "symfony/var-dumper", diff --git a/phpstan.dist.neon b/phpstan.dist.neon index afb7a06..b7cb307 100644 --- a/phpstan.dist.neon +++ b/phpstan.dist.neon @@ -7,4 +7,3 @@ parameters: level: 8 paths: - src - - tests diff --git a/src/Controller/PostController.php b/src/Controller/PostController.php index 0df3016..8bbe909 100644 --- a/src/Controller/PostController.php +++ b/src/Controller/PostController.php @@ -2,19 +2,85 @@ namespace App\Controller; +use App\Entity\Post; +use App\Form\PostType; use App\Repository\PostRepository; +use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Security\Http\Attribute\IsGranted; class PostController extends AbstractController { #[Route('/', name: 'app_posts')] + #[Route('/post', name: 'app_post_index', methods: ['GET'])] public function index(PostRepository $repository): Response { $posts = $repository->findAll(); - return $this->render('post/index.html.twig', [ + return $this->render('post/table.html.twig', [ 'posts' => $posts, ]); } + + #[Route('/post/new', name: 'app_post_new', methods: ['GET', 'POST'])] + #[IsGranted('ROLE_USER')] + public function new(Request $request, EntityManagerInterface $entityManager): Response + { + $post = new Post(); + $form = $this->createForm(PostType::class, $post); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $entityManager->persist($post); + $entityManager->flush(); + + return $this->redirectToRoute('app_posts', [], Response::HTTP_SEE_OTHER); + } + + return $this->render('post/new.html.twig', [ + 'post' => $post, + 'form' => $form, + ]); + } + + #[Route('/post/{id}', name: 'app_post_show', methods: ['GET'])] + public function show(Post $post): Response + { + return $this->render('post/show.html.twig', [ + 'post' => $post, + ]); + } + + #[Route('/post/{id}/edit', name: 'app_post_edit', methods: ['GET', 'POST'])] + #[IsGranted('ROLE_USER')] + public function edit(Request $request, Post $post, EntityManagerInterface $entityManager): Response + { + $form = $this->createForm(PostType::class, $post); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $entityManager->flush(); + + return $this->redirectToRoute('app_posts', [], Response::HTTP_SEE_OTHER); + } + + return $this->render('post/edit.html.twig', [ + 'post' => $post, + 'form' => $form, + ]); + } + + #[Route('/post/{id}', name: 'app_post_delete', methods: ['POST'])] + #[IsGranted('ROLE_USER')] + public function delete(Request $request, Post $post, EntityManagerInterface $entityManager): Response + { + if ($this->isCsrfTokenValid('delete'.$post->getId(), (string) $request->getPayload()->get('_token'))) { + $entityManager->remove($post); + $entityManager->flush(); + } + + return $this->redirectToRoute('app_posts', [], Response::HTTP_SEE_OTHER); + } } diff --git a/src/Controller/RegistrationController.php b/src/Controller/RegistrationController.php index 6bfc237..43043a9 100644 --- a/src/Controller/RegistrationController.php +++ b/src/Controller/RegistrationController.php @@ -34,7 +34,11 @@ class RegistrationController extends AbstractController // do anything else you need here, like send an email - return $this->redirectToRoute('_profiler_home'); + return $this->redirectToRoute('app_login'); + } + + if ($this->getUser()) { + return $this->redirectToRoute('app_posts'); } return $this->render('registration/register.html.twig', [ diff --git a/src/Controller/SpeciesController.php b/src/Controller/SpeciesController.php index e727c11..a495a0f 100644 --- a/src/Controller/SpeciesController.php +++ b/src/Controller/SpeciesController.php @@ -3,27 +3,83 @@ namespace App\Controller; use App\Entity\Species; +use App\Form\SpeciesType; use App\Repository\SpeciesRepository; +use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; +use Symfony\Component\Security\Http\Attribute\IsGranted; +#[Route('/species')] class SpeciesController extends AbstractController { - #[Route('/species', name: 'app_species')] - public function index(SpeciesRepository $repository): Response + #[Route('/', name: 'app_species_index', methods: ['GET'])] + public function table(SpeciesRepository $speciesRepository): Response { - $species = $repository->findAll(); - return $this->render('species/index.html.twig', [ + return $this->render('species/table.html.twig', [ + 'species' => $speciesRepository->findAll(), + ]); + } + + #[Route('/new', name: 'app_species_new', methods: ['GET', 'POST'])] + #[IsGranted('ROLE_USER')] + public function new(Request $request, EntityManagerInterface $entityManager): Response + { + $species = new Species(); + $form = $this->createForm(SpeciesType::class, $species); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $entityManager->persist($species); + $entityManager->flush(); + + return $this->redirectToRoute('app_species_index', [], Response::HTTP_SEE_OTHER); + } + + return $this->render('species/new.html.twig', [ + 'species' => $species, + 'form' => $form, + ]); + } + + #[Route('/{id}', name: 'app_species_show', methods: ['GET'])] + public function show(Species $species): Response + { + return $this->render('species/show.html.twig', [ 'species' => $species, ]); } - #[Route('/species/{id}', name: 'app_species_detail')] - public function detail(Species $specie): Response + #[Route('/{id}/edit', name: 'app_species_edit', methods: ['GET', 'POST'])] + #[IsGranted('ROLE_USER')] + public function edit(Request $request, Species $species, EntityManagerInterface $entityManager): Response { - return $this->render('species/detail.html.twig', [ - 'specie' => $specie, + $form = $this->createForm(SpeciesType::class, $species); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $entityManager->flush(); + + return $this->redirectToRoute('app_species_index', [], Response::HTTP_SEE_OTHER); + } + + return $this->render('species/edit.html.twig', [ + 'species' => $species, + 'form' => $form, ]); } + + #[Route('/{id}', name: 'app_species_delete', methods: ['POST'])] + #[IsGranted('ROLE_USER')] + public function delete(Request $request, Species $species, EntityManagerInterface $entityManager): Response + { + if ($this->isCsrfTokenValid('delete'.$species->getId(), (string) $request->getPayload()->get('_token'))) { + $entityManager->remove($species); + $entityManager->flush(); + } + + return $this->redirectToRoute('app_species_index', [], Response::HTTP_SEE_OTHER); + } } diff --git a/src/DataFixtures/AppFixtures.php b/src/DataFixtures/AppFixtures.php index 2fbb47f..6bff9e2 100644 --- a/src/DataFixtures/AppFixtures.php +++ b/src/DataFixtures/AppFixtures.php @@ -4,17 +4,29 @@ namespace App\DataFixtures; use App\Entity\Post; use App\Entity\Species; +use App\Entity\User; use DateTimeImmutable; use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Persistence\ObjectManager; +use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; class AppFixtures extends Fixture { + public function __construct( + private readonly UserPasswordHasherInterface $passwordHasher + ) + { + } + public function load(ObjectManager $manager): void { + $user = (new User())->setEmail('test@test.fr'); + $user->setPassword($this->passwordHasher->hashPassword($user, 'password')); + $manager->persist($user); + $faker = \Faker\Factory::create(); for ($i = 0; $i < 20; ++$i) { - $name = $faker->text(); + $name = $faker->name(); $species = (new Species()) ->setScientificName($name) ->setVernacularName($name) diff --git a/src/Entity/Post.php b/src/Entity/Post.php index 0b54550..22e346a 100644 --- a/src/Entity/Post.php +++ b/src/Entity/Post.php @@ -11,6 +11,7 @@ use App\Repository\PostRepository; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Attribute\Groups; +use Symfony\Component\Validator\Constraints as Assert; #[ORM\Entity(repositoryClass: PostRepository::class)] #[ORM\HasLifecycleCallbacks] @@ -30,6 +31,7 @@ class Post #[ORM\Column] #[Groups(['post:collection:read'])] + #[Assert\NotBlank] private ?\DateTimeImmutable $foundDate = null; #[ORM\Column] @@ -51,6 +53,7 @@ class Post #[ORM\Column(type: Types::TEXT)] #[Groups(['post:read'])] + #[Assert\NotBlank] private ?string $commentary = null; #[ORM\ManyToOne(inversedBy: 'posts')] diff --git a/src/Entity/Species.php b/src/Entity/Species.php index e2e1b78..285fe6c 100644 --- a/src/Entity/Species.php +++ b/src/Entity/Species.php @@ -9,6 +9,7 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Attribute\Groups; +use Symfony\Component\Validator\Constraints as Assert; #[ORM\Entity(repositoryClass: SpeciesRepository::class)] #[ApiResource( @@ -26,14 +27,17 @@ class Species #[ORM\Column(length: 255)] #[Groups(['species:collection:read'])] + #[Assert\NotBlank] private ?string $scientific_name = null; #[ORM\Column(length: 255)] #[Groups(['species:collection:read'])] + #[Assert\NotBlank] private ?string $vernacular_name = null; #[ORM\Column(length: 255)] #[Groups(['species:collection:read'])] + #[Assert\NotBlank] private ?string $region = null; /** diff --git a/src/Form/PostType.php b/src/Form/PostType.php new file mode 100644 index 0000000..bea7e6f --- /dev/null +++ b/src/Form/PostType.php @@ -0,0 +1,38 @@ +add('foundDate', null, [ + 'widget' => 'single_text', + ]) + ->add('latitude') + ->add('longitude') + ->add('altitude') + ->add('commentary') + ->add('species', EntityType::class, [ + 'class' => Species::class, + 'choice_label' => 'scientific_name', + ]) + + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => Post::class, + ]); + } +} diff --git a/src/Form/SpeciesType.php b/src/Form/SpeciesType.php new file mode 100644 index 0000000..ae3b919 --- /dev/null +++ b/src/Form/SpeciesType.php @@ -0,0 +1,27 @@ +add('scientific_name') + ->add('vernacular_name') + ->add('region') + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => Species::class, + ]); + } +} diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php index 4f2804e..e861224 100644 --- a/src/Repository/UserRepository.php +++ b/src/Repository/UserRepository.php @@ -33,6 +33,16 @@ class UserRepository extends ServiceEntityRepository implements PasswordUpgrader $this->getEntityManager()->flush(); } + public function findOneByEmail(string $email): ?User + { + return $this->createQueryBuilder('u') + ->andWhere('u.email = :email') + ->setParameter('email', $email) + ->getQuery() + ->getOneOrNullResult() + ; + } + // /** // * @return User[] Returns an array of User objects // */ diff --git a/templates/post/_delete_form.html.twig b/templates/post/_delete_form.html.twig new file mode 100644 index 0000000..1223c9b --- /dev/null +++ b/templates/post/_delete_form.html.twig @@ -0,0 +1,4 @@ +
+ + +
diff --git a/templates/post/_form.html.twig b/templates/post/_form.html.twig new file mode 100644 index 0000000..bf20b98 --- /dev/null +++ b/templates/post/_form.html.twig @@ -0,0 +1,4 @@ +{{ form_start(form) }} + {{ form_widget(form) }} + +{{ form_end(form) }} diff --git a/templates/post/edit.html.twig b/templates/post/edit.html.twig new file mode 100644 index 0000000..16b6b5f --- /dev/null +++ b/templates/post/edit.html.twig @@ -0,0 +1,13 @@ +{% extends 'base.html.twig' %} + +{% block title %}Edit Post{% endblock %} + +{% block body %} +

Edit Post

+ + {{ include('post/_form.html.twig', {'button_label': 'Update'}) }} + + back to list + + {{ include('post/_delete_form.html.twig') }} +{% endblock %} diff --git a/templates/post/index.html.twig b/templates/post/index.html.twig deleted file mode 100644 index 94f3756..0000000 --- a/templates/post/index.html.twig +++ /dev/null @@ -1,11 +0,0 @@ -{% extends 'base.html.twig' %} - -{% block title %}Posts!{% endblock %} - -{% block body %} -{% for post in posts %} -
- #{{ post.id }} trouvé le {{ post.foundDate | date("d/m/Y \\à H \\h") }} -
-{% endfor %} -{% endblock %} diff --git a/templates/post/new.html.twig b/templates/post/new.html.twig new file mode 100644 index 0000000..b8bc236 --- /dev/null +++ b/templates/post/new.html.twig @@ -0,0 +1,11 @@ +{% extends 'base.html.twig' %} + +{% block title %}New Post{% endblock %} + +{% block body %} +

Create new Post

+ + {{ include('post/_form.html.twig') }} + + back to list +{% endblock %} diff --git a/templates/post/show.html.twig b/templates/post/show.html.twig new file mode 100644 index 0000000..a332dba --- /dev/null +++ b/templates/post/show.html.twig @@ -0,0 +1,46 @@ +{% extends 'base.html.twig' %} + +{% block title %}Post{% endblock %} + +{% block body %} +

Post

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Id{{ post.id }}
FoundDate{{ post.foundDate ? post.foundDate|date('Y-m-d H:i:s') : '' }}
PublicationDate{{ post.publicationDate ? post.publicationDate|date('Y-m-d H:i:s') : '' }}
Latitude{{ post.latitude }}
Longitude{{ post.longitude }}
Altitude{{ post.altitude }}
Commentary{{ post.commentary }}
+ + back to list + + edit + + {{ include('post/_delete_form.html.twig') }} +{% endblock %} diff --git a/templates/post/table.html.twig b/templates/post/table.html.twig new file mode 100644 index 0000000..3a5db93 --- /dev/null +++ b/templates/post/table.html.twig @@ -0,0 +1,45 @@ +{% extends 'base.html.twig' %} + +{% block title %}Post index{% endblock %} + +{% block body %} +

Post index

+ + + + + + + + + + + + + + + + {% for post in posts %} + + + + + + + + + + + {% else %} + + + + {% endfor %} + +
IdFoundDatePublicationDateLatitudeLongitudeAltitudeCommentaryactions
{{ post.id }}{{ post.foundDate ? post.foundDate|date('Y-m-d H:i:s') : '' }}{{ post.publicationDate ? post.publicationDate|date('Y-m-d H:i:s') : '' }}{{ post.latitude }}{{ post.longitude }}{{ post.altitude }}{{ post.commentary }} + show + edit +
no records found
+ + Create new +{% endblock %} diff --git a/templates/species/_delete_form.html.twig b/templates/species/_delete_form.html.twig new file mode 100644 index 0000000..9e7c05a --- /dev/null +++ b/templates/species/_delete_form.html.twig @@ -0,0 +1,4 @@ +
+ + +
diff --git a/templates/species/_form.html.twig b/templates/species/_form.html.twig new file mode 100644 index 0000000..bf20b98 --- /dev/null +++ b/templates/species/_form.html.twig @@ -0,0 +1,4 @@ +{{ form_start(form) }} + {{ form_widget(form) }} + +{{ form_end(form) }} diff --git a/templates/species/detail.html.twig b/templates/species/detail.html.twig deleted file mode 100644 index d6a5ebf..0000000 --- a/templates/species/detail.html.twig +++ /dev/null @@ -1,43 +0,0 @@ -{% extends 'base.html.twig' %} - -{% block title %}Herbarium - Détail de l'espèces{% endblock %} - -{% block body %} - - -
-

{{ specie.vernacularName }}

-

- 🔬 Nom Scientifique : {{ specie.scientificName }}
- 📍 Region : {{ specie.region }} -

-
-

Posts :

- - {% for post in specie.posts %} -
-
-

- - 📅 {{ post.publicationDate | date }} - -

-
-
- 📍 géolocalisation :
- - Longitude : {{ post.longitude }}
- - Latitude : {{ post.latitude }}
- - Altitude : {{ post.altitude }}

- - 💬 Commentaire :
- - {{ post.getCommentary }} -
-
- {% endfor %} -
- -
-{% endblock %} \ No newline at end of file diff --git a/templates/species/edit.html.twig b/templates/species/edit.html.twig new file mode 100644 index 0000000..8e4401b --- /dev/null +++ b/templates/species/edit.html.twig @@ -0,0 +1,13 @@ +{% extends 'base.html.twig' %} + +{% block title %}Edit Species{% endblock %} + +{% block body %} +

Edit Species

+ + {{ include('species/_form.html.twig', {'button_label': 'Update'}) }} + + back to list + + {{ include('species/_delete_form.html.twig') }} +{% endblock %} diff --git a/templates/species/index.html.twig b/templates/species/index.html.twig index 666ad41..e855f6c 100644 --- a/templates/species/index.html.twig +++ b/templates/species/index.html.twig @@ -1,6 +1,6 @@ {% extends 'base.html.twig' %} -{% block title %}Herbarium - Espèces{% endblock %} +{% block title %}Species{% endblock %} {% block body %}