src/Controller/ProfileFilterController.php line 62

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\Dto\ProfileFilterFormData;
  4. use App\Dto\ProfileFilterFormDataFilters;
  5. use App\Dto\ProfileListSpecification;
  6. use App\Entity\Location\City;
  7. use App\Form\ProfileFilterForm;
  8. use App\Service\Filter\FilterModeResolver;
  9. use App\Service\Map\ProfileMapFilterService;
  10. use App\Service\Map\MapContextResolver;
  11. use App\Service\ProfileFiltersCatalogBuilder;
  12. use App\Service\ProfileFiltersQueryResolver;
  13. use App\Service\ProfileList;
  14. use App\Service\ProfileListingDataCreator;
  15. use App\Service\ProfileListingRecommendationsFiller;
  16. use App\Service\ProfileFilterService;
  17. use OpenApi\Attributes as OA;
  18. use Porpaginas\Page;
  19. use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
  20. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  21. use Symfony\Component\HttpFoundation\JsonResponse;
  22. use Symfony\Component\HttpFoundation\Request;
  23. use Symfony\Component\HttpFoundation\Response;
  24. use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
  25. use Symfony\Component\Routing\Annotation\Route;
  26. use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
  27. class ProfileFilterController extends AbstractController
  28. {
  29.     use ResponseTrait;
  30.     public function __construct(
  31.         private ProfileListingDataCreator $profileListingDataCreator,
  32.         private ProfileFilterService $profilesFilterService,
  33.         private ProfileList $profileList,
  34.         private ProfileListingRecommendationsFiller $profileListingRecommendationsFiller,
  35.         private ProfileFiltersCatalogBuilder $filtersCatalogBuilder,
  36.         private ProfileFiltersQueryResolver $filtersQueryResolver,
  37.         private FilterModeResolver $filterModeResolver,
  38.         private ProfileMapFilterService $profileMapFilterService,
  39.         private MapContextResolver $mapContextResolver,
  40.         private NormalizerInterface $normalizer,
  41.     ) {}
  42.     #[ParamConverter("city", converter: "city_converter")]
  43.     public function form(City $city, Request $request): Response
  44.     {
  45.         if(!$request->isXmlHttpRequest()) {
  46.             throw new BadRequestHttpException();
  47.         }
  48.         $listing = $request->request->get('listing');
  49.         $listingParameters = json_decode($request->request->get('listingParams'), true);
  50.         $formData = new ProfileFilterFormData($listing, $listingParameters, new ProfileFilterFormDataFilters());
  51.         $listingData = $this->profileListingDataCreator->getListingDataForFilter($listing, $listingParameters, $city);
  52.         /** @var ProfileListSpecification $specs */
  53.         $specs = $listingData['specs'];
  54.         //холостой вызов, чтобы сформировался запрос и записался в свойство ProfileList
  55.         $this->profileList->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city, $specs->spec(), $specs->additionalSpecs(), $specs->genders());
  56.         $this->profilesFilterService->checkProfilesPresenceByFilters($city, $listingData['listingTypeName']);
  57.         $profilePresenceByFilters = $this->profilesFilterService->profilesPresenceByFilters();
  58.         $form = $this->createForm(ProfileFilterForm::class, $formData, [
  59.             'action'          => $this->generateUrl('profile_filters.filter', ['city' => $city->getUriIdentity()]),
  60.             'csrf_protection' => false,
  61.             'form_data' => $formData,
  62.             'profiles_presence_by_filters' => $profilePresenceByFilters,
  63.         ]);
  64.         return $this->prepareForXhr($this->render('ProfileList/filter/form.html.twig', [
  65.             'profiles_presence_by_filters' => $profilePresenceByFilters,
  66.             'profile_filter_form' => $form->createView(),
  67.         ]));
  68.     }
  69.     #[ParamConverter("city", converter: "city_converter")]
  70.     public function filter(City $city, Request $request, ProfileFilterService $profilesFilterService): Response
  71.     {
  72.         if(!$request->isXmlHttpRequest()) {
  73.             throw new BadRequestHttpException();
  74.         }
  75.         $form = $this->createForm(ProfileFilterForm::class, new ProfileFilterFormData('', [], new ProfileFilterFormDataFilters()), [
  76.             'action' => $this->generateUrl('profile_filters.filter', ['city' => $city->getUriIdentity()]),
  77.             'csrf_protection' => false,
  78.         ]);
  79.         $form->handleRequest($request);
  80.         if (false === $form->isSubmitted() || false === $form->isValid()) {
  81.             throw new BadRequestHttpException('Form is not submitted');
  82.         }
  83.         /** @var ProfileFilterFormData $formData */
  84.         $formData = $form->getData();
  85. //        $specs = $this->profileListSpecificationService->{$listingType}(...$listingArgs);
  86.         $listingData = $this->profileListingDataCreator->getListingDataForFilter($formData->listing, $formData->listingParameters, $city);
  87.         $specs = $listingData['specs'];
  88.         $listingTypeName = $listingData['listingTypeName'];
  89.         $result = $profilesFilterService->filter($city, $listingTypeName, $formData->filters, $specs->spec(), $specs->additionalSpecs(), $specs->genders());
  90.         $presence = $profilesFilterService->profilesPresenceByFilters();
  91.         $recommendationsFill = [
  92.             'profiles' => $result,
  93.             'applied' => false,
  94.             'original_count' => $result instanceof Page ? $result->totalCount() : 0,
  95.         ];
  96.         if ($result instanceof Page) {
  97.             $recommendationsFill = $this->profileListingRecommendationsFiller->fill($city, $result, $specs->genders());
  98.         }
  99.         $form = $this->createForm(ProfileFilterForm::class, $formData, [
  100.             'action' => $this->generateUrl('profile_filters.filter', ['city' => $city->getUriIdentity()]),
  101.             'csrf_protection' => false,
  102.             'form_data' => $formData,
  103.             'profiles_presence_by_filters' => $presence,
  104.         ]);
  105.         return $this->prepareForXhr($this->render('ProfileList/profile_filter.profiles.partial.html.twig', [
  106.             'profiles' => $recommendationsFill['profiles'],
  107.             'recommendationSpec' => \App\Specification\ElasticSearch\ProfileIsLocated::withinCity($city),
  108.             'recommendations_fill_applied' => $recommendationsFill['applied'],
  109.             'recommendations_fill_original_count' => $recommendationsFill['original_count'],
  110.             'show_pagination' => true,
  111.             'profiles_presence_by_filters' => $presence,
  112.             'profile_filter_form' => $form->createView(),
  113.         ]));
  114.     }
  115.     #[Route("/api/profiles/filter", name: "api.profile_filter", methods: ["GET"], format: "json")]
  116.     #[OA\Tag(name: "ProfileFilter")]
  117.     #[OA\Parameter(name: "page", in: "query", schema: new OA\Schema(type: "integer", default: 1, minimum: 1))]
  118.     #[OA\Parameter(name: "mode", in: "query", description: "Response mode: list (default) or map", schema: new OA\Schema(type: "string", enum: ["list", "map"], default: "list"))]
  119.     #[OA\Parameter(name: "mapContext", in: "query", description: "main enables empty-map fallback without filters; listing disables it", schema: new OA\Schema(type: "string", enum: ["main", "listing"]))]
  120.     #[OA\Parameter(name: "scale", in: "query", description: "Map zoom level (0–21), used only with mode=map", schema: new OA\Schema(type: "integer", default: 0, minimum: 0, maximum: 21))]
  121.     #[OA\Parameter(name: "city", in: "query", description: "City id or uriIdentity (required for search)", schema: new OA\Schema(oneOf: [new OA\Schema(type: "integer"), new OA\Schema(type: "string")]))]
  122.     #[OA\Response(
  123.         response: 200,
  124.         description: 'Filter catalog (no search params) or search results',
  125.         content: new OA\JsonContent(
  126.             oneOf: [
  127.                 new OA\Schema(ref: "#/components/schemas/ProfileFiltersCatalog"),
  128.                 new OA\Schema(ref: "#/components/schemas/ProfileFilteredListingPage"),
  129.                 new OA\Schema(ref: "#/components/schemas/ProfileFiltersMapResult"),
  130.             ]
  131.         )
  132.     )]
  133.     #[OA\Response(response: 400, description: 'Search requested without valid city, or invalid mode/scale')]
  134.     public function filterApi(Request $request): JsonResponse
  135.     {
  136.         if (!$this->filtersQueryResolver->hasSearchParams($request)) {
  137.             return $this->json([
  138.                 'filters' => $this->filtersCatalogBuilder->build(),
  139.             ]);
  140.         }
  141.         $mode = $this->filterModeResolver->resolve($request);
  142.         [$city, $filters, $appliedFilters] = $this->filtersQueryResolver->resolve($request);
  143.         if ($mode->isMap()) {
  144.             $result = $this->profileMapFilterService->filter($city, $filters, $mode->scale(), $this->mapContextResolver->isMainMap($request));
  145.             $map = $this->normalizer->normalize($result);
  146.             if (!is_array($map)) {
  147.                 $map = ['data' => $map];
  148.             }
  149.             return $this->json(array_merge(['filters' => $appliedFilters], $map), headers: ['Cache-Control' => 'private, no-store']);
  150.         }
  151.         $listingData = $this->profileListingDataCreator->getListingDataForFilter('listByCity', [], $city);
  152.         /** @var ProfileListSpecification $specs */
  153.         $specs = $listingData['specs'];
  154.         $result = $this->profilesFilterService->filter(
  155.             $city,
  156.             $listingData['listingTypeName'],
  157.             $filters,
  158.             $specs->spec(),
  159.             $specs->additionalSpecs(),
  160.             $specs->genders()
  161.         );
  162.         $page = $this->normalizer->normalize($result);
  163.         if (!is_array($page)) {
  164.             $page = ['data' => $page];
  165.         }
  166.         return $this->json(array_merge(['filters' => $appliedFilters], $page));
  167.     }
  168. }