src/Service/ProfileList.php line 85

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