src/Controller/ProfileListController.php line 447

Open in your IDE?
  1. <?php
  2. /**
  3.  * Created by simpson <simpsonwork@gmail.com>
  4.  * Date: 2019-03-19
  5.  * Time: 22:28
  6.  */
  7. namespace App\Controller;
  8. use App\Bridge\Porpaginas\Doctrine\ORM\FakeORMQueryPage;
  9. use App\Entity\Location\City;
  10. use App\Entity\Location\County;
  11. use App\Entity\Location\District;
  12. use App\Entity\Location\Station;
  13. use App\Entity\Profile\BodyTypes;
  14. use App\Entity\Profile\BreastTypes;
  15. use App\Entity\Profile\Genders;
  16. use App\Entity\Profile\HairColors;
  17. use App\Entity\Profile\Nationalities;
  18. use App\Entity\Profile\PrivateHaircuts;
  19. use App\Entity\Service;
  20. use App\Entity\ServiceGroups;
  21. use App\Entity\TakeOutLocations;
  22. use App\Repository\ServiceRepository;
  23. use App\Repository\StationRepository;
  24. use App\Service\CountryCurrencyResolver;
  25. use App\Service\DefaultCityProvider;
  26. use App\Service\Features;
  27. use App\Service\ListingRotationApi;
  28. use App\Service\ListingService;
  29. use App\Service\ProfileList;
  30. use App\Service\ProfileListingDataCreator;
  31. use App\Service\ProfileListSpecificationService;
  32. use App\Service\ProfileFilterService;
  33. use App\Specification\ElasticSearch\ISpecification;
  34. use App\Specification\Profile\ProfileHasApartments;
  35. use App\Specification\Profile\ProfileHasComments;
  36. use App\Specification\Profile\ProfileHasVideo;
  37. use App\Specification\Profile\ProfileIdIn;
  38. use App\Specification\Profile\ProfileIdINOrderedByINValues;
  39. use App\Specification\Profile\ProfileIdNotIn;
  40. use App\Specification\Profile\ProfileIsApproved;
  41. use App\Specification\Profile\ProfileIsElite;
  42. use App\Specification\Profile\ProfileIsLocated;
  43. use App\Specification\Profile\ProfileIsProvidingOneOfServices;
  44. use App\Specification\Profile\ProfileIsProvidingTakeOut;
  45. use App\Specification\Profile\ProfileWithAge;
  46. use App\Specification\Profile\ProfileWithBodyType;
  47. use App\Specification\Profile\ProfileWithBreastType;
  48. use App\Specification\Profile\ProfileWithHairColor;
  49. use App\Specification\Profile\ProfileWithNationality;
  50. use App\Specification\Profile\ProfileWithApartmentsOneHourPrice;
  51. use App\Specification\Profile\ProfileWithPrivateHaircut;
  52. use Flagception\Bundle\FlagceptionBundle\Annotations\Feature;
  53. use Happyr\DoctrineSpecification\Filter\Filter;
  54. use Happyr\DoctrineSpecification\Logic\OrX;
  55. use Porpaginas\Doctrine\ORM\ORMQueryResult;
  56. use Porpaginas\Page;
  57. use Psr\Cache\CacheItemPoolInterface;
  58. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
  59. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
  60. use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
  61. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  62. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  63. use Symfony\Component\HttpFoundation\Request;
  64. use Happyr\DoctrineSpecification\Spec;
  65. use Symfony\Component\HttpFoundation\RequestStack;
  66. use Symfony\Component\HttpFoundation\Response;
  67. /**
  68.  * @see \App\Console\Export\ExportRotationListingsConfigCommand for listing API endpoints
  69.  */
  70. #[Cache(maxage60, public: true)]
  71. class ProfileListController extends AbstractController
  72. {
  73.     use ExtendedPaginationTrait;
  74.     use SpecTrait;
  75.     use ProfileMinPriceTrait;
  76.     use ResponseTrait;
  77.     const ENTRIES_ON_PAGE 36;
  78.     const RESULT_SOURCE_COUNTY 'county';
  79.     const RESULT_SOURCE_DISTRICT 'district';
  80.     const RESULT_SOURCE_STATION 'station';
  81.     const RESULT_SOURCE_APPROVED 'approved';
  82.     const RESULT_SOURCE_WITH_COMMENTS 'with_comments';
  83.     const RESULT_SOURCE_WITH_VIDEO 'with_video';
  84.     const RESULT_SOURCE_WITH_SELFIE 'with_selfie';
  85.     const RESULT_SOURCE_ELITE 'elite';
  86.     const RESULT_SOURCE_MASSEURS 'masseurs';
  87.     const RESULT_SOURCE_MASSAGE_SERVICE 'massage_service';
  88.     const RESULT_SOURCE_BY_PARAMS 'by_params';
  89.     const RESULT_SOURCE_SERVICE 'service';
  90.     const RESULT_SOURCE_CITY 'city';
  91.     const RESULT_SOURCE_COUNTRY 'country';
  92.     const CACHE_ITEM_STATION_ADDED_PROFILES 'station_added_profiles_ids_';
  93.     private ?string $source null;
  94.     public function __construct(
  95.         private RequestStack $requestStack,
  96.         private ProfileList $profileList,
  97.         private CountryCurrencyResolver $countryCurrencyResolver,
  98.         private ServiceRepository $serviceRepository,
  99.         private ListingService $listingService,
  100.         private Features $features,
  101.         private ProfileFilterService $profilesFilterService,
  102.         private ProfileListSpecificationService $profileListSpecificationService,
  103.         private ProfileListingDataCreator $profileListingDataCreator,
  104.         private CacheItemPoolInterface $stationAddedProfilesCache,
  105.         private ParameterBagInterface $parameterBag,
  106.         private ListingRotationApi $listingRotationApi,
  107.     ) {}
  108.     /**
  109.      * @Feature("has_masseurs")
  110.      */
  111.     #[ParamConverter("city"converter"city_converter")]
  112.     public function listForMasseur(City $cityServiceRepository $serviceRepository): Response
  113.     {
  114.         $specs $this->profileListSpecificationService->listForMasseur($city);
  115.         $response null;
  116.         try {
  117.             $result $this->listingRotationApi->paginate('/city/{city}/masseur', ['city' => $city->getId()], $this->getCurrentPageNumber());
  118.             $response = new Response();
  119.             $response->setMaxAge(10);
  120.         } catch (\Exception) {
  121.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  122.         }
  123.         $massageGroupServices $serviceRepository->findBy(['group' => ServiceGroups::MASSAGE]);
  124.         $orX $this->getORSpecForItemsArray([$massageGroupServices], function ($item): ProfileIsProvidingOneOfServices {
  125.             return new ProfileIsProvidingOneOfServices($item);
  126.         });
  127.         $prevCount $result->count();
  128.         $result $this->checkEmptyResultNotMasseur($result$city$orXself::RESULT_SOURCE_MASSAGE_SERVICE);
  129.         if ($result->count() > $prevCount) {
  130.             $response?->setMaxAge(60);
  131.         }
  132.         return $this->render('ProfileList/list.html.twig', [
  133.             'profiles' => $result,
  134.             'source' => $this->source,
  135.             'source_default' => self::RESULT_SOURCE_MASSEURS,
  136.             'recommendationSpec' => $specs->recommendationSpec(),
  137.         ], response$response);
  138.     }
  139.     public function listByDefaultCity(ParameterBagInterface $parameterBagRequest $request): Response
  140.     {
  141.         $controller get_class($this) . '::listByCity';
  142.         $path = [
  143.             'city' => $parameterBag->get('default_city'),
  144.             'subRequest' => true,
  145.         ];
  146.         //чтобы в обработчике можно было понять, по какому роуту зашли
  147.         $request->request->set('_route''profile_list.list_by_city');
  148.         return $this->forward($controller$path);
  149.     }
  150.     #[ParamConverter("city"converter"city_converter")]
  151.     public function listByCity(ParameterBagInterface $parameterBagRequest $requestCity $citybool $subRequest false): Response
  152.     {
  153.         $page $this->getCurrentPageNumber();
  154.         if ($this->features->redirect_default_city_to_homepage() && false === $subRequest && $city->equals($parameterBag->get('default_city')) && $page 2) {
  155.             return $this->redirectToRoute('homepage', [], 301);
  156.         }
  157.         $specs $this->profileListSpecificationService->listByCity();
  158.         $response null;
  159.         try {
  160.             $result $this->listingRotationApi->paginate('/city/{city}', ['city' => $city->getId()], $page);
  161.             $response = new Response();
  162.             $response->setMaxAge(10);
  163.         } catch (\Exception) {
  164.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  165.         }
  166.         return $this->render('ProfileList/list.html.twig', [
  167.             'profiles' => $result,
  168.             'recommendationSpec' => $specs->recommendationSpec(),
  169.         ], response$response);
  170.     }
  171.     /**
  172.      * @Feature("intim_moscow_listing")
  173.      */
  174.     #[Cache(maxage3600, public: true)]
  175.     public function listIntim(DefaultCityProvider $defaultCityProvider): Response
  176.     {
  177.         $city $defaultCityProvider->getDefaultCity();
  178.         $request $this->requestStack->getCurrentRequest();
  179.         $request?->attributes->set('city'$city);
  180.         $specs $this->profileListSpecificationService->listByCity();
  181.         $response null;
  182.         try {
  183.             $result $this->listingRotationApi->paginate('/city/{city}', ['city' => $city->getId()], $this->getCurrentPageNumber());
  184.             $response = new Response();
  185.             $response->setMaxAge(3600);
  186.         } catch (\Exception) {
  187.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  188.         }
  189.         $result $this->shuffleProfilesOnPage($result);
  190.         return $this->render('ProfileList/list.html.twig', [
  191.             'profiles' => $result,
  192.             'city' => $city,
  193.             'recommendationSpec' => $specs->recommendationSpec(),
  194.         ], response$response);
  195.     }
  196.     #[ParamConverter("city"converter"city_converter")]
  197.     #[Entity("county"expr"repository.ofUriIdentityWithinCity(county, city)")]
  198.     public function listByCounty(Request $requestCity $cityCounty $county): Response
  199.     {
  200.         if (!$city->hasCounty($county)) {
  201.             throw $this->createNotFoundException();
  202.         }
  203.         $specs $this->profileListSpecificationService->listByCounty($county);
  204.         $response null;
  205.         try {
  206.             $result $this->listingRotationApi->paginate('/city/{city}/county/{county}', ['city' => $city->getId(), 'county' => $county->getId()], $this->getCurrentPageNumber());
  207.             $response = new Response();
  208.             $response->setMaxAge(10);
  209.         } catch (\Exception) {
  210.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  211.         }
  212.         $prevCount $result->count();
  213.         $result $this->checkEmptyResultNotMasseur($result$citySpec::orX(ProfileIsLocated::withinCounties($city$city->getCounties()->toArray())), self::RESULT_SOURCE_COUNTY);
  214.         if ($result->count() > $prevCount) {
  215.             $response?->setMaxAge(60);
  216.         }
  217.         return $this->render('ProfileList/list.html.twig', [
  218.             'profiles' => $result,
  219.             'source' => $this->source,
  220.             'source_default' => self::RESULT_SOURCE_COUNTY,
  221.             'county' => $county,
  222.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  223.                 'city' => $city->getUriIdentity(),
  224.                 'county' => $county->getUriIdentity(),
  225.                 'page' => $this->getCurrentPageNumber()
  226.             ]),
  227.             'recommendationSpec' => $specs->recommendationSpec(),
  228.         ], response$response);
  229.     }
  230.     #[ParamConverter("city"converter"city_converter")]
  231.     #[Entity("district"expr"repository.ofUriIdentityWithinCity(district, city)")]
  232.     public function listByDistrict(Request $requestCity $cityDistrict $district): Response
  233.     {
  234.         if (!$city->hasDistrict($district)) {
  235.             throw $this->createNotFoundException();
  236.         }
  237.         $specs $this->profileListSpecificationService->listByDistrict($district);
  238.         $response null;
  239.         try {
  240.             $result $this->listingRotationApi->paginate('/city/{city}/district/{district}', ['city' => $city->getId(), 'district' => $district->getId()], $this->getCurrentPageNumber());
  241.             $response = new Response();
  242.             $response->setMaxAge(10);
  243.         } catch (\Exception) {
  244.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  245.         }
  246.         $prevCount $result->count();
  247.         $result $this->checkEmptyResultNotMasseur($result$citySpec::orX(ProfileIsLocated::withinDistricts($city$city->getDistricts()->toArray())), self::RESULT_SOURCE_DISTRICT);
  248.         if ($result->count() > $prevCount) {
  249.             $response?->setMaxAge(60);
  250.         }
  251.         return $this->render('ProfileList/list.html.twig', [
  252.             'profiles' => $result,
  253.             'source' => $this->source,
  254.             'source_default' => self::RESULT_SOURCE_DISTRICT,
  255.             'district' => $district,
  256.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  257.                 'city' => $city->getUriIdentity(),
  258.                 'district' => $district->getUriIdentity(),
  259.                 'page' => $this->getCurrentPageNumber()
  260.             ]),
  261.             'recommendationSpec' => $specs->recommendationSpec(),
  262.         ], response$response);
  263.     }
  264.     /**
  265.      * @Feature("extra_category_without_prepayment")
  266.      */
  267.     #[ParamConverter("city"converter"city_converter")]
  268.     public function listWithoutPrepayment(Request $requestCity $city): Response
  269.     {
  270.         $listingData $this->profileListingDataCreator->getListingDataForFilter('listWithoutPrepayment', [], $city);
  271.         $specs $listingData['specs'];
  272.         $listingTypeName $listingData['listingTypeName'];
  273.         $response null;
  274.         try {
  275.             $result $this->listingRotationApi->paginate('/city/{city}/category/bez-predoplaty', ['city' => $city->getId()], $this->getCurrentPageNumber());
  276.             $response = new Response();
  277.             $response->setMaxAge(10);
  278.         } catch (\Exception) {
  279.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  280.         }
  281.         $request->attributes->set('profiles_count'$result->count());
  282.         $request->attributes->set('listingTypeName'$listingTypeName);
  283.         $request->attributes->set('city'$city);
  284.         return $this->render('ProfileList/list.html.twig', [
  285.             'profiles' => $result,
  286.             'source' => $this->source,
  287.             'source_default' => 'without_prepayment',
  288.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  289.                 'city' => $city->getUriIdentity(),
  290.                 'page' => $this->getCurrentPageNumber(),
  291.             ]),
  292.             'recommendationSpec' => $specs->recommendationSpec(),
  293.         ], response$response);
  294.     }
  295.     #[ParamConverter("city"converter"city_converter")]
  296.     #[Entity("station"expr"repository.ofUriIdentityWithinCity(station, city)")]
  297.     public function listByStation(Request $requestCity $cityStation $station): Response
  298.     {
  299.         if (!$city->hasStation($station)) {
  300.             throw $this->createNotFoundException();
  301.         }
  302.         $specs $this->profileListSpecificationService->listByStation($station);
  303.         $response null;
  304.         try {
  305.             $result $this->listingRotationApi->paginate('/city/{city}/station/{station}', ['city' => $city->getId(), 'station' => $station->getId()], $this->getCurrentPageNumber());
  306.             $response = new Response();
  307.             $response->setMaxAge(10);
  308.         } catch (\Exception) {
  309.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  310.         }
  311.         $prevCount $result->count();
  312.         if (true === $this->features->station_page_add_profiles()) {
  313.             $spread $this->parameterBag->get('app.profile.station_page.added_profiles.spread');
  314.             $result $this->addSinglePageStationResults($result$city$station$spread ?: 5);
  315.         }
  316.         if (null !== $station->getDistrict()) {
  317.             $result $this->checkEmptyResultNotMasseur($result$citySpec::orX(ProfileIsLocated::nearStations($city$station->getDistrict()->getStations()->toArray())), self::RESULT_SOURCE_STATION);
  318.         } else {
  319.             $result $this->checkCityAndCountrySource($result$city);
  320.         }
  321.         if ($result->count() > $prevCount) {
  322.             $response?->setMaxAge(60);
  323.         }
  324.         return $this->render('ProfileList/list.html.twig', [
  325.             'profiles' => $result,
  326.             'source' => $this->source,
  327.             'source_default' => self::RESULT_SOURCE_STATION,
  328.             'station' => $station,
  329.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  330.                 'city' => $city->getUriIdentity(),
  331.                 'station' => $station->getUriIdentity(),
  332.                 'page' => $this->getCurrentPageNumber()
  333.             ]),
  334.             'recommendationSpec' => $specs->recommendationSpec(),
  335.         ], response$response);
  336.     }
  337.     private function addSinglePageStationResults(Page $resultCity $cityStation $stationint $spread): Page
  338.     {
  339.         if ($result->totalCount() >= $result->getCurrentLimit()) {
  340.             return $result;
  341.         }
  342.         $addedProfileIds $this->stationAddedProfilesCache->get(self::CACHE_ITEM_STATION_ADDED_PROFILES $station->getId(), function () use ($result$city$station$spread): array {
  343.             $currentSpread rand(0$spread);
  344.             $plannedTotalCount $result->getCurrentLimit() - $spread $currentSpread;
  345.             $result iterator_to_array($result->getIterator());
  346.             $originalProfileIds $this->extractProfileIds($result);
  347.             if ($station->getDistrict()) {
  348.                 $result $this->addSinglePageResultsUptoAmount($result$citySpec::orX(ProfileIsLocated::withinDistrict($station->getDistrict())), $plannedTotalCount);
  349.             }
  350.             if ($station->getDistrict()?->getCounty()) {
  351.                 $result $this->addSinglePageResultsUptoAmount($result$citySpec::orX(ProfileIsLocated::withinCounty($station->getDistrict()->getCounty())), $plannedTotalCount);
  352.             }
  353.             $result $this->addSinglePageResultsUptoAmount($result$citySpec::orX(ProfileIsLocated::withinCity($city)), $plannedTotalCount);
  354.             $result $this->extractProfileIds($result);
  355.             return array_diff($result$originalProfileIds);
  356.         });
  357.         $addedProfileIds array_slice($addedProfileIds0$result->getCurrentLimit() - $result->totalCount());
  358.         $originalProfiles iterator_to_array($result->getIterator());
  359.         $addedProfiles $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacementLimited($city, new ProfileIdIn($addedProfileIds), null, [Genders::FEMALE], count($addedProfileIds));
  360.         $newResult array_merge($originalProfiles$addedProfiles);
  361.         return new FakeORMQueryPage(01$result->getCurrentLimit(), count($newResult), $newResult);
  362.     }
  363.     private function addSinglePageResultsUptoAmount(array $resultCity $city, ?Filter $specsint $totalCount): array
  364.     {
  365.         $toAdd $totalCount count($result);
  366.         $currentResultIds $this->extractProfileIds($result);
  367.         $resultsToAdd $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacementLimited($city$specs, [new ProfileIdNotIn($currentResultIds)], [Genders::FEMALE], $toAdd);
  368.         $result array_merge($result$resultsToAdd);
  369.         return $result;
  370.     }
  371.     #[ParamConverter("city"converter"city_converter")]
  372.     public function listByStations(City $citystring $stationsStationRepository $stationRepository): Response
  373.     {
  374.         $stationIds explode(','$stations);
  375.         $stations $stationRepository->findBy(['uriIdentity' => $stationIds]);
  376.         $specs $this->profileListSpecificationService->listByStations($stations);
  377.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  378.         return $this->render('ProfileList/list.html.twig', [
  379.             'profiles' => $result,
  380.             'recommendationSpec' => $specs->recommendationSpec(),
  381.         ]);
  382.     }
  383.     #[ParamConverter("city"converter"city_converter")]
  384.     public function listApproved(Request $requestCity $city): Response
  385.     {
  386.         $specs $this->profileListSpecificationService->listApproved();
  387.         $response null;
  388.         try {
  389.             $result $this->listingRotationApi->paginate('/city/{city}/approved', ['city' => $city->getId()], $this->getCurrentPageNumber());
  390.             $response = new Response();
  391.             $response->setMaxAge(10);
  392.         } catch (\Exception) {
  393.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  394.         }
  395.         $prevCount $result->count();
  396.         if ($this->features->fill_empty_profile_list() && $result->count() == 0) {
  397.             $this->source self::RESULT_SOURCE_WITH_COMMENTS;
  398.             $result $this->listRandomSinglePage($citynull, new ProfileHasComments(), nulltruefalse);
  399.             if ($result->count() == 0) {
  400.                 $this->source self::RESULT_SOURCE_WITH_VIDEO;
  401.                 $result $this->listRandomSinglePage($citynull, new ProfileHasVideo(), nulltruefalse);
  402.             }
  403.             if ($result->count() == 0) {
  404.                 $this->source self::RESULT_SOURCE_ELITE;
  405.                 $result $this->listRandomSinglePage($citynull$this->getSpecForEliteGirls($city), nulltruenull);
  406.             }
  407.             $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  408.         }
  409.         if ($result->count() > $prevCount) {
  410.             $response?->setMaxAge(60);
  411.         }
  412.         return $this->render('ProfileList/list.html.twig', [
  413.             'profiles' => $result,
  414.             'source' => $this->source,
  415.             'source_default' => self::RESULT_SOURCE_APPROVED,
  416.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  417.                 'city' => $city->getUriIdentity(),
  418.                 'page' => $this->getCurrentPageNumber()
  419.             ]),
  420.             'recommendationSpec' => $specs->recommendationSpec(),
  421.         ], response$response);
  422.     }
  423.     #[ParamConverter("city"converter"city_converter")]
  424.     public function listWithComments(Request $requestCity $city): Response
  425.     {
  426.         $specs $this->profileListSpecificationService->listWithComments();
  427.         $response null;
  428.         try {
  429.             $result $this->listingRotationApi->paginate('/city/{city}/with_comments', ['city' => $city->getId()], $this->getCurrentPageNumber());
  430.             $response = new Response();
  431.             $response->setMaxAge(10);
  432.         } catch (\Exception) {
  433.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  434.         }
  435.         $prevCount $result->count();
  436.         if ($this->features->fill_empty_profile_list() && $result->count() == 0) {
  437.             $this->source self::RESULT_SOURCE_APPROVED;
  438.             $result $this->listRandomSinglePage($citynull, new ProfileIsApproved(), nulltruefalse);
  439.             if ($result->count() == 0) {
  440.                 $this->source self::RESULT_SOURCE_WITH_VIDEO;
  441.                 $result $this->listRandomSinglePage($citynull, new ProfileHasVideo(), nulltruefalse);
  442.             }
  443.             if ($result->count() == 0) {
  444.                 $this->source self::RESULT_SOURCE_ELITE;
  445.                 $result $this->listRandomSinglePage($citynull$this->getSpecForEliteGirls($city), nulltruenull);
  446.             }
  447.             $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  448.         }
  449.         if ($result->count() > $prevCount) {
  450.             $response?->setMaxAge(60);
  451.         }
  452.         return $this->render('ProfileList/list.html.twig', [
  453.             'profiles' => $result,
  454.             'source' => $this->source,
  455.             'source_default' => self::RESULT_SOURCE_WITH_COMMENTS,
  456.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  457.                 'city' => $city->getUriIdentity(),
  458.                 'page' => $this->getCurrentPageNumber()
  459.             ]),
  460.             'recommendationSpec' => $specs->recommendationSpec(),
  461.         ], response$response);
  462.     }
  463.     #[ParamConverter("city"converter"city_converter")]
  464.     public function listWithVideo(Request $requestCity $city): Response
  465.     {
  466.         $specs $this->profileListSpecificationService->listWithVideo();
  467.         $response null;
  468.         try {
  469.             $result $this->listingRotationApi->paginate('/city/{city}/with_video', ['city' => $city->getId()], $this->getCurrentPageNumber());
  470.             $response = new Response();
  471.             $response->setMaxAge(10);
  472.         } catch (\Exception) {
  473.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  474.         }
  475.         $prevCount $result->count();
  476.         if ($this->features->fill_empty_profile_list() && $result->count() == 0) {
  477.             $this->source self::RESULT_SOURCE_APPROVED;
  478.             $result $this->listRandomSinglePage($citynull, new ProfileIsApproved(), nulltruefalse);
  479.             if ($result->count() == 0) {
  480.                 $this->source self::RESULT_SOURCE_WITH_COMMENTS;
  481.                 $result $this->listRandomSinglePage($citynull, new ProfileHasComments(), nulltruefalse);
  482.             }
  483.             if ($result->count() == 0) {
  484.                 $this->source self::RESULT_SOURCE_ELITE;
  485.                 $result $this->listRandomSinglePage($citynull$this->getSpecForEliteGirls($city), nulltruenull);
  486.             }
  487.             $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  488.         }
  489.         if ($result->count() > $prevCount) {
  490.             $response?->setMaxAge(60);
  491.         }
  492.         return $this->render('ProfileList/list.html.twig', [
  493.             'profiles' => $result,
  494.             'source' => $this->source,
  495.             'source_default' => self::RESULT_SOURCE_WITH_VIDEO,
  496.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  497.                 'city' => $city->getUriIdentity(),
  498.                 'page' => $this->getCurrentPageNumber()
  499.             ]),
  500.             'recommendationSpec' => $specs->recommendationSpec(),
  501.         ], response$response);
  502.     }
  503.     #[ParamConverter("city"converter"city_converter")]
  504.     public function listWithSelfie(Request $requestCity $city): Response
  505.     {
  506.         $specs $this->profileListSpecificationService->listWithSelfie();
  507.         $response null;
  508.         try {
  509.             $result $this->listingRotationApi->paginate('/city/{city}/with_selfie', ['city' => $city->getId()], $this->getCurrentPageNumber());
  510.             $response = new Response();
  511.             $response->setMaxAge(10);
  512.         } catch (\Exception) {
  513.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  514.         }
  515.         $prevCount $result->count();
  516.         if ($this->features->fill_empty_profile_list() && $result->count() == 0) {
  517.             $this->source self::RESULT_SOURCE_WITH_VIDEO;
  518.             $result $this->listRandomSinglePage($citynull, new ProfileHasVideo(), nulltruefalse);
  519.             if ($result->count() == 0) {
  520.                 $this->source self::RESULT_SOURCE_APPROVED;
  521.                 $result $this->listRandomSinglePage($citynull, new ProfileIsApproved(), nulltruefalse);
  522.             }
  523.             if ($result->count() == 0) {
  524.                 $this->source self::RESULT_SOURCE_ELITE;
  525.                 $result $this->listRandomSinglePage($citynull$this->getSpecForEliteGirls($city), nulltruenull);
  526.             }
  527.             $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  528.         }
  529.         if ($result->count() > $prevCount) {
  530.             $response?->setMaxAge(60);
  531.         }
  532.         return $this->render('ProfileList/list.html.twig', [
  533.             'profiles' => $result,
  534.             'source' => $this->source,
  535.             'source_default' => self::RESULT_SOURCE_WITH_SELFIE,
  536.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  537.                 'city' => $city->getUriIdentity(),
  538.                 'page' => $this->getCurrentPageNumber()
  539.             ]),
  540.             'recommendationSpec' => $specs->recommendationSpec(),
  541.         ], response$response);
  542.     }
  543.     #[ParamConverter("city"converter"city_converter")]
  544.     public function listByPrice(Request $requestCountryCurrencyResolver $countryCurrencyResolverCity $citystring $priceTypeint $minPrice nullint $maxPrice null): Response
  545.     {
  546.         $specs $this->profileListSpecificationService->listByPrice($city$priceType$minPrice$maxPrice);
  547.         $response null;
  548.         try {
  549.             if (!in_array($priceType, ['low''high''elite'])) {
  550.                 throw new \LogicException(sprintf('Price type "%s" is not supported'$priceType));
  551.             }
  552.             $result $this->listingRotationApi->paginate('/city/{city}/price/' $priceType, ['city' => $city->getId()], $this->getCurrentPageNumber());
  553.             $response = new Response();
  554.             $response->setMaxAge(10);
  555.         } catch (\Exception) {
  556.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  557.         }
  558.         $prevCount $result->count();
  559.         if ($this->features->fill_empty_profile_list() && $result->count() == 0) {
  560.             $result $this->processListByPriceEmptyResult($result$city$priceType$minPrice$maxPrice);
  561.         }
  562.         if ($result->count() > $prevCount) {
  563.             $response?->setMaxAge(60);
  564.         }
  565.         return $this->render('ProfileList/list.html.twig', [
  566.             'profiles' => $result,
  567.             'source' => $this->source,
  568.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  569.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  570.                 'city' => $city->getUriIdentity(),
  571.                 'priceType' => $priceType,
  572.                 'minPrice' => $minPrice,
  573.                 'maxPrice' => $maxPrice,
  574.                 'page' => $this->getCurrentPageNumber()
  575.             ]),
  576.             'recommendationSpec' => $specs->recommendationSpec(),
  577.         ], response$response);
  578.     }
  579.     private function processListByPriceEmptyResult(Page $resultCity $citystring $priceTypeint $minPrice nullint $maxPrice null)
  580.     {
  581.         if (!$this->features->fill_empty_profile_list())
  582.             return $result;
  583.         $this->source self::RESULT_SOURCE_BY_PARAMS;
  584.         if ($this->countryCurrencyResolver->getCurrencyFor($city->getCountryCode()) == 'RUB') {
  585.             if ($minPrice && $maxPrice) {
  586.                 if ($minPrice == 2000 && $maxPrice == 3000) {
  587.                     $priceSpec = [
  588.                         ProfileWithApartmentsOneHourPrice::range(15002000),
  589.                         ProfileWithApartmentsOneHourPrice::range(30004000),
  590.                     ];
  591.                 } else if ($minPrice == 3000 && $maxPrice == 4000) {
  592.                     $priceSpec = [
  593.                         ProfileWithApartmentsOneHourPrice::range(20003000),
  594.                         ProfileWithApartmentsOneHourPrice::range(40005000),
  595.                     ];
  596.                 } else if ($minPrice == 4000 && $maxPrice == 5000) {
  597.                     $priceSpec = [
  598.                         ProfileWithApartmentsOneHourPrice::range(30004000),
  599.                         ProfileWithApartmentsOneHourPrice::range(50006000),
  600.                     ];
  601.                 } else if ($minPrice == 5000 && $maxPrice == 6000) {
  602.                     $priceSpec = [
  603.                         ProfileWithApartmentsOneHourPrice::range(4000999999)
  604.                     ];
  605.                 } else {
  606.                     $priceSpec = [
  607.                         ProfileWithApartmentsOneHourPrice::range($minPrice$maxPrice)
  608.                     ];
  609.                 }
  610.                 $result $this->listRandomSinglePage($citynullnull$priceSpectruefalse);
  611.             } elseif ($maxPrice) {
  612.                 if ($maxPrice == 500) {
  613.                     $priceSpec ProfileWithApartmentsOneHourPrice::cheaperThan(1500);
  614.                     $result $this->listRandomSinglePage($citynull$priceSpecnulltruefalse);
  615.                     if ($result->count() == 0) {
  616.                         $priceSpec ProfileWithApartmentsOneHourPrice::range(15002000);
  617.                         $result $this->listRandomSinglePage($citynull$priceSpecnulltruefalse);
  618.                     }
  619.                 } else if ($maxPrice == 1500) {
  620.                     $priceSpec ProfileWithApartmentsOneHourPrice::range(15002000);
  621.                     $result $this->listRandomSinglePage($citynull$priceSpecnulltruefalse);
  622.                     if ($result->count() == 0) {
  623.                         $priceSpec ProfileWithApartmentsOneHourPrice::range(20003000);
  624.                         $result $this->listRandomSinglePage($citynull$priceSpecnulltruefalse);
  625.                     }
  626.                 }
  627.             } else {
  628.                 switch ($priceType) {
  629.                     case 'not_expensive':
  630.                         $priceSpec ProfileWithApartmentsOneHourPrice::cheaperThan(2000);
  631.                         break;
  632.                     case 'high':
  633.                         $priceSpec ProfileWithApartmentsOneHourPrice::range(30006000);
  634.                         break;
  635.                     case 'low':
  636.                         $priceSpec ProfileWithApartmentsOneHourPrice::cheaperThan(2000);
  637.                         break;
  638.                     case 'elite':
  639.                         $priceSpec ProfileWithApartmentsOneHourPrice::moreExpensiveThan(6000);
  640.                         break;
  641.                     default:
  642.                         throw new \LogicException('Unknown price type');
  643.                         break;
  644.                 }
  645.                 $result $this->listRandomSinglePage($citynull$priceSpecnulltruefalse);
  646.             }
  647.         }
  648.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  649.         return $result;
  650.     }
  651.     #[ParamConverter("city"converter"city_converter")]
  652.     public function listByAge(Request $requestCity $citystring $ageTypeint $minAge nullint $maxAge null): Response
  653.     {
  654.         $specs $this->profileListSpecificationService->listByAge($ageType$minAge$maxAge);
  655.         $response null;
  656.         try {
  657.             if (!in_array($ageType, ['young''old'])) {
  658.                 throw new \LogicException(sprintf('Age type "%s" is not supported'$ageType));
  659.             }
  660.             $result $this->listingRotationApi->paginate('/city/{city}/age/' $ageType, ['city' => $city->getId()], $this->getCurrentPageNumber());
  661.             $response = new Response();
  662.             $response->setMaxAge(10);
  663.         } catch (\Exception) {
  664.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  665.         }
  666.         $prevCount $result->count();
  667.         if ($this->features->fill_empty_profile_list() && $result->count() == 0) {
  668.             $filled $this->processListByAgeEmptyResult($result$city$ageType$minAge$maxAge);
  669.             if ($filled)
  670.                 $result $filled;
  671.         }
  672.         if ($result->count() > $prevCount) {
  673.             $response?->setMaxAge(60);
  674.         }
  675.         return $this->render('ProfileList/list.html.twig', [
  676.             'profiles' => $result,
  677.             'source' => $this->source,
  678.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  679.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  680.                 'city' => $city->getUriIdentity(),
  681.                 'ageType' => $ageType,
  682.                 'minAge' => $minAge,
  683.                 'maxAge' => $maxAge,
  684.                 'page' => $this->getCurrentPageNumber()
  685.             ]),
  686.             'recommendationSpec' => $specs->recommendationSpec(),
  687.         ], response$response);
  688.     }
  689.     private function processListByAgeEmptyResult(Page $resultCity $citystring $ageTypeint $minAge nullint $maxAge null)
  690.     {
  691.         if (!$this->features->fill_empty_profile_list())
  692.             return $result;
  693.         $this->source self::RESULT_SOURCE_BY_PARAMS;
  694.         if ($minAge && !$maxAge) {
  695.             $startMinAge $minAge;
  696.             do {
  697.                 $startMinAge -= 2;
  698.                 $ageSpec ProfileWithAge::olderThan($startMinAge);
  699.                 $result $this->listRandomSinglePage($citynull$ageSpecnulltruefalse);
  700.             } while ($result->count() == && $startMinAge >= 18);
  701.         } else if ($ageType == 'young') {
  702.             $startMaxAge 20;
  703.             do {
  704.                 $startMaxAge += 2;
  705.                 $ageSpec ProfileWithAge::youngerThan($startMaxAge);
  706.                 $result $this->listRandomSinglePage($citynull$ageSpecnulltruefalse);
  707.             } while ($result->count() == && $startMaxAge <= 100);
  708.         }
  709.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  710.         return $result;
  711.     }
  712.     #[ParamConverter("city"converter"city_converter")]
  713.     public function listByHeight(Request $requestCity $citystring $heightType): Response
  714.     {
  715.         $specs $this->profileListSpecificationService->listByHeight($heightType);
  716.         $response null;
  717.         try {
  718.             $result $this->listingRotationApi->paginate('/city/{city}/height/' $heightType, ['city' => $city->getId()], $this->getCurrentPageNumber());
  719.             $response = new Response();
  720.             $response->setMaxAge(10);
  721.         } catch (\Exception) {
  722.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  723.         }
  724.         $prevCount $result->count();
  725.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  726.         if ($result->count() > $prevCount) {
  727.             $response?->setMaxAge(60);
  728.         }
  729.         return $this->render('ProfileList/list.html.twig', [
  730.             'profiles' => $result,
  731.             'source' => $this->source,
  732.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  733.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  734.                 'city' => $city->getUriIdentity(),
  735.                 'heightType' => $heightType,
  736.                 'page' => $this->getCurrentPageNumber()
  737.             ]),
  738.             'recommendationSpec' => $specs->recommendationSpec(),
  739.         ], response$response);
  740.     }
  741.     #[ParamConverter("city"converter"city_converter")]
  742.     public function listByBreastType(Request $requestCity $citystring $breastType): Response
  743.     {
  744.         if (null === $type BreastTypes::getValueByUriIdentity($breastType))
  745.             throw $this->createNotFoundException();
  746.         $specs $this->profileListSpecificationService->listByBreastType($breastType);
  747.         $response null;
  748.         try {
  749.             $result $this->listingRotationApi->paginate('/city/{city}/breasttype/' $type, ['city' => $city->getId()], $this->getCurrentPageNumber());
  750.             $response = new Response();
  751.             $response->setMaxAge(10);
  752.         } catch (\Exception) {
  753.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  754.         }
  755.         $orX $this->getORSpecForItemsArray(BreastTypes::getList(), function ($item): ProfileWithBreastType {
  756.             return new ProfileWithBreastType($item);
  757.         });
  758.         $prevCount $result->count();
  759.         $result $this->checkEmptyResultNotMasseur($result$city$orXself::RESULT_SOURCE_BY_PARAMS);
  760.         if ($result->count() > $prevCount) {
  761.             $response?->setMaxAge(60);
  762.         }
  763.         return $this->render('ProfileList/list.html.twig', [
  764.             'profiles' => $result,
  765.             'source' => $this->source,
  766.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  767.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  768.                 'city' => $city->getUriIdentity(),
  769.                 'breastType' => $breastType,
  770.                 'page' => $this->getCurrentPageNumber()
  771.             ]),
  772.             'recommendationSpec' => $specs->recommendationSpec(),
  773.         ], response$response);
  774.     }
  775.     #[ParamConverter("city"converter"city_converter")]
  776.     public function listByHairColor(Request $requestCity $citystring $hairColor): Response
  777.     {
  778.         if (null === $color HairColors::getValueByUriIdentity($hairColor))
  779.             throw $this->createNotFoundException();
  780.         $specs $this->profileListSpecificationService->listByHairColor($hairColor);
  781.         $response null;
  782.         try {
  783.             $result $this->listingRotationApi->paginate('/city/{city}/haircolor/' $color, ['city' => $city->getId()], $this->getCurrentPageNumber());
  784.             $response = new Response();
  785.             $response->setMaxAge(10);
  786.         } catch (\Exception) {
  787.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  788.         }
  789.         $orX $this->getORSpecForItemsArray(HairColors::getList(), function ($item): ProfileWithHairColor {
  790.             return new ProfileWithHairColor($item);
  791.         });
  792.         $prevCount $result->count();
  793.         $result $this->checkEmptyResultNotMasseur($result$city$orXself::RESULT_SOURCE_BY_PARAMS);
  794.         if ($result->count() > $prevCount) {
  795.             $response?->setMaxAge(60);
  796.         }
  797.         return $this->render('ProfileList/list.html.twig', [
  798.             'profiles' => $result,
  799.             'source' => $this->source,
  800.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  801.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  802.                 'city' => $city->getUriIdentity(),
  803.                 'hairColor' => $hairColor,
  804.                 'page' => $this->getCurrentPageNumber()
  805.             ]),
  806.             'recommendationSpec' => $specs->recommendationSpec(),
  807.         ], response$response);
  808.     }
  809.     #[ParamConverter("city"converter"city_converter")]
  810.     public function listByBodyType(Request $requestCity $citystring $bodyType): Response
  811.     {
  812.         if (null === $type BodyTypes::getValueByUriIdentity($bodyType))
  813.             throw $this->createNotFoundException();
  814.         $specs $this->profileListSpecificationService->listByBodyType($bodyType);
  815.         $response null;
  816.         try {
  817.             $result $this->listingRotationApi->paginate('/city/{city}/bodytype/' $type, ['city' => $city->getId()], $this->getCurrentPageNumber());
  818.             $response = new Response();
  819.             $response->setMaxAge(10);
  820.         } catch (\Exception) {
  821.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  822.         }
  823.         $orX $this->getORSpecForItemsArray(BodyTypes::getList(), function ($item): ProfileWithBodyType {
  824.             return new ProfileWithBodyType($item);
  825.         });
  826.         $prevCount $result->count();
  827.         $result $this->checkEmptyResultNotMasseur($result$city$orXself::RESULT_SOURCE_BY_PARAMS);
  828.         if ($result->count() > $prevCount) {
  829.             $response?->setMaxAge(60);
  830.         }
  831.         return $this->render('ProfileList/list.html.twig', [
  832.             'profiles' => $result,
  833.             'source' => $this->source,
  834.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  835.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  836.                 'city' => $city->getUriIdentity(),
  837.                 'bodyType' => $bodyType,
  838.                 'page' => $this->getCurrentPageNumber()
  839.             ]),
  840.             'recommendationSpec' => $specs->recommendationSpec(),
  841.         ], response$response);
  842.     }
  843.     #[ParamConverter("city"converter"city_converter")]
  844.     public function listByPlace(Request $requestCity $citystring $placeTypestring $takeOutLocation null): Response
  845.     {
  846.         $specs $this->profileListSpecificationService->listByPlace($placeType$takeOutLocation);
  847.         if (null === $specs)
  848.             throw $this->createNotFoundException();
  849.         $response null;
  850.         try {
  851.             $endpoint '/city/{city}/place/' $placeType;
  852.             if (null !== $takeOutLocation) {
  853.                 $endpoint .= '/' $takeOutLocation;
  854.             }
  855.             $result $this->listingRotationApi->paginate($endpoint, ['city' => $city->getId()], $this->getCurrentPageNumber());
  856.             $response = new Response();
  857.             $response->setMaxAge(10);
  858.         } catch (\Exception) {
  859.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  860.         }
  861.         $orX $this->getORSpecForItemsArray(TakeOutLocations::getList(), function ($item): ProfileIsProvidingTakeOut {
  862.             return new ProfileIsProvidingTakeOut($item);
  863.         });
  864.         if ($placeType == 'take-out')
  865.             $orX->orX(new ProfileHasApartments());
  866.         $prevCount $result->count();
  867.         $result $this->checkEmptyResultNotMasseur($result$city$orXself::RESULT_SOURCE_BY_PARAMS);
  868.         if ($result->count() > $prevCount) {
  869.             $response?->setMaxAge(60);
  870.         }
  871.         return $this->render('ProfileList/list.html.twig', [
  872.             'profiles' => $result,
  873.             'source' => $this->source,
  874.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  875.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  876.                 'city' => $city->getUriIdentity(),
  877.                 'placeType' => $placeType,
  878.                 'takeOutLocation' => TakeOutLocations::getUriIdentity(TakeOutLocations::getValueByUriIdentity($takeOutLocation)),
  879.                 'page' => $this->getCurrentPageNumber()
  880.             ]),
  881.             'recommendationSpec' => $specs->recommendationSpec(),
  882.         ], response$response);
  883.     }
  884.     #[ParamConverter("city"converter"city_converter")]
  885.     public function listByPrivateHaircut(Request $requestCity $citystring $privateHaircut): Response
  886.     {
  887.         if (null === $type PrivateHaircuts::getValueByUriIdentity($privateHaircut))
  888.             throw $this->createNotFoundException();
  889.         $specs $this->profileListSpecificationService->listByPrivateHaircut($privateHaircut);
  890.         $response null;
  891.         try {
  892.             $result $this->listingRotationApi->paginate('/city/{city}/privatehaircut/' $type, ['city' => $city->getId()], $this->getCurrentPageNumber());
  893.             $response = new Response();
  894.             $response->setMaxAge(10);
  895.         } catch (\Exception) {
  896.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  897.         }
  898.         $orX $this->getORSpecForItemsArray(PrivateHaircuts::getList(), function ($item): ProfileWithPrivateHaircut {
  899.             return new ProfileWithPrivateHaircut($item);
  900.         });
  901.         $prevCount $result->count();
  902.         $result $this->checkEmptyResultNotMasseur($result$city$orXself::RESULT_SOURCE_BY_PARAMS);
  903.         if ($result->count() > $prevCount) {
  904.             $response?->setMaxAge(60);
  905.         }
  906.         return $this->render('ProfileList/list.html.twig', [
  907.             'profiles' => $result,
  908.             'source' => $this->source,
  909.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  910.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  911.                 'city' => $city->getUriIdentity(),
  912.                 'privateHaircut' => $privateHaircut,
  913.                 'page' => $this->getCurrentPageNumber()
  914.             ]),
  915.             'recommendationSpec' => $specs->recommendationSpec(),
  916.         ], response$response);
  917.     }
  918.     #[ParamConverter("city"converter"city_converter")]
  919.     public function listByNationality(Request $requestCity $citystring $nationality): Response
  920.     {
  921.         if (null === $type Nationalities::getValueByUriIdentity($nationality))
  922.             throw $this->createNotFoundException();
  923.         $specs $this->profileListSpecificationService->listByNationality($nationality);
  924.         $response null;
  925.         try {
  926.             $result $this->listingRotationApi->paginate('/city/{city}/nationality/' $type, ['city' => $city->getId()], $this->getCurrentPageNumber());
  927.             $response = new Response();
  928.             $response->setMaxAge(10);
  929.         } catch (\Exception) {
  930.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  931.         }
  932.         $orX $this->getORSpecForItemsArray(Nationalities::getList(), function ($item): ProfileWithNationality {
  933.             return new ProfileWithNationality($item);
  934.         });
  935.         $prevCount $result->count();
  936.         $result $this->checkEmptyResultNotMasseur($result$city$orXself::RESULT_SOURCE_BY_PARAMS);
  937.         if ($result->count() > $prevCount) {
  938.             $response?->setMaxAge(60);
  939.         }
  940.         return $this->render('ProfileList/list.html.twig', [
  941.             'profiles' => $result,
  942.             'source' => $this->source,
  943.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  944.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  945.                 'city' => $city->getUriIdentity(),
  946.                 'nationality' => $nationality,
  947.                 'page' => $this->getCurrentPageNumber()
  948.             ]),
  949.             'recommendationSpec' => $specs->recommendationSpec(),
  950.         ], response$response);
  951.     }
  952.     #[ParamConverter("city"converter"city_converter")]
  953.     #[ParamConverter("service"options: ['mapping' => ['service' => 'uriIdentity']])]
  954.     public function listByProvidedService(Request $requestCity $cityService $service): Response
  955.     {
  956.         $specs $this->profileListSpecificationService->listByProvidedService($service$city);
  957.         $response null;
  958.         try {
  959.             $result $this->listingRotationApi->paginate('/city/{city}/service/{service}', ['city' => $city->getId(), 'service' => $service->getId()], $this->getCurrentPageNumber());
  960.             $response = new Response();
  961.             $response->setMaxAge(10);
  962.         } catch (\Exception) {
  963.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  964.         }
  965.         $prevCount $result->count();
  966.         $sameGroupServices $this->serviceRepository->findBy(['group' => $service->getGroup()]);
  967.         $orX $this->getORSpecForItemsArray([$sameGroupServices], function ($item): ProfileIsProvidingOneOfServices {
  968.             return new ProfileIsProvidingOneOfServices($item);
  969.         });
  970.         $result $this->checkEmptyResultNotMasseur($result$city$orXself::RESULT_SOURCE_SERVICE);
  971.         if ($result->count() > $prevCount) {
  972.             $response?->setMaxAge(60);
  973.         }
  974.         return $this->render('ProfileList/list.html.twig', [
  975.             'profiles' => $result,
  976.             'source' => $this->source,
  977.             'source_default' => self::RESULT_SOURCE_SERVICE,
  978.             'service' => $service,
  979.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  980.                 'city' => $city->getUriIdentity(),
  981.                 'service' => $service->getUriIdentity(),
  982.                 'page' => $this->getCurrentPageNumber()
  983.             ]),
  984.             'recommendationSpec' => $specs->recommendationSpec(),
  985.         ], response$response);
  986.     }
  987.     /**
  988.      * @Feature("has_archive_page")
  989.      */
  990.     #[ParamConverter("city"converter"city_converter")]
  991.     public function listArchived(Request $requestCity $city): Response
  992.     {
  993.         $result $this->profileList->list($citynullnullnullfalsenullProfileList::ORDER_BY_UPDATED);
  994.         return $this->render('ProfileList/list.html.twig', [
  995.             'profiles' => $result,
  996.             'recommendationSpec' => new \App\Specification\ElasticSearch\ProfileIsNotArchived(), //ProfileIsArchived, согласно https://redminez.net/issues/28305 в реках выводятся неарзивные
  997.         ]);
  998.     }
  999.     #[ParamConverter("city"converter"city_converter")]
  1000.     public function listNew(City $cityint $weeks 2): Response
  1001.     {
  1002.         $specs $this->profileListSpecificationService->listNew($weeks);
  1003.         $response null;
  1004.         try {
  1005.             $result $this->listingRotationApi->paginate('/city/{city}/recent', ['city' => $city->getId()], $this->getCurrentPageNumber());
  1006.             $response = new Response();
  1007.             $response->setMaxAge(10);
  1008.         } catch (\Exception) {
  1009.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1010.         }
  1011.         return $this->render('ProfileList/list.html.twig', [
  1012.             'profiles' => $result,
  1013.             'recommendationSpec' => $specs->recommendationSpec(),
  1014.         ], response$response);
  1015.     }
  1016.     #[ParamConverter("city"converter"city_converter")]
  1017.     public function listByNoRetouch(City $city): Response
  1018.     {
  1019.         $specs $this->profileListSpecificationService->listByNoRetouch();
  1020.         $response null;
  1021.         try {
  1022.             $result $this->listingRotationApi->paginate('/city/{city}/noretouch', ['city' => $city->getId()], $this->getCurrentPageNumber());
  1023.             $response = new Response();
  1024.             $response->setMaxAge(10);
  1025.         } catch (\Exception) {
  1026.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1027.         }
  1028.         $prevCount $result->count();
  1029.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  1030.         if ($result->count() > $prevCount) {
  1031.             $response?->setMaxAge(60);
  1032.         }
  1033.         return $this->render('ProfileList/list.html.twig', [
  1034.             'profiles' => $result,
  1035.             'source' => $this->source,
  1036.             'recommendationSpec' => $specs->recommendationSpec(),
  1037.         ], response$response);
  1038.     }
  1039.     #[ParamConverter("city"converter"city_converter")]
  1040.     public function listByNice(City $city): Response
  1041.     {
  1042.         $specs $this->profileListSpecificationService->listByNice();
  1043.         $response null;
  1044.         try {
  1045.             $result $this->listingRotationApi->paginate('/city/{city}/nice', ['city' => $city->getId()], $this->getCurrentPageNumber());
  1046.             $response = new Response();
  1047.             $response->setMaxAge(10);
  1048.         } catch (\Exception) {
  1049.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1050.         }
  1051.         $prevCount $result->count();
  1052.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  1053.         if ($result->count() > $prevCount) {
  1054.             $response?->setMaxAge(60);
  1055.         }
  1056.         return $this->render('ProfileList/list.html.twig', [
  1057.             'profiles' => $result,
  1058.             'source' => $this->source,
  1059.             'recommendationSpec' => $specs->recommendationSpec(),
  1060.         ], response$response);
  1061.     }
  1062.     #[ParamConverter("city"converter"city_converter")]
  1063.     public function listByOnCall(City $city): Response
  1064.     {
  1065.         $specs $this->profileListSpecificationService->listByOnCall();
  1066.         $response null;
  1067.         try {
  1068.             $result $this->listingRotationApi->paginate('/city/{city}/oncall', ['city' => $city->getId()], $this->getCurrentPageNumber());
  1069.             $response = new Response();
  1070.             $response->setMaxAge(10);
  1071.         } catch (\Exception) {
  1072.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1073.         }
  1074.         $prevCount $result->count();
  1075.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  1076.         if ($result->count() > $prevCount) {
  1077.             $response?->setMaxAge(60);
  1078.         }
  1079.         return $this->render('ProfileList/list.html.twig', [
  1080.             'profiles' => $result,
  1081.             'source' => $this->source,
  1082.             'recommendationSpec' => $specs->recommendationSpec(),
  1083.         ], response$response);
  1084.     }
  1085.     #[ParamConverter("city"converter"city_converter")]
  1086.     public function listForHour(City $city): Response
  1087.     {
  1088.         $specs $this->profileListSpecificationService->listForHour();
  1089.         $response null;
  1090.         try {
  1091.             $result $this->listingRotationApi->paginate('/city/{city}/forhour', ['city' => $city->getId()], $this->getCurrentPageNumber());
  1092.             $response = new Response();
  1093.             $response->setMaxAge(10);
  1094.         } catch (\Exception) {
  1095.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1096.         }
  1097.         $prevCount $result->count();
  1098.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  1099.         if ($result->count() > $prevCount) {
  1100.             $response?->setMaxAge(60);
  1101.         }
  1102.         return $this->render('ProfileList/list.html.twig', [
  1103.             'profiles' => $result,
  1104.             'source' => $this->source,
  1105.             'recommendationSpec' => $specs->recommendationSpec(),
  1106.         ], response$response);
  1107.     }
  1108.     #[ParamConverter("city"converter"city_converter")]
  1109.     public function listForNight(City $city): Response
  1110.     {
  1111.         $specs $this->profileListSpecificationService->listForNight();
  1112.         $response null;
  1113.         try {
  1114.             $result $this->listingRotationApi->paginate('/city/{city}/fornight', ['city' => $city->getId()], $this->getCurrentPageNumber());
  1115.             $response = new Response();
  1116.             $response->setMaxAge(10);
  1117.         } catch (\Exception) {
  1118.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1119.         }
  1120.         $prevCount $result->count();
  1121.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  1122.         if ($result->count() > $prevCount) {
  1123.             $response?->setMaxAge(60);
  1124.         }
  1125.         return $this->render('ProfileList/list.html.twig', [
  1126.             'profiles' => $result,
  1127.             'source' => $this->source,
  1128.             'recommendationSpec' => $specs->recommendationSpec(),
  1129.         ], response$response);
  1130.     }
  1131.     private function getSpecForEliteGirls(City $city): Filter
  1132.     {
  1133.         $minPrice $this->countryCurrencyResolver->getValueByCountryCode($city->getCountryCode(), [
  1134.             'RUB' => 5000,
  1135.             'UAH' => 1500,
  1136.             'USD' => 100,
  1137.             'EUR' => 130,
  1138.         ]);
  1139.         return new ProfileIsElite($minPrice);
  1140.     }
  1141.     private function getElasticSearchSpecForEliteGirls(City $city): ISpecification
  1142.     {
  1143.         $minPrice $this->countryCurrencyResolver->getValueByCountryCode($city->getCountryCode(), [
  1144.             'RUB' => 5000,
  1145.             'UAH' => 1500,
  1146.             'USD' => 100,
  1147.             'EUR' => 130,
  1148.         ]);
  1149.         return new \App\Specification\ElasticSearch\ProfileIsElite($minPrice);
  1150.     }
  1151.     #[ParamConverter("city"converter"city_converter")]
  1152.     public function listForEliteGirls(CountryCurrencyResolver $countryCurrencyResolverRequest $requestCity $city): Response
  1153.     {
  1154.         $specs $this->profileListSpecificationService->listForEliteGirls($city);
  1155.         $response null;
  1156.         try {
  1157.             $result $this->listingRotationApi->paginate('/city/{city}/elite', ['city' => $city->getId()], $this->getCurrentPageNumber());
  1158.             $response = new Response();
  1159.             $response->setMaxAge(10);
  1160.         } catch (\Exception) {
  1161.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1162.         }
  1163.         $prevCount $result->count();
  1164.         if ($this->features->fill_empty_profile_list() && $result->count() == 0) {
  1165.             $prices = [
  1166.                 'RUB' => 5000,
  1167.                 'UAH' => 1500,
  1168.                 'USD' => 100,
  1169.                 'EUR' => 130,
  1170.             ];
  1171.             $currency $countryCurrencyResolver->getCurrencyFor($city->getCountryCode());
  1172.             if (isset($prices[$currency])) {
  1173.                 $minPrice $prices[$currency];
  1174.                 switch ($currency) {
  1175.                     case 'RUB':
  1176.                         $diff 1000;
  1177.                         break;
  1178.                     case 'UAH':
  1179.                         $diff 500;
  1180.                         break;
  1181.                     case 'USD':
  1182.                     case 'EUR':
  1183.                         $diff 20;
  1184.                         break;
  1185.                     default:
  1186.                         throw new \LogicException('Unexpected currency code');
  1187.                 }
  1188.                 while ($minPrice >= $diff) {
  1189.                     $minPrice -= $diff;
  1190.                     $result $this->listRandomSinglePage($citynullProfileWithApartmentsOneHourPrice::moreExpensiveThan($minPrice), nulltruefalse);
  1191.                     if ($result->count() > 0) {
  1192.                         $this->source self::RESULT_SOURCE_BY_PARAMS;
  1193.                         break;
  1194.                     }
  1195.                 }
  1196.                 $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  1197.             }
  1198.         }
  1199.         if ($result->count() > $prevCount) {
  1200.             $response?->setMaxAge(60);
  1201.         }
  1202.         return $this->render('ProfileList/list.html.twig', [
  1203.             'profiles' => $result,
  1204.             'source' => $this->source,
  1205.             'source_default' => self::RESULT_SOURCE_BY_PARAMS,
  1206.             'category_url' => $this->generateUrl($request->attributes->get('_route'), [
  1207.                 'city' => $city->getUriIdentity(),
  1208.                 'page' => $this->getCurrentPageNumber()
  1209.             ]),
  1210.             'recommendationSpec' => $specs->recommendationSpec(),
  1211.         ], response$response);
  1212.     }
  1213.     #[ParamConverter("city"converter"city_converter")]
  1214.     public function listForRealElite(CountryCurrencyResolver $countryCurrencyResolverCity $city): Response
  1215.     {
  1216.         $specs $this->profileListSpecificationService->listForRealElite($city);
  1217.         $response null;
  1218.         try {
  1219.             $result $this->listingRotationApi->paginate('/city/{city}/realelite', ['city' => $city->getId()], $this->getCurrentPageNumber());
  1220.             $response = new Response();
  1221.             $response->setMaxAge(10);
  1222.         } catch (\Exception) {
  1223.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1224.         }
  1225.         $prevCount $result->count();
  1226.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  1227.         if ($result->count() > $prevCount) {
  1228.             $response?->setMaxAge(60);
  1229.         }
  1230.         return $this->render('ProfileList/list.html.twig', [
  1231.             'profiles' => $result,
  1232.             'source' => $this->source,
  1233.             'recommendationSpec' => $specs->recommendationSpec(),
  1234.         ], response$response);
  1235.     }
  1236.     #[ParamConverter("city"converter"city_converter")]
  1237.     public function listForVipPros(CountryCurrencyResolver $countryCurrencyResolverCity $city): Response
  1238.     {
  1239.         $specs $this->profileListSpecificationService->listForVipPros($city);
  1240.         $response null;
  1241.         try {
  1242.             $result $this->listingRotationApi->paginate('/city/{city}/vip', ['city' => $city->getId()], $this->getCurrentPageNumber());
  1243.             $response = new Response();
  1244.             $response->setMaxAge(10);
  1245.         } catch (\Exception) {
  1246.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1247.         }
  1248.         $prevCount $result->count();
  1249.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  1250.         if ($result->count() > $prevCount) {
  1251.             $response?->setMaxAge(60);
  1252.         }
  1253.         return $this->render('ProfileList/list.html.twig', [
  1254.             'profiles' => $result,
  1255.             'source' => $this->source,
  1256.             'recommendationSpec' => $specs->recommendationSpec(),
  1257.         ], response$response);
  1258.     }
  1259.     #[ParamConverter("city"converter"city_converter")]
  1260.     public function listForVipIndividual(CountryCurrencyResolver $countryCurrencyResolverCity $city): Response
  1261.     {
  1262.         $specs $this->profileListSpecificationService->listForVipIndividual($city);
  1263.         $response null;
  1264.         try {
  1265.             $result $this->listingRotationApi->paginate('/city/{city}/vipindi', ['city' => $city->getId()], $this->getCurrentPageNumber());
  1266.             $response = new Response();
  1267.             $response->setMaxAge(10);
  1268.         } catch (\Exception) {
  1269.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1270.         }
  1271.         $prevCount $result->count();
  1272.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  1273.         if ($result->count() > $prevCount) {
  1274.             $response?->setMaxAge(60);
  1275.         }
  1276.         return $this->render('ProfileList/list.html.twig', [
  1277.             'profiles' => $result,
  1278.             'source' => $this->source,
  1279.             'recommendationSpec' => $specs->recommendationSpec(),
  1280.         ], response$response);
  1281.     }
  1282.     #[ParamConverter("city"converter"city_converter")]
  1283.     public function listForVipGirlsCity(City $city): Response
  1284.     {
  1285.         $specs $this->profileListSpecificationService->listForVipGirlsCity($city);
  1286.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1287.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  1288.         return $this->render('ProfileList/list.html.twig', [
  1289.             'profiles' => $result,
  1290.             'source' => $this->source,
  1291.             'recommendationSpec' => $specs->recommendationSpec(),
  1292.         ]);
  1293.     }
  1294.     #[ParamConverter("city"converter"city_converter")]
  1295.     public function listOfGirlfriends(City $city): Response
  1296.     {
  1297.         $specs $this->profileListSpecificationService->listOfGirlfriends();
  1298.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1299.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  1300.         return $this->render('ProfileList/list.html.twig', [
  1301.             'profiles' => $result,
  1302.             'source' => $this->source,
  1303.             'recommendationSpec' => $specs->recommendationSpec(),
  1304.         ]);
  1305.     }
  1306.     #[ParamConverter("city"converter"city_converter")]
  1307.     public function listOfMostExpensive(City $city): Response
  1308.     {
  1309.         $specs $this->profileListSpecificationService->listOfMostExpensive($city);
  1310.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec());
  1311.         $result $this->checkEmptyResultNotMasseur($result$citynullself::RESULT_SOURCE_CITY);
  1312.         return $this->render('ProfileList/list.html.twig', [
  1313.             'profiles' => $result,
  1314.             'source' => $this->source,
  1315.             'recommendationSpec' => $specs->recommendationSpec(),
  1316.         ]);
  1317.     }
  1318.     #[ParamConverter("city"converter"city_converter")]
  1319.     public function listBdsm(City $cityServiceRepository $serviceRepositoryParameterBagInterface $parameterBag): Response
  1320.     {
  1321.         $specs $this->profileListSpecificationService->listBdsm();
  1322.         $response null;
  1323.         try {
  1324.             $result $this->listingRotationApi->paginate('/city/{city}/bdsm', ['city' => $city->getId()], $this->getCurrentPageNumber());
  1325.             $response = new Response();
  1326.             $response->setMaxAge(10);
  1327.         } catch (\Exception) {
  1328.             $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec(), $specs->additionalSpecs());
  1329.         }
  1330.         $bdsmIds $serviceRepository->findBy(['group' => ServiceGroups::BDSM]);
  1331.         return $this->render('ProfileList/list.html.twig', [
  1332.             'profiles' => $result,
  1333.             'recommendationSpec' => $specs->recommendationSpec(),
  1334.         ], response$response);
  1335.     }
  1336.     #[ParamConverter("city"converter"city_converter")]
  1337.     public function listByGender(City $citystring $genderDefaultCityProvider $defaultCityProvider): Response
  1338.     {
  1339.         if ($city->getId() != $defaultCityProvider->getDefaultCity()->getId()) {
  1340.             throw $this->createNotFoundException();
  1341.         }
  1342.         if (null === Genders::getValueByUriIdentity($gender))
  1343.             throw $this->createNotFoundException();
  1344.         $specs $this->profileListSpecificationService->listByGender($gender);
  1345.         $result $this->listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement($city$specs->spec(), $specs->additionalSpecs(), $specs->genders());
  1346.         return $this->render('ProfileList/list.html.twig', [
  1347.             'profiles' => $result,
  1348.             'recommendationSpec' => $specs->recommendationSpec(),
  1349.         ]);
  1350.     }
  1351.     protected function checkCityAndCountrySource(Page $resultCity $city): Page
  1352.     {
  1353.         if (($result && $result->count() != 0) || false == $this->features->fill_empty_profile_list())
  1354.             return $result;
  1355.         $this->source self::RESULT_SOURCE_CITY;
  1356.         $result $this->listRandomSinglePage($citynullnullnulltruefalse);
  1357.         if ($result->count() == 0) {
  1358.             $this->source self::RESULT_SOURCE_COUNTRY;
  1359.             $result $this->listRandomSinglePage($city$city->getCountryCode(), nullnulltruefalse);
  1360.         }
  1361.         return $result;
  1362.     }
  1363.     protected function checkEmptyResultNotMasseur(Page $resultCity $city, ?OrX $alternativeSpecstring $source): Page
  1364.     {
  1365.         if ($result->count() != || false == $this->features->fill_empty_profile_list())
  1366.             return $result;
  1367.         if (null != $alternativeSpec) {
  1368.             $this->source $source;
  1369.             $result $this->listRandomSinglePage($citynull$alternativeSpecnulltruefalse);
  1370.         }
  1371.         if ($result->count() == 0)
  1372.             $result $this->checkCityAndCountrySource($result$city);
  1373.         return $result;
  1374.     }
  1375.     /**
  1376.      * Сейчас не используется, решили доставать их всех соседних подкатегорий разом.
  1377.      * Пока оставил, вдруг передумают.
  1378.      * @deprecated
  1379.      */
  1380.     public function listByNextSimilarCategories(callable $listMethod$requestCategory, array $similarItems): ORMQueryResult
  1381.     {
  1382.         $similarItems array_filter($similarItems, function ($item) use ($requestCategory): bool {
  1383.             return $item != $requestCategory;
  1384.         });
  1385.         //shuffle($similarItems);
  1386.         $item null;
  1387.         $result null;
  1388.         do {
  1389.             $item $item == null current($similarItems) : next($similarItems);
  1390.             if (false === $item)
  1391.                 return $result;
  1392.             $result $listMethod($item);
  1393.         } while ($result->count() == 0);
  1394.         return $result;
  1395.     }
  1396.     protected function getCurrentPageNumber(): int
  1397.     {
  1398.         $page = (int) $this->requestStack->getCurrentRequest()?->get($this->pageParameter1);
  1399.         if ($page 1) {
  1400.             $page 1;
  1401.         }
  1402.         return $page;
  1403.     }
  1404.     protected function render(string $view, array $parameters = [], Response $response null): Response
  1405.     {
  1406.         $this->listingService->setCurrentListingPage($parameters['profiles']);
  1407.         $requestAttrs $this->requestStack->getCurrentRequest();
  1408.         $listing $requestAttrs->get('_controller');
  1409.         $listing is_array($listing) ? $listing[count($listing) - 1] : $listing;
  1410.         $listing preg_replace('/[^:]+::/'''$listing);
  1411.         $listingParameters $requestAttrs->get('_route_params');
  1412.         $listingParameters is_array($listingParameters) ? $listingParameters : [];
  1413.         $mainRequestHasPageParam = isset(($this->requestStack->getMainRequest()->get('_route_params') ?? [])['page']);
  1414.         if ($this->requestStack->getCurrentRequest()->isXmlHttpRequest()) {
  1415.             $view = (
  1416.                 str_starts_with($listing'list')
  1417.                 && 'ProfileList/list.html.twig' === $view
  1418.                 && $mainRequestHasPageParam //isset($listingParameters['page'])
  1419.             )
  1420.                 ? 'ProfileList/list.profiles.html.twig'
  1421.                 $view;
  1422.             return $this->prepareForXhr(parent::render($view$parameters$response));
  1423.             //return $this->getJSONResponse($parameters);
  1424.         } else {
  1425.             $parameters array_merge($parameters, [
  1426.                 'listing' => $listing,
  1427.                 'listing_parameters' => $listingParameters,
  1428.             ]);
  1429.             return parent::render($view$parameters$response);
  1430.         }
  1431.     }
  1432.     private function listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement(
  1433.         City $city,
  1434.         ?Filter $spec,
  1435.         array $additionalSpecs null,
  1436.         array $genders = [Genders::FEMALE]
  1437.     ): array|Page {
  1438.         return $this->profileList->listActiveWithinCityOrderedByStatusWithSpec($city$spec$additionalSpecs$genders$this->getCurrentPageNumber() < 2);
  1439.     }
  1440.     private function listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacementLimited(
  1441.         City $city,
  1442.         ?Filter $spec,
  1443.         array $additionalSpecs null,
  1444.         array $genders = [Genders::FEMALE],
  1445.         int $limit 0,
  1446.     ): array|Page {
  1447.         return $this->profileList->listActiveWithinCityOrderedByStatusWithSpecLimited($city$spec$additionalSpecs$genderstrue$limit);
  1448.     }
  1449.     private function listRandomSinglePage(
  1450.         City $city,
  1451.         ?string $country,
  1452.         ?Filter $spec,
  1453.         ?array $additionalSpecs,
  1454.         bool $active,
  1455.         ?bool $masseur false,
  1456.         array $genders = [Genders::FEMALE]
  1457.     ): Page {
  1458.         return $this->profileList->listRandom($city$country$spec$additionalSpecs$active$masseur$genderstrue);
  1459.     }
  1460.     private function shuffleProfilesOnPage(Page $result): Page
  1461.     {
  1462.         $profiles iterator_to_array($result->getIterator());
  1463.         if(count($profiles) > 1) {
  1464.             shuffle($profiles);
  1465.         }
  1466.         return new FakeORMQueryPage(
  1467.             $result->getCurrentOffset(),
  1468.             $result->getCurrentPage(),
  1469.             $result->getCurrentLimit(),
  1470.             $result->totalCount(),
  1471.             $profiles
  1472.         );
  1473.     }
  1474.     /**
  1475.      * Достает из списка анкет их id с учетом совместимости разных форматов данных
  1476.      */
  1477.     private function extractProfileIds(array $profiles): array
  1478.     {
  1479.         $ids array_map(static function ($item) {
  1480.             /**
  1481.              * - array - данные из микросервиса ротации через API
  1482.              * - Profile::getId() - полноценная сущность анкеты
  1483.              * - ProfileListingReadModel::$id - read-model анкеты
  1484.              */
  1485.             return is_array($item) ? $item['id'] : ($item?->id ?? $item?->getId());
  1486.         }, $profiles);
  1487.         return array_filter($ids); // remove null values
  1488.     }
  1489. //    protected function getJSONResponse(array $parameters)
  1490. //    {
  1491. //        $request = $this->request;
  1492. //        $data = json_decode($request->getContent(), true);
  1493. //
  1494. //        $imageSize = !empty($data['imageSize']) ? $data['imageSize'] : "357x500";
  1495. //
  1496. //        /** @var FakeORMQueryPage $queryPage */
  1497. //        $queryPage = $parameters['profiles'];
  1498. //
  1499. //        $profiles = array_map(function(ProfileListingReadModel $profile) use ($imageSize) {
  1500. //            $profile->stations = array_values($profile->stations);
  1501. //            $profile->avatar['path'] = $this->responsiveAssetsService->getResponsiveImageUrl($profile->avatar['path'], 'profile_media', $imageSize, 'jpg');
  1502. //            $profile->uri = $this->generateUrl('profile_preview.page', ['city' => $profile->city->uriIdentity, 'profile' => $profile->uriIdentity]);
  1503. //            return $profile;
  1504. //        }, $queryPage->getArray());
  1505. //
  1506. //        return new JsonResponse([
  1507. //            'profiles' => $profiles,
  1508. //            'currentPage' => $queryPage->getCurrentPage(),
  1509. //        ], Response::HTTP_OK);
  1510. //    }
  1511. }