<?php
namespace App\Service;
use App\Dto\ProfileListSpecification;
use App\Entity\Location\County;
use App\Entity\Location\District;
use App\Entity\Location\Station;
use App\Entity\Profile\Genders;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Contracts\Cache\ItemInterface;
class GeoCategoryLinkageService
{
private const CACHE_ITEM_PREFIX = 'geo_category_linkage_count';
private const CACHE_TTL = 300;
private const CATEGORIES = [
'eromassage' => [
'text' => 'Эромассаж',
'feature' => 'extra_category_eromassage',
],
'verified' => [
'text' => 'Проверенные',
'feature' => 'extra_category_verified',
],
'cheap' => [
'text' => 'Дешевые',
'feature' => 'extra_category_cheap',
],
'mature' => [
'text' => 'Зрелые',
'feature' => 'extra_category_mature',
],
'uzbek' => [
'text' => 'Узбечки',
'feature' => 'extra_category_uzbek',
],
];
private const GEO_ENTITY_CLASSES = [
'county' => County::class,
'district' => District::class,
'station' => Station::class,
];
private ?Request $request;
public function __construct(
private Features $features,
private ProfileList $profileList,
private ProfileListSpecificationService $profileListSpecificationService,
private RouterInterface $router,
private CacheItemPoolInterface $projectDataCache,
RequestStack $requestStack
) {
$this->request = $requestStack->getCurrentRequest();
}
public function links(): ?array
{
if (null === $this->request) {
return null;
}
$context = $this->resolveContext($this->normalizeRoute($this->request->attributes->get('_route')));
if (null === $context) {
return null;
}
$links = [];
if (null !== $context['current_category']) {
$profilesCount = $this->countProfiles($context['geo_type'], $context['geo']);
if ($profilesCount > 0) {
$links[] = $this->createLink(
$context['main_route'],
$context['route_parameters'],
'Проститутки',
$profilesCount
);
}
}
foreach (self::CATEGORIES as $category => $configuration) {
if ($category === $context['current_category'] || !$this->featureEnabled($configuration['feature'])) {
continue;
}
$profilesCount = $this->countProfiles(
$context['geo_type'],
$context['geo'],
$category
);
if ($profilesCount < 1) {
continue;
}
$links[] = $this->createLink(
$context['main_route'] . '_' . $category,
$context['route_parameters'],
$configuration['text'],
$profilesCount,
null === $context['current_category']
);
}
return $links ?: null;
}
private function resolveContext(string $route): ?array
{
foreach (self::GEO_ENTITY_CLASSES as $geoType => $entityClass) {
$mainRoute = 'profile_list.list_by_' . $geoType;
$currentCategory = null;
if ($route !== $mainRoute) {
foreach (array_keys(self::CATEGORIES) as $category) {
if ($route === $mainRoute . '_' . $category) {
$currentCategory = $category;
break;
}
}
if (null === $currentCategory) {
continue;
}
}
$geo = $this->request->attributes->get($geoType);
if (!$geo instanceof $entityClass) {
return null;
}
return [
'geo_type' => $geoType,
'geo' => $geo,
'main_route' => $mainRoute,
'current_category' => $currentCategory,
'route_parameters' => [
'city' => $geo->getCity()->getUriIdentity(),
$geoType => $geo->getUriIdentity(),
],
];
}
return null;
}
private function countProfiles(
string $geoType,
County|District|Station $geo,
string $category = ''
): int {
$cacheKey = sprintf(
'%s.%d.%s.%d.%s.%d.%d',
self::CACHE_ITEM_PREFIX,
$geo->getCity()->getId(),
$geoType,
$geo->getId(),
$category ?: 'base',
(int) $this->features->hard_moderation(),
(int) $this->features->free_profiles()
);
return $this->projectDataCache->get($cacheKey, function (ItemInterface $item) use ($geoType, $geo, $category): int {
$item->expiresAfter(self::CACHE_TTL);
$specification = $this->createSpecification($geoType, $geo, $category);
return $this->profileList->countActiveWithinCityWithSpec(
$geo->getCity(),
$specification->spec(),
$specification->additionalSpecs(),
$specification->genders() ?? [Genders::FEMALE]
);
});
}
private function createSpecification(
string $geoType,
County|District|Station $geo,
string $category
): ProfileListSpecification {
switch ($geoType) {
case 'county':
if (!$geo instanceof County) {
break;
}
switch ($category) {
case '':
return $this->profileListSpecificationService->listByCounty($geo);
case 'eromassage':
return $this->profileListSpecificationService->listByCountyEromassage($geo);
case 'verified':
return $this->profileListSpecificationService->listByCountyVerified($geo);
case 'cheap':
return $this->profileListSpecificationService->listByCountyCheap($geo);
case 'mature':
return $this->profileListSpecificationService->listByCountyMature($geo);
case 'uzbek':
return $this->profileListSpecificationService->listByCountyUzbek($geo);
}
break;
case 'district':
if (!$geo instanceof District) {
break;
}
switch ($category) {
case '':
return $this->profileListSpecificationService->listByDistrict($geo);
case 'eromassage':
return $this->profileListSpecificationService->listByDistrictEromassage($geo);
case 'verified':
return $this->profileListSpecificationService->listByDistrictVerified($geo);
case 'cheap':
return $this->profileListSpecificationService->listByDistrictCheap($geo);
case 'mature':
return $this->profileListSpecificationService->listByDistrictMature($geo);
case 'uzbek':
return $this->profileListSpecificationService->listByDistrictUzbek($geo);
}
break;
case 'station':
if (!$geo instanceof Station) {
break;
}
switch ($category) {
case '':
return $this->profileListSpecificationService->listByStation($geo);
case 'eromassage':
return $this->profileListSpecificationService->listByStationEromassage($geo);
case 'verified':
return $this->profileListSpecificationService->listByStationVerified($geo);
case 'cheap':
return $this->profileListSpecificationService->listByStationCheap($geo);
case 'mature':
return $this->profileListSpecificationService->listByStationMature($geo);
case 'uzbek':
return $this->profileListSpecificationService->listByStationUzbek($geo);
}
break;
}
throw new \LogicException(sprintf('Unsupported geo category "%s" for "%s".', $category, $geoType));
}
private function createLink(
string $route,
array $routeParameters,
string $text,
int $profilesCount,
bool $additive = false
): array {
return [
'href' => $this->router->generate($route, $routeParameters),
'text' => $text,
'profiles_count' => $profilesCount,
'additive' => $additive,
];
}
private function featureEnabled(string $feature): bool
{
switch ($feature) {
case 'extra_category_eromassage':
return $this->features->extra_category_eromassage();
case 'extra_category_verified':
return $this->features->extra_category_verified();
case 'extra_category_cheap':
return $this->features->extra_category_cheap();
case 'extra_category_mature':
return $this->features->extra_category_mature();
case 'extra_category_uzbek':
return $this->features->extra_category_uzbek();
}
throw new \LogicException(sprintf('Unsupported geo category feature "%s".', $feature));
}
private function normalizeRoute(?string $route): string
{
$route = preg_replace('/\.(?:ru|en)$/', '', (string) $route);
return preg_replace('/\._pagination$/', '', $route);
}
}