From 57862f7a972b9cb46d9de9208f3291ac65c8bf98 Mon Sep 17 00:00:00 2001 From: AlexP Date: Fri, 10 May 2024 06:11:49 +0500 Subject: [PATCH] STA-960 | controller&handle for restaurant listing --- app/src/Controller/AbstractController.php | 27 ++++++ app/src/Controller/RestaurantsController.php | 21 +++-- app/src/Dto/ErrorDto.php | 6 +- app/src/Dto/PaginationDto.php | 13 ++- app/src/Dto/RestaurantFilterVariantsDto.php | 14 ++- app/src/Dto/RestaurantListDto.php | 17 ++-- app/src/Dto/ValidateErrorDto.php | 13 ++- app/src/Repository/KitchensRepository.php | 2 +- .../KitchensRepositoryInterface.php | 11 +++ .../RestaurantTypeRepositoryInterface.php | 12 +++ .../Repository/RestaurantTypesRepository.php | 4 +- app/src/Repository/RestaurantsRepository.php | 28 +++--- .../RestaurantsRepositoryInterface.php | 14 +++ app/src/Request/AbstractRequest.php | 74 ++++++++++++++++ app/src/Request/RestaurantListingRequest.php | 35 ++++++++ app/src/Service/RestaurantListingService.php | 88 +++++++++++++++++++ app/src/Service/ServiceInterface.php | 11 +++ 17 files changed, 346 insertions(+), 44 deletions(-) create mode 100644 app/src/Controller/AbstractController.php create mode 100644 app/src/Repository/KitchensRepositoryInterface.php create mode 100644 app/src/Repository/RestaurantTypeRepositoryInterface.php create mode 100644 app/src/Repository/RestaurantsRepositoryInterface.php create mode 100644 app/src/Request/AbstractRequest.php create mode 100644 app/src/Request/RestaurantListingRequest.php create mode 100644 app/src/Service/RestaurantListingService.php create mode 100644 app/src/Service/ServiceInterface.php diff --git a/app/src/Controller/AbstractController.php b/app/src/Controller/AbstractController.php new file mode 100644 index 0000000..534d4dd --- /dev/null +++ b/app/src/Controller/AbstractController.php @@ -0,0 +1,27 @@ +service->serve($request)); +// } catch (Throwable $exception) { +// $error = new ErrorDto(); +// return new JsonResponse($error, $error->status); +// } + } +} diff --git a/app/src/Controller/RestaurantsController.php b/app/src/Controller/RestaurantsController.php index 52248bb..5840dbf 100644 --- a/app/src/Controller/RestaurantsController.php +++ b/app/src/Controller/RestaurantsController.php @@ -2,19 +2,24 @@ namespace App\Controller; -use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\HttpFoundation\Response; +use App\Request\RestaurantListingRequest; +use App\Service\RestaurantListingService; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpKernel\Attribute\MapQueryParameter; use Symfony\Component\Routing\Attribute\Route; +#[Route('/api/v1/restaurants/')] class RestaurantsController extends AbstractController { - /*** - * Листинг ресторанов - * @return Response - */ - #[Route('/restaurants/', name: 'restaurants')] - public function restaurants(): Response + public function __construct(RestaurantListingService $service) { + parent::__construct($service); + } + /** Листинг ресторанов */ + #[Route(name: 'restaurants')] + public function index(RestaurantListingRequest $request): JsonResponse + { + return $this->build($request); } } diff --git a/app/src/Dto/ErrorDto.php b/app/src/Dto/ErrorDto.php index 8ac836d..e54f5cd 100644 --- a/app/src/Dto/ErrorDto.php +++ b/app/src/Dto/ErrorDto.php @@ -4,9 +4,9 @@ namespace App\Dto; class ErrorDto implements DtoInterface { - public string $status; + public string $status = '500'; - public string $message; + public string $message = 'Something went wrong'; - public string $code; + public string $code = '100'; } diff --git a/app/src/Dto/PaginationDto.php b/app/src/Dto/PaginationDto.php index 7bd5a5c..abfd5eb 100644 --- a/app/src/Dto/PaginationDto.php +++ b/app/src/Dto/PaginationDto.php @@ -6,5 +6,16 @@ class PaginationDto implements DtoInterface { public int $current_page = 1; public int $pages; - public int $page_size; + public int $page_size = 12; + + public function __construct(int $page, int $limit, int $total) + { + $this->pages = ceil($total/$limit); + + if (($page > 1) && ($page <= $this->pages)) { + $this->current_page = $page; + } + + $this->page_size = $limit; + } } diff --git a/app/src/Dto/RestaurantFilterVariantsDto.php b/app/src/Dto/RestaurantFilterVariantsDto.php index 612cd5c..8fd5a95 100644 --- a/app/src/Dto/RestaurantFilterVariantsDto.php +++ b/app/src/Dto/RestaurantFilterVariantsDto.php @@ -6,9 +6,15 @@ use Ramsey\Collection\Collection; class RestaurantFilterVariantsDto implements DtoInterface { - /** @var DtoCollection */ - public DtoCollection $type; + /** + * @param DtoCollection $type + * @param DtoCollection $kitchen + */ + public function __construct( + /** @var DtoCollection */ + public DtoCollection $type, - /** @var DtoCollection */ - public DtoCollection $kitchen; + /** @var DtoCollection */ + public DtoCollection $kitchen, + ) {} } diff --git a/app/src/Dto/RestaurantListDto.php b/app/src/Dto/RestaurantListDto.php index 87c6e61..fd500cb 100644 --- a/app/src/Dto/RestaurantListDto.php +++ b/app/src/Dto/RestaurantListDto.php @@ -2,14 +2,19 @@ namespace App\Dto; -use Ramsey\Collection\Collection; - class RestaurantListDto implements DtoInterface { - public PaginationDto $pagination; + /** + * @param PaginationDto $pagination + * @param DtoCollection $list + * @param RestaurantFilterVariantsDto $filterVariants + */ + public function __construct( + public PaginationDto $pagination, - /** @var DtoCollection */ - public DtoCollection $list; + /** @var DtoCollection */ + public DtoCollection $list, - public RestaurantFilterVariantsDto $filterVariants; + public RestaurantFilterVariantsDto $filterVariants, + ) {} } diff --git a/app/src/Dto/ValidateErrorDto.php b/app/src/Dto/ValidateErrorDto.php index 307b43e..c6331f6 100644 --- a/app/src/Dto/ValidateErrorDto.php +++ b/app/src/Dto/ValidateErrorDto.php @@ -2,4 +2,15 @@ namespace App\Dto; -class ValidateErrorDto extends ErrorDto {} +use Symfony\Component\Validator\ConstraintViolationInterface; + +class ValidateErrorDto extends ErrorDto +{ + public string $status = '422'; + + public function __construct(ConstraintViolationInterface $violation) + { + $this->message = $violation->getPropertyPath() . ':' . $violation->getMessage(); + $this->code = $violation->getCode(); + } +} diff --git a/app/src/Repository/KitchensRepository.php b/app/src/Repository/KitchensRepository.php index cd53ebd..39c355b 100644 --- a/app/src/Repository/KitchensRepository.php +++ b/app/src/Repository/KitchensRepository.php @@ -15,7 +15,7 @@ use Ramsey\Collection\Collection; * @method Kitchens[] findAll() * @method Kitchens[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class KitchensRepository extends ServiceEntityRepository +class KitchensRepository extends ServiceEntityRepository implements KitchensRepositoryInterface { public function __construct(ManagerRegistry $registry) { diff --git a/app/src/Repository/KitchensRepositoryInterface.php b/app/src/Repository/KitchensRepositoryInterface.php new file mode 100644 index 0000000..afba82d --- /dev/null +++ b/app/src/Repository/KitchensRepositoryInterface.php @@ -0,0 +1,11 @@ + */ + public function getAll(): Collection; +} diff --git a/app/src/Repository/RestaurantTypesRepository.php b/app/src/Repository/RestaurantTypesRepository.php index 3f5eb7d..cecf853 100644 --- a/app/src/Repository/RestaurantTypesRepository.php +++ b/app/src/Repository/RestaurantTypesRepository.php @@ -2,9 +2,7 @@ namespace App\Repository; -use App\Entity\Restaurants; use App\Entity\RestaurantTypes; -use Cassandra\FutureSession; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Persistence\ManagerRegistry; use Ramsey\Collection\Collection; @@ -17,7 +15,7 @@ use Ramsey\Collection\Collection; * @method RestaurantTypes[] findAll() * @method RestaurantTypes[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class RestaurantTypesRepository extends ServiceEntityRepository +class RestaurantTypesRepository extends ServiceEntityRepository implements RestaurantTypeRepositoryInterface { public function __construct(ManagerRegistry $registry) { diff --git a/app/src/Repository/RestaurantsRepository.php b/app/src/Repository/RestaurantsRepository.php index b80d4af..188e4db 100644 --- a/app/src/Repository/RestaurantsRepository.php +++ b/app/src/Repository/RestaurantsRepository.php @@ -2,7 +2,6 @@ namespace App\Repository; -use App\DTO\CollectionDTO; use App\Entity\Restaurants; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\ORM\Query\Expr\Join; @@ -18,7 +17,7 @@ use Ramsey\Collection\Collection; * @method Restaurants[] findAll() * @method Restaurants[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class RestaurantsRepository extends ServiceEntityRepository +class RestaurantsRepository extends ServiceEntityRepository implements RestaurantsRepositoryInterface { public function __construct(ManagerRegistry $registry) { @@ -31,7 +30,7 @@ class RestaurantsRepository extends ServiceEntityRepository return new Collection( Restaurants::class, $query - ->orderBy('n.sort', 'ASC') + ->orderBy('r.sort', 'ASC') ->getQuery() ->getResult() ); @@ -39,9 +38,9 @@ class RestaurantsRepository extends ServiceEntityRepository protected function filterByKitchen(?string $kitchenId, QueryBuilder $query): QueryBuilder { - if ($kitchenId === null) { + if ($kitchenId !== null) { $query = $query - ->innerJoin('r.categories', 'k', Join::WITH, 'k.id = :kitchenId') + ->innerJoin('r.kitchens', 'k', Join::WITH, 'k.id = :kitchenId') ->setParameter('kitchenId', $kitchenId); } return $query; @@ -49,7 +48,7 @@ class RestaurantsRepository extends ServiceEntityRepository protected function filterByType(?string $typeId, QueryBuilder $query): QueryBuilder { - if ($typeId === null) { + if ($typeId !== null) { $query = $query ->where('r.type.id = :typeId') ->setParameter('typeId', $typeId); @@ -57,27 +56,22 @@ class RestaurantsRepository extends ServiceEntityRepository return $query; } - public function getCountWithFilters(?string $categoryId, ?string $typeId): int + public function getCountWithFilters(?string $kitchenId, ?string $typeId): int { $query = $this->createQueryBuilder('r'); - $query = $this->filterByKitchen($categoryId, $query); + $query = $this->filterByKitchen($kitchenId, $query); $query = $this->filterByType($typeId, $query); - return $query->select('COUNT(n.id)')->getQuery()->getSingleScalarResult(); + return $query->select('COUNT(r.id)')->getQuery()->getSingleScalarResult(); } /** @return Collection */ - public function findByFilters(?string $categoryId, ?string $typeId, int $limit): Collection + public function findByFilters(?string $kitchenId, ?string $typeId, int $limit, int $offset): Collection { $query = $this->createQueryBuilder('r'); - $query = $this->filterByKitchen($categoryId, $query); + $query = $this->filterByKitchen($kitchenId, $query); $query = $this->filterByType($typeId, $query); $query = $query->setMaxResults($limit); + $query = $query->setFirstResult($offset); return $this->toCollection($query); } - - /** @return Collection */ - public function getAll(): Collection - { - return $this->toCollection($this->createQueryBuilder('r')); - } } diff --git a/app/src/Repository/RestaurantsRepositoryInterface.php b/app/src/Repository/RestaurantsRepositoryInterface.php new file mode 100644 index 0000000..d2337ad --- /dev/null +++ b/app/src/Repository/RestaurantsRepositoryInterface.php @@ -0,0 +1,14 @@ + */ + public function findByFilters(?string $kitchenId, ?string $typeId, int $limit, int $offset): Collection; +} diff --git a/app/src/Request/AbstractRequest.php b/app/src/Request/AbstractRequest.php new file mode 100644 index 0000000..9ddccd5 --- /dev/null +++ b/app/src/Request/AbstractRequest.php @@ -0,0 +1,74 @@ +populate(); + + if (self::AUTO_VALIDATE) { + $this->validate(); + } + } + + /** + * Маппинг реквеста + * @return void + */ + protected function populate(): void + { + foreach ($this->getRequest()->toArray() as $property => $value) { + if (property_exists($this, $property)) { + $this->{$property} = $value; + } + } + } + + /** + * Валидация и выброкса ошибки при валидации + * @return void + */ + public function validate(): void + { + $errors = $this->validator->validate($this); + + + $messages = new DtoCollection(ValidateErrorDto::class); + + foreach ($errors as $error) { + $messages->add(new ValidateErrorDto($error)); + } + + if ($messages->count() > 0) { + $response = new JsonResponse($messages, 422); + $response->send(); + + throw new ValidatorException('Validation failed', $messages); + } + } + + /** + * Возвращает HttpFoundation реквест + * @return Request + */ + public function getRequest(): Request + { + return Request::createFromGlobals(); + } +} diff --git a/app/src/Request/RestaurantListingRequest.php b/app/src/Request/RestaurantListingRequest.php new file mode 100644 index 0000000..403a3a9 --- /dev/null +++ b/app/src/Request/RestaurantListingRequest.php @@ -0,0 +1,35 @@ +getRequest(); + foreach ($request->query->getIterator() as $property => $value) { + if (property_exists($this, $property)) { + $this->{$property} = $value; + } + } + } +} diff --git a/app/src/Service/RestaurantListingService.php b/app/src/Service/RestaurantListingService.php new file mode 100644 index 0000000..d24741a --- /dev/null +++ b/app/src/Service/RestaurantListingService.php @@ -0,0 +1,88 @@ +restaurants + ->getCountWithFilters( + $request->restaurant_type_id, + $request->kitchen_id + ); + + $pagination = new PaginationDto( + $request->page, + $request->limit, + $countOfRestaurants, + ); + + $offset = min($pagination->page_size, $countOfRestaurants) * ($pagination->current_page - 1); + + $list = new DtoCollection( + RestaurantListingElementDto::class, + $this + ->restaurants + ->findByFilters( + $request + ->restaurant_type_id, + $request->kitchen_id, + $request->limit, + $offset, + ) + ->map(function (Restaurants $restaurant) { + return $restaurant->getDto(); + }) + ->toArray() + ); + + $filters = new RestaurantFilterVariantsDto( + new DtoCollection( + RestaurantTypeDto::class, + $this->types->getAll() + ->map(function(RestaurantTypes $types) { + return $types->getDto(); + }) + ->toArray() + ), + new DtoCollection( + KitchenTypeDto::class, + $this->kitchens->getAll() + ->map(function(Kitchens $kitchen) { + return $kitchen->getDto(); + }) + ->toArray() + ), + ); + + return new RestaurantListDto( + $pagination, + $list, + $filters, + ); + } +} diff --git a/app/src/Service/ServiceInterface.php b/app/src/Service/ServiceInterface.php new file mode 100644 index 0000000..f52194d --- /dev/null +++ b/app/src/Service/ServiceInterface.php @@ -0,0 +1,11 @@ +