diff --git a/src/Controller/API/Dinosaurs/Create.php b/src/Controller/API/Dinosaurs/Create.php index 8b1ebb4..f987b40 100644 --- a/src/Controller/API/Dinosaurs/Create.php +++ b/src/Controller/API/Dinosaurs/Create.php @@ -6,7 +6,8 @@ use App\Entity\Dinosaur; use App\Entity\Species; -use Doctrine\Persistence\ManagerRegistry; +use App\Repository\DinosaurRepository; +use App\Repository\SpeciesRepository; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; @@ -83,7 +84,8 @@ enum: ["Male", "Female"], description: 'The species ID does not exists' )] public function __invoke( - ManagerRegistry $manager, + DinosaurRepository $dinosaurRepository, + SpeciesRepository $speciesDinosaur, Request $request, SerializerInterface $serializer, JsonSchemaValidator $jsonSchemaValidator @@ -100,9 +102,7 @@ public function __invoke( $dinosaurData = json_decode($request->getContent(), true); - $species = $manager - ->getRepository(Species::class) - ->find($dinosaurData['speciesId']); + $species = $speciesDinosaur->find($dinosaurData['speciesId']); if (!$species instanceof Species) { return new JsonResponse([ @@ -122,9 +122,8 @@ public function __invoke( $dinosaurData['eyesColor'], ); - $em = $manager->getManager(); - $em->persist($dinosaur); - $em->flush(); + $dinosaurRepository->persist($dinosaur); + $dinosaurRepository->flush(); $content = $serializer->serialize( $dinosaur, diff --git a/src/Controller/API/Dinosaurs/Delete.php b/src/Controller/API/Dinosaurs/Delete.php index 3251e2a..a7506d7 100644 --- a/src/Controller/API/Dinosaurs/Delete.php +++ b/src/Controller/API/Dinosaurs/Delete.php @@ -5,7 +5,7 @@ namespace App\Controller\API\Dinosaurs; use App\Entity\Dinosaur; -use Doctrine\Persistence\ManagerRegistry; +use App\Repository\DinosaurRepository; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; @@ -24,11 +24,9 @@ final class Delete extends AbstractController response: Response::HTTP_NO_CONTENT, description: 'Dinosaur successfully deleted' )] - public function __invoke(ManagerRegistry $manager, string $id): Response + public function __invoke(DinosaurRepository $dinosaurRepository, string $id): Response { - $dinosaur = $manager - ->getRepository(Dinosaur::class) - ->find($id); + $dinosaur = $dinosaurRepository->find($id); if (!$dinosaur instanceof Dinosaur) { return new JsonResponse([ @@ -36,9 +34,8 @@ public function __invoke(ManagerRegistry $manager, string $id): Response ], Response::HTTP_UNPROCESSABLE_ENTITY); } - $em = $manager->getManager(); - $em->remove($dinosaur); - $em->flush(); + $dinosaurRepository->remove($dinosaur); + $dinosaurRepository->flush(); return new Response(status: Response::HTTP_NO_CONTENT); } diff --git a/src/Controller/API/Dinosaurs/GetAll.php b/src/Controller/API/Dinosaurs/GetAll.php index ee7acf2..ffa7ce9 100644 --- a/src/Controller/API/Dinosaurs/GetAll.php +++ b/src/Controller/API/Dinosaurs/GetAll.php @@ -5,12 +5,14 @@ namespace App\Controller\API\Dinosaurs; use App\Entity\Dinosaur; +use App\Repository\DinosaurRepository; use Doctrine\Persistence\ManagerRegistry; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Serializer\SerializerInterface; +use Symfony\Component\HttpFoundation\Request; use Nelmio\ApiDocBundle\Annotation\Model; use OpenApi\Attributes as OA; @@ -18,6 +20,51 @@ final class GetAll extends AbstractController { #[Route('/api/dinosaurs', methods: 'GET')] #[OA\Tag('dinosaur')] + #[OA\Parameter( + parameter: 'page', + name: 'page', + in: 'query', + schema: new OA\Schema( + type: 'integer', + default: 1, + minimum: 1 + ), + )] + #[OA\Parameter( + parameter: 'limit', + name: 'limit', + in: 'query', + schema: new OA\Schema( + type: 'integer', + default: 25, + minimum: 1 + ), + )] + #[OA\Parameter( + parameter: 'search', + name: 'search', + in: 'query', + schema: new OA\Schema(type: 'string'), + example: 'Dino' + )] + #[OA\Parameter( + parameter: 'filters', + name: 'filters', + in: 'query', + schema: new OA\Schema(type: 'object'), + style: 'deepObject', + explode: true, + example: '{"name": "rex", "gender": "male"}', + )] + #[OA\Parameter( + parameter: 'sorts', + name: 'sorts', + in: 'query', + schema: new OA\Schema(type: 'object'), + style: 'deepObject', + explode: true, + example: '{"age": "ASC"}' + )] #[OA\Response( response: Response::HTTP_OK, description: 'List all the dinosaurs', @@ -32,12 +79,25 @@ final class GetAll extends AbstractController ) )] public function __invoke( - ManagerRegistry $manager, - SerializerInterface $serializer + Request $request, + SerializerInterface $serializer, + ManagerRegistry $managerRegistry, ): Response { - $dinosaurs = $manager + $filters = $request->query->all('filters') ?? []; + $sorts = $request->query->all('sorts') ?? []; + $search = $request->query->get('search'); + $page = $request->query->getInt('page', 1); + $limit = $request->query->getInt('limit', 5); + + $dinosaurs = $managerRegistry ->getRepository(Dinosaur::class) - ->findAll(); + ->filter($filters) + ->search($search) + ->sort($sorts) + ->paginate( + $page, + $limit + ); $content = $serializer->serialize( $dinosaurs, diff --git a/src/Controller/API/Dinosaurs/GetOne.php b/src/Controller/API/Dinosaurs/GetOne.php index 940284e..d7c2aa7 100644 --- a/src/Controller/API/Dinosaurs/GetOne.php +++ b/src/Controller/API/Dinosaurs/GetOne.php @@ -5,6 +5,7 @@ namespace App\Controller\API\Dinosaurs; use App\Entity\Dinosaur; +use App\Repository\DinosaurRepository; use Doctrine\Persistence\ManagerRegistry; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; @@ -28,13 +29,11 @@ final class GetOne extends AbstractController content: new Model(type: Dinosaur::class, groups: ['dinosaur']) )] public function __invoke( - ManagerRegistry $manager, + DinosaurRepository $dinosaurRepository, SerializerInterface $serializer, string $id ): Response { - $dinosaur = $manager - ->getRepository(Dinosaur::class) - ->find($id); + $dinosaur = $dinosaurRepository->find($id); if (!$dinosaur instanceof Dinosaur) { return new JsonResponse([ diff --git a/src/Repository/DinosaurRepository.php b/src/Repository/DinosaurRepository.php index 1016b70..a5ccbf7 100644 --- a/src/Repository/DinosaurRepository.php +++ b/src/Repository/DinosaurRepository.php @@ -4,25 +4,96 @@ use App\Entity\Dinosaur; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ManagerRegistry; class DinosaurRepository extends ServiceEntityRepository { - public function __construct(ManagerRegistry $registry) - { + private const ALIAS = 'dinosaur'; + + private QueryBuilder $queryBuilder; + private ManagerRegistry $registry; + + public function __construct( + ManagerRegistry $registry, + ?QueryBuilder $queryBuilder + ) { parent::__construct($registry, Dinosaur::class); + + $this->registry = $registry; + $this->queryBuilder = $queryBuilder ?: $this->createQueryBuilder(self::ALIAS); + } + + private function cloneQueryBuilder(): QueryBuilder + { + return clone $this->queryBuilder; } - public function search(?string $q): array + public function search(?string $name = null): self { - if (null === $q) { - return $this->findAll(); + $queryBuilder = $this->cloneQueryBuilder(); + + if ($name !== null) { + $queryBuilder + ->andWhere(sprintf('%s.name LIKE :name', self::ALIAS)) + ->setParameter('name', '%' . $name . '%'); } - return $this->createQueryBuilder('d') - ->where('d.name = :q') - ->setParameter('q', $q) - ->getQuery() - ->getResult(); + return $this->duplicate($queryBuilder); + } + + public function sort(array $sorts): self + { + $queryBuilder = $this->cloneQueryBuilder(); + + foreach ($sorts as $field => $order) { + $queryBuilder->addOrderBy(sprintf('%s.%s', self::ALIAS, $field), $order); + } + + return $this->duplicate($queryBuilder); + } + + public function filter(array $filters): self + { + $queryBuilder = $this->cloneQueryBuilder(); + + foreach ($filters as $field => $value) { + $queryBuilder + ->andWhere(sprintf('%s.%s = :%s', self::ALIAS, $field, $field)) + ->setParameter($field, $value); + } + + return $this->duplicate($queryBuilder); + } + + public function paginate(int $page, int $limit): array + { + $queryBuilder = $this->cloneQueryBuilder(); + + $queryBuilder + ->setFirstResult(($page - 1) * $limit) + ->setMaxResults($limit); + + return $queryBuilder->getQuery()->getResult(); + } + + public function persist(Dinosaur $dinosaur): void + { + $this->registry->getManager()->persist($dinosaur); + } + + public function flush(): void + { + $this->registry->getManager()->flush(); + } + + public function remove(Dinosaur $dinosaur): void + { + $this->registry->getManager()->remove($dinosaur); + } + + private function duplicate(QueryBuilder $queryBuilder): self + { + return new self($this->registry, $queryBuilder); } }