src/Service/ProfileList.php line 290

Open in your IDE?
  1. <?php
  2. namespace App\Service;
  3. use App\Bridge\Porpaginas\Doctrine\ORM\FakeORMQueryPage;
  4. use App\Entity\Location\City;
  5. use App\Entity\Profile\Genders;
  6. use App\Event\PaginatorPageTakenEvent;
  7. use App\Repository\ProfileRepository;
  8. use App\Repository\ReadModel\ProfileListingReadModel;
  9. use App\Specification\Profile\ICountIdSpec;
  10. use App\Specification\Profile\ISelectIdListSpec;
  11. use App\Specification\Profile\ProfileHasOneOfGenders;
  12. use App\Specification\Profile\ProfileIdINOrderedByINValues;
  13. use App\Specification\Profile\ProfileIdNotIn;
  14. use App\Specification\Profile\ProfileIsActive;
  15. use App\Specification\Profile\ProfileIsArchived;
  16. use App\Specification\Profile\ProfileIsLocatedInCountry;
  17. use App\Specification\Profile\ProfileIsLocated;
  18. use App\Specification\Profile\ProfileIsMasseur;
  19. use App\Specification\Profile\ProfileIsModerationPassed;
  20. use App\Specification\Profile\ProfileIsNotMasseur;
  21. use App\Specification\Profile\ProfileIsNotRejected;
  22. use App\Specification\QueryModifier\FreeProfilesFeatureArchivedProfileOrder;
  23. use App\Specification\QueryModifier\FreeProfilesFeatureProfileOrder;
  24. use App\Specification\Profile\ProfileIsHidden;
  25. use App\Specification\Profile\ProfileIsNotHidden;
  26. use App\Specification\QueryModifier\LimitResult;
  27. use App\Specification\QueryModifier\ProfileOrderedByCreated;
  28. use App\Specification\QueryModifier\ProfileOrderedByInactivated;
  29. use App\Specification\QueryModifier\ProfileOrderedByRandom;
  30. use App\Specification\QueryModifier\ProfileOrderedByUpdated;
  31. use App\Specification\QueryModifier\ProfileOrderedByStatus;
  32. use App\Specification\QueryModifier\ProfilePlacementHiding;
  33. use Doctrine\ORM\EntityManagerInterface;
  34. use Happyr\DoctrineSpecification\Filter\Filter;
  35. use Happyr\DoctrineSpecification\Logic\AndX;
  36. use Happyr\DoctrineSpecification\Spec;
  37. use Happyr\DoctrineSpecification\Specification\Specification;
  38. use Porpaginas\Page;
  39. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  40. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  41. use Symfony\Component\HttpFoundation\Request;
  42. use Symfony\Component\HttpFoundation\RequestStack;
  43. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  44. class ProfileList
  45. {
  46.     public const ORDER_BY_STATUS 'status';
  47.     public const ORDER_BY_UPDATED 'updated';
  48.     public const ORDER_NONE 'none';
  49.     private ?Request $request;
  50.     private int $perPageDefault;
  51.     private int $perPage;
  52.     private array $executedCountQueryData;
  53.     public function __construct(
  54.         private ProfileRepository $profileRepository,
  55.         RequestStack $requestStack,
  56.         private Features $features,
  57.         ParameterBagInterface $parameterBag,
  58.         private EventDispatcherInterface $eventDispatcher,
  59.         private EntityManagerInterface $entityManager,
  60.         private ProfileTopBoard $profileTopBoard
  61.     )
  62.     {
  63.         $this->request $requestStack->getCurrentRequest();
  64.         $this->perPageDefault intval($parameterBag->get('profile_list.per_page'));
  65.         $this->perPage $this->perPageDefault;
  66.     }
  67.     public function listActiveWithinCityOrderedByStatusWithSpecAvoidingTopPlacement(
  68.         City $city, ?Filter $spec, array $additionalSpecs null, array $genders = [Genders::FEMALE]
  69.     ): array|Page
  70.     {
  71.         return $this->listActiveWithinCityOrderedByStatusWithSpec($city$spec$additionalSpecs$genderstrue);
  72.     }
  73.     public function listActiveWithinCityOrderedByStatusWithSpec(
  74.         City $city, ?Filter $spec, array $additionalSpecs null, array $genders = [Genders::FEMALE], bool $avoidTopPlacement false
  75.     ): array|Page
  76.     {
  77.         return $this->list($citynull$spec$additionalSpecstruenullself::ORDER_BY_STATUS,
  78.             nulltruenull$genders$avoidTopPlacement);
  79.     }
  80.     public function listActiveWithinCityOrderedByStatusWithSpecLimited(
  81.         City $city, ?Filter $spec, array $additionalSpecs null, array $genders = [Genders::FEMALE], bool $avoidTopPlacement falseint $limit 0,
  82.     ): array|Page
  83.     {
  84.         return $this->list($citynull$spec$additionalSpecstruenullself::ORDER_BY_STATUS,
  85.             $limitfalsenull$genders$avoidTopPlacement);
  86.     }
  87.     public function listActiveNotMasseurWithinCityOrderedByStatusWithSpec(
  88.         City $city, ?Filter $spec, array $additionalSpecs null, array $genders = [Genders::FEMALE]
  89.     ): array|Page
  90.     {
  91.         return $this->list($citynull$spec$additionalSpecstruefalseself::ORDER_BY_STATUS,
  92.             nulltruenull$genders);
  93.     }
  94.     public function list(
  95.         City $city, ?string $country, ?Filter $spec, ?array $additionalSpecsbool $active, ?bool $masseur false,
  96.         ?string $order self::ORDER_BY_STATUS, ?int $limit nullbool $paged true, ?callable $fetchByIdMethod null,
  97.         array $genders = [Genders::FEMALE], bool $avoidTopPlacement false
  98.     ): array|Page
  99.     {
  100. //        $this->perPage = $limit ?? $this->perPageDefault;
  101.         $this->perPage $this->perPageDefault;
  102.         $topPlacementToAvoidId null;
  103.         if(/*null === $limit && */true === $avoidTopPlacement) {
  104.             $profileTopPlacement $this->profileTopBoard->currentTopPlacement(false);
  105.             if(null !== $profileTopPlacement) {
  106.                 $topPlacementToAvoidId $profileTopPlacement->getId();
  107.                 //на тесте часто ставят 1 на страницу, что ломает логику, т.к. после декремента становится 0 на страницу,
  108.                 //за тем и условие
  109.                 if($this->perPage 1) {
  110.                     $this->perPage--;
  111.                 }
  112.             }
  113.         }
  114.         $order $this->getOrderSpecByFlags($order$active);
  115.         $masseurSpec $this->getMasseurSpecByFlag($masseur);
  116.         $activeSpec $this->getActiveSpecByFlag($active);
  117.         $excludeTopProfileSpec null !== $topPlacementToAvoidId ? new ProfileIdNotIn([$topPlacementToAvoidId]) : null;
  118.         $criteria Spec::andX(
  119.             $country ProfileIsLocatedInCountry::withCountryCode($city->getCountryCode()) : ProfileIsLocated::withinCity($city),
  120.             $activeSpec
  121.         );
  122.         if($masseurSpec)
  123.             $criteria->andX($masseurSpec);
  124.         if(null !== $excludeTopProfileSpec)
  125.             $criteria->andX($excludeTopProfileSpec);
  126.         $criteria->andX($this->getModerationSpecByFlag());
  127.         $criteria->andX(new ProfileHasOneOfGenders($genders));
  128.         $criteriaForIdList = clone $criteria;
  129.         if($paged) {
  130.             $count $this->countOfCriteriaWithCustomSpec($criteria$spec$additionalSpecs);
  131.             if(== $count)
  132.                 return $this->takeFakePage($count, []);
  133.         }
  134.         if($order)
  135.             $criteriaForIdList->andX($order);
  136.         $idList $this->listIdOfCriteriaWithCustomSpec($criteriaForIdList$spec$additionalSpecs$paged$limit);
  137.         $profiles = [];
  138.         if(!empty($idList)) {
  139.             // $profiles = $this->profileRepository->matchingSpecRaw(new ProfileIdINOrderedByINValues($idList), null, true, array($this->profileRepository, 'hydrateProfileRow'));
  140.             $profiles null == $fetchByIdMethod
  141.                 $this->profileRepository->fetchListingByIds(new ProfileIdINOrderedByINValues($idList))
  142.                 : $fetchByIdMethod(new ProfileIdINOrderedByINValues($idList));
  143.         }
  144.         if($paged) {
  145.             $profiles $this->takeFakePage($count$profiles);
  146.         }
  147.         $this->restorePerPageToDefault();
  148.         return $profiles;
  149.     }
  150.     public function listForMap(
  151.         City $city, ?string $country, ?Filter $spec, ?array $additionalSpecsbool $active, ?bool $masseur false,
  152.         ?string $order self::ORDER_BY_STATUS, array $genders = [Genders::FEMALE], int $coordsRoundPrecision 3,
  153.     ): array|Page
  154.     {
  155.         $order $this->getOrderSpecByFlags($order$active);
  156.         $masseurSpec $this->getMasseurSpecByFlag($masseur);
  157.         $activeSpec $this->getActiveSpecByFlag($active);
  158.         $criteria Spec::andX(
  159.             $country ProfileIsLocatedInCountry::withCountryCode($city->getCountryCode()) : ProfileIsLocated::withinCity($city),
  160.             $activeSpec
  161.         );
  162.         if($masseurSpec)
  163.             $criteria->andX($masseurSpec);
  164.         $criteria->andX($this->getModerationSpecByFlag());
  165.         $criteria->andX(new ProfileHasOneOfGenders($genders));
  166.         if(null == $additionalSpecs)
  167.             $additionalSpecs = [];
  168.         array_unshift($additionalSpecs$spec);
  169.         array_walk($additionalSpecs, function($spec) use ($criteria): void {
  170.             $criteria->andX($spec);
  171.         });
  172.         $profiles $this->profileRepository->listForMapMatchingSpec($criteria$coordsRoundPrecision);
  173.         return $profiles;
  174.     }
  175.     protected function countOfCriteriaWithCustomSpec(AndX $criteria, ?Filter $spec, array $additionalSpecs null): int
  176.     {
  177.         if(null == $additionalSpecs)
  178.             $additionalSpecs = [];
  179.         array_unshift($additionalSpecs$spec);
  180.         array_walk($additionalSpecs, function($spec) use ($criteria): void {
  181.             $criteria->andX($spec);
  182.             if($spec instanceof ICountIdSpec)
  183.                 $criteria->andX($spec->getCountIdSpec());
  184.         });
  185.         $defaultStack $this->entityManager->getConnection()->getConfiguration()->getSQLLogger();
  186.         $stack = new \Doctrine\DBAL\Logging\DebugStack();
  187.         $this->entityManager->getConnection()->getConfiguration()->setSQLLogger($stack);
  188.         $result $this->profileRepository->countMatchingSpec($criteria);
  189.         //$this->executedCountQueryData = ['sql' => 'SELECT count( p0_.id ) AS sclr_0 FROM `profiles` p0_ INNER JOIN profile_adboard_placements p1_ ON p0_.id = p1_.profile_id  WHERE ( p0_.deleted_at IS NULL )', 'params' => [], 'types' => []];//
  190.         $this->executedCountQueryData $stack->queries[count($stack->queries)];
  191.         $this->entityManager->getConnection()->getConfiguration()->setSQLLogger($defaultStack);
  192.         return $result;
  193.     }
  194.     protected function listIdOfCriteriaWithCustomSpec(AndX $criteria, ?Filter $spec, array $additionalSpecs nullbool $paged true, ?int $limit null): array
  195.     {
  196.         if(null == $additionalSpecs)
  197.             $additionalSpecs = [];
  198.         array_unshift($additionalSpecs$spec);
  199.         array_walk($additionalSpecs, function($spec) use ($criteria): void {
  200.             $criteria->andX($spec);
  201.             if($spec instanceof ISelectIdListSpec)
  202.                 $criteria->andX($spec->getIdListSpec());
  203.         });
  204.         return $this->profileRepository->listIdMatchingSpec(
  205.             $criteria,
  206.             $paged $this->getOffset() : 0,
  207.             $paged $this->perPage $limit
  208. //            $limit ?: $this->perPage
  209.         );
  210.     }
  211.     protected function getPage(): int
  212.     {
  213.         $page = (int)$this->request->get('page');
  214.         if ($page 1)
  215.             $page 1;
  216.         return $page;
  217.     }
  218.     protected function getOffset(): float|int
  219.     {
  220.         return ($this->getPage() - 1) * $this->perPage;
  221.     }
  222.     protected function takeFakePage(int $totalResults, array $profiles): Page
  223.     {
  224.         //если передана страница, которой нет в основной выборке
  225.         //if($totalResults != 0 && $this->getPage() > ceil($totalResults / $this->perPage))
  226.         if($totalResults && $this->getPage() != && $this->getOffset() > $totalResults 1)
  227.             throw new NotFoundHttpException('Page number doesn\'t exist');
  228.         $profileIds array_map(function(ProfileListingReadModel $profileListingReadModel): int {
  229.             return $profileListingReadModel->id;
  230.         }, $profiles);
  231.         $this->eventDispatcher->dispatch(new PaginatorPageTakenEvent($profiles), PaginatorPageTakenEvent::NAME);
  232.         return new FakeORMQueryPage($this->getOffset(), $this->getPage(), $this->perPage$totalResults$profiles);
  233.     }
  234.     public function listRandom(
  235.         City $city, ?string $country, ?Filter $spec, ?array $additionalSpecsbool $active, ?bool $masseur false,
  236.         array $genders = [Genders::FEMALE], bool $paged true, ?int $limit null
  237.     ): array|Page
  238.     {
  239.         $masseurSpec $this->getMasseurSpecByFlag($masseur);
  240.         $activeSpec $this->getActiveSpecByFlag($active);
  241.         $criteria Spec::andX(
  242.             $country ProfileIsLocatedInCountry::withCountryCode($city->getCountryCode()) : ProfileIsLocated::withinCity($city),
  243.             $activeSpec
  244.         );
  245.         if($masseurSpec)
  246.             $criteria->andX($masseurSpec);
  247.         $criteria->andX($this->getModerationSpecByFlag());
  248.         $criteria->andX(new ProfileHasOneOfGenders($genders));
  249.         $criteria->andX(new ProfileOrderedByRandom());
  250.         $idList $this->listIdOfCriteriaWithCustomSpec($criteria$spec$additionalSpecs$paged$limit);
  251.         $profiles = [];
  252.         if(!empty($idList)) {
  253.             $profiles $this->profileRepository->fetchListingByIds(new ProfileIdINOrderedByINValues($idList));
  254.         }
  255.         if($paged) {
  256.             $profiles $this->takeFakePage(count($profiles)/*$this->perPage*/$profiles);
  257.         }
  258.         return $profiles;
  259.     }
  260.     public function listRecent(City $cityint $count, array $genders = [Genders::FEMALE]): array
  261.     {
  262.         $criteria Spec::andX(
  263.             //new ProfileAdBoardPlacement(),
  264.             new ProfileIsActive(),
  265.             new ProfilePlacementHiding(),
  266.             new \App\Specification\QueryModifier\ProfileAvatar(),
  267.             // убрано чтобы не было мало записей на маленьких городах
  268.             // new ProfileIsNew(),
  269.             ProfileIsLocated::withinCity($city),
  270.             new ProfileOrderedByCreated(),
  271.             new LimitResult($count)
  272.         );
  273.         $criteria->andX($this->getActiveSpecByFlag(true));
  274.         $criteria->andX($this->getModerationSpecByFlag());
  275.         $criteria->andX(new ProfileHasOneOfGenders($genders));
  276.         $result $this->profileRepository->matchingSpecRaw($criterianullfalse);
  277.         return $result;
  278.     }
  279.     public function listBySpec(?Filter $spec, ?array $additionalSpecs null, ?int $limit nullbool $paged true, ?callable $fetchByIdMethod null): array|Page
  280.     {
  281.         $this->perPage $limit ?? $this->perPageDefault;
  282.         $criteria Spec::andX(
  283.             $spec
  284.         );
  285.         $criteriaForIdList = clone $criteria;
  286.         if($paged) {
  287.             $count $this->countOfCriteriaWithCustomSpec($criteria$spec$additionalSpecs);
  288.             if(== $count)
  289.                 return $this->takeFakePage($count, []);
  290.         }
  291.         $idList $this->listIdOfCriteriaWithCustomSpec($criteriaForIdList$spec$additionalSpecs$paged);
  292.         $profiles = [];
  293.         if(!empty($idList)) {
  294.             # $fetchByIdMethod передается если нужно, чтобы результат был не в виде ProfileListingReadModel, например.
  295.             # Как вариант - [$profileRepository, 'findByIds']
  296.             $profiles null == $fetchByIdMethod
  297.                 $this->profileRepository->fetchListingByIds(new ProfileIdINOrderedByINValues($idList))
  298.                 : $fetchByIdMethod($idList);
  299.         }
  300.         if($paged) {
  301.             $profiles $this->takeFakePage($count$profiles);
  302.         }
  303.         $this->restorePerPageToDefault();
  304.         return $profiles;
  305.     }
  306.     protected function getMasseurSpecByFlag(?bool $masseur): ?Specification
  307.     {
  308.         if(true === $masseur) {
  309.             $masseurSpec = new ProfileIsMasseur();
  310.         } else if(false === $masseur) {
  311.             $masseurSpec = new ProfileIsNotMasseur();
  312.         } else {
  313.             $masseurSpec null;
  314.         }
  315.         return $masseurSpec;
  316.     }
  317.     public function getActiveSpecByFlag(bool $active): ProfileIsHidden|ProfileIsArchived|ProfileIsNotHidden|ProfileIsActive
  318.     {
  319.         if($active) {
  320.             $activeSpec $this->features->free_profiles() ? new ProfileIsNotHidden() : new ProfileIsActive();
  321.         } else {
  322.             $activeSpec $this->features->free_profiles() ? new ProfileIsHidden() : new ProfileIsArchived();
  323.         }
  324.         return $activeSpec;
  325.     }
  326.     public function getModerationSpecByFlag(): ProfileIsModerationPassed|ProfileIsNotRejected
  327.     {
  328.         return $this->features->hard_moderation() ? new ProfileIsModerationPassed() : new ProfileIsNotRejected();
  329.     }
  330.     protected function getOrderSpecByFlags(string $orderbool $active): string|ProfileOrderedByStatus|ProfileOrderedByUpdated|ProfileOrderedByInactivated|FreeProfilesFeatureProfileOrder|FreeProfilesFeatureArchivedProfileOrder
  331.     {
  332.         switch($order) {
  333.             case self::ORDER_BY_UPDATED:
  334.                 $defaultOrder = new ProfileOrderedByUpdated();
  335.                 break;
  336.             case self::ORDER_NONE:
  337.                 $defaultOrder null;
  338.                 break;
  339.             case self::ORDER_BY_STATUS:
  340.             default:
  341.                 $defaultOrder = new ProfileOrderedByStatus();
  342.                 break;
  343.         }
  344.         if(null != $defaultOrder) {
  345.             if ($this->features->free_profiles()) {
  346.                 $order $active ? new FreeProfilesFeatureProfileOrder($this->features->consider_approved_priority()) : new FreeProfilesFeatureArchivedProfileOrder();
  347.             } else {
  348.                 $order $active $defaultOrder : new ProfileOrderedByInactivated();
  349.             }
  350.         }
  351.         return $order;
  352.     }
  353.     public function executedCountQueryData(): array
  354.     {
  355.         return $this->executedCountQueryData;
  356.     }
  357.     protected function restorePerPageToDefault(): void
  358.     {
  359.         $this->perPage $this->perPageDefault;
  360.     }
  361. }