<?php
declare(strict_types=1);
namespace App\Service;
use App\Bridge\Porpaginas\Doctrine\ORM\FakeORMQueryPage;
use App\Entity\Location\City;
use App\Entity\Profile\Genders;
use App\Entity\Profile\Profile;
use Porpaginas\Page;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
class ProfileListingRecommendationsFiller
{
private int $perPage;
public function __construct(
private ColdStartProfileRecommendationsCache $coldStartProfileRecommendationsCache,
private ProfileList $profileList,
ParameterBagInterface $parameterBag,
) {
$this->perPage = (int)$parameterBag->get('profile_list.per_page');
}
public function fill(
City $city,
Page $profiles,
array $genders = [Genders::FEMALE],
): array
{
if (!$this->shouldFill($profiles)) {
$currentProfiles = iterator_to_array($profiles->getIterator());
return [
'profiles' => $profiles,
'applied' => false,
'original_count' => $profiles->totalCount(),
'filled_profile_ids' => [],
'visible_profile_ids' => $this->extractProfileIds($currentProfiles),
];
}
$currentProfiles = iterator_to_array($profiles->getIterator());
$excludeProfileIds = $this->extractProfileIds($currentProfiles);
$neededCount = $this->perPage - count($currentProfiles);
$recommendedListingProfiles = $this->loadPopularCityProfiles(
$city,
$genders,
$excludeProfileIds,
$neededCount,
);
if (empty($recommendedListingProfiles)) {
return [
'profiles' => $profiles,
'applied' => false,
'original_count' => $profiles->totalCount(),
'filled_profile_ids' => [],
'visible_profile_ids' => $excludeProfileIds,
];
}
$filledProfiles = array_merge($currentProfiles, $recommendedListingProfiles);
$originalCount = $profiles->totalCount();
$filledProfileIds = $this->extractProfileIds($recommendedListingProfiles);
return [
'profiles' => new FakeORMQueryPage(
$profiles->getCurrentOffset(),
$profiles->getCurrentPage(),
$profiles->getCurrentLimit(),
$originalCount + count($recommendedListingProfiles),
$filledProfiles,
),
'applied' => true,
'original_count' => $originalCount,
'filled_profile_ids' => $filledProfileIds,
'visible_profile_ids' => array_values(array_unique(array_merge($excludeProfileIds, $filledProfileIds))),
];
}
public function recommendedProfilesForPage(
City $city,
Page $items,
array $genders = [Genders::FEMALE],
): array
{
if (!$this->shouldFill($items)) {
return [
'profiles' => [],
'applied' => false,
'original_count' => $items->totalCount(),
];
}
$recommendedListingProfiles = $this->loadPopularCityProfiles(
$city,
$genders,
[],
$this->perPage - $items->count(),
);
return [
'profiles' => $recommendedListingProfiles,
'applied' => !empty($recommendedListingProfiles),
'original_count' => $items->totalCount(),
];
}
public function popularProfilesForBlock(
City $city,
Page $items,
array $genders = [Genders::FEMALE],
int $count = 5,
array $excludeProfileIds = [],
): array
{
$excludeProfileIds = array_values(array_unique(array_merge(
$excludeProfileIds,
$this->extractProfileIds(iterator_to_array($items->getIterator())),
)));
return $this->loadPopularCityProfiles(
$city,
$genders,
$excludeProfileIds,
$count,
);
}
private function shouldFill(Page $profiles): bool
{
if ($profiles->count() >= $this->perPage) {
return false;
}
if (0 === $profiles->totalCount()) {
return 1 === $profiles->getCurrentPage();
}
return $profiles->getCurrentOffset() < $profiles->totalCount();
}
private function extractProfileIds(array $profiles): array
{
$ids = [];
foreach ($profiles as $profile) {
if ($profile instanceof Profile) {
$ids[] = $profile->getId();
continue;
}
if (isset($profile->id) && is_numeric($profile->id)) {
$ids[] = (int)$profile->id;
continue;
}
if (is_array($profile) && isset($profile['id']) && is_numeric($profile['id'])) {
$ids[] = (int)$profile['id'];
}
}
return array_values(array_unique($ids));
}
private function resolveGender(array $genders): int
{
$gender = reset($genders);
return is_numeric($gender) ? (int)$gender : Genders::FEMALE;
}
private function loadPopularCityProfiles(
City $city,
array $genders,
array $excludeProfileIds,
int $neededCount,
): array {
if ($neededCount <= 0) {
return [];
}
$recommendedProfileIds = $this->coldStartProfileRecommendationsCache->getProfileIds(
$city,
$this->resolveGender($genders),
$this->recommendationCandidateLimit($neededCount),
$excludeProfileIds,
);
return array_slice(
$this->profileList->getActiveProfilesByIdsInOrderWithinCityAndSpec(
$city,
$recommendedProfileIds,
null,
null,
$genders,
),
0,
$neededCount,
);
}
private function recommendationCandidateLimit(int $neededCount): int
{
return min($this->perPage, max($neededCount, $neededCount * 4));
}
}