<?php
/**
* Created by simpson <simpsonwork@gmail.com>
* Date: 2019-04-15
* Time: 19:51
*/
namespace App\Controller;
use App\Entity\Account\Customer;
use App\Entity\Location\City;
use App\Entity\Profile\Profile;
use App\Entity\Saloon\Comment\CommentByCustomer;
use App\Entity\Saloon\Saloon;
use App\Entity\User;
use App\Form\CommentForm;
use App\Repository\ProfileRepository;
use App\Repository\ServiceRepository;
use App\Service\Features;
use App\Specification\ElasticSearch\ProfileIsProvidingOneOfServices;
use Carbon\CarbonImmutable;
use Flagception\Bundle\FlagceptionBundle\Annotations\Feature;
use Nelmio\ApiDocBundle\Attribute\Model;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\GoneHttpException;
use Symfony\Component\Routing\Annotation\Route;
use OpenApi\Attributes as OA;
class SaloonPreviewController extends AbstractController
{
use ResponseTrait;
private const PROFILES_PER_PAGE = 6;
/**
* @Feature("has_saloons")
*/
#[ParamConverter('city', converter: 'city_converter')]
#[Entity('saloon', expr: 'repository.ofUriIdentityWithinCity(saloon, city)')]
#[Route("/api/saloons/city/{city}/preview/{saloon}", name: "api.saloons.preview", methods: "GET", format: "json")]
#[OA\Tag(name: "Preview")]
#[OA\Response(
response: 200,
description: 'Полная информация салона',
content: new OA\JsonContent(ref: new Model(type: Saloon::class, groups: ['preview', 'comments']))
)]
public function page(Request $request, City $city, Saloon $saloon, ServiceRepository $serviceRepository, ProfileRepository $profileRepository, Features $features, ParameterBagInterface $parameterBag): Response
{
// DMCA hard delete — всегда 404
if ($saloon->isHardDeleted()) {
throw $this->createNotFoundException();
}
$showHttp200FromDate = $this->createDateFromParameter(
$parameterBag->get('app.profile.page.deleted_saloon_http_200_starting_from')
);
if ($saloon->isDeleted() && $saloon->getDeletedAt() < $showHttp200FromDate) {
throw new GoneHttpException();
}
if ($this->isApiRequest($request)) {
return $this->json($saloon, context: ['groups' => ['preview', 'comments']]);
}
//$entityManager->getFilters()->enable('not_deleted_saloon_filter');
$services = $serviceRepository->allIndexedByGroup();
$providedServices = array_map(
static fn($providedService) => $providedService->getService(),
$saloon->getProvidedServices()->toArray(),
);
$profilesTotalCount = $profileRepository->countPublicProfilesBySaloon($saloon);
$profilesRotationSeed = (int)(new \DateTimeImmutable())->format('z');
$parameters = [
'saloon' => $saloon,
'services' => $services,
'rating' => $this->countAverageRating($saloon),
'recommendationSpec' => !empty($providedServices) ? new ProfileIsProvidingOneOfServices($providedServices) : null,
'saloon_profiles' => $profileRepository->findPublicProfilesBySaloonRotatedByPlacementStatus(
$saloon,
min(self::PROFILES_PER_PAGE, $profilesTotalCount),
0,
$profilesRotationSeed
),
'saloon_profiles_total_count' => $profilesTotalCount,
'saloon_profiles_per_page' => self::PROFILES_PER_PAGE,
'saloon_profiles_rotation_offset' => $profilesRotationSeed,
];
return $this->render('SaloonPreview/page.html.twig', $parameters);
}
private function createDateFromParameter(string $value): CarbonImmutable
{
$value = trim(html_entity_decode($value, ENT_QUOTES), '"\' ');
$value = preg_replace('/([+-]\d{2}:\d{2})([+-]\d{2}:\d{2})$/', '$1', $value);
return CarbonImmutable::createFromTimeString($value);
}
#[ParamConverter('city', converter: 'city_converter')]
#[Entity('saloon', expr: 'repository.ofUriIdentityWithinCity(saloon, city)')]
public function profiles(City $city, Saloon $saloon, ProfileRepository $profileRepository, Request $request, int $page): Response
{
if ($saloon->isHardDeleted()) {
throw $this->createNotFoundException();
}
$page = max(1, $page);
$profilesTotalCount = $profileRepository->countPublicProfilesBySaloon($saloon);
$profilesRotationSeed = (int)$request->query->get('rotation', 0);
$loadedProfilesCount = ($page - 1) * self::PROFILES_PER_PAGE;
$limit = min(self::PROFILES_PER_PAGE, max(0, $profilesTotalCount - $loadedProfilesCount));
$profiles = $profileRepository->findPublicProfilesBySaloonRotatedByPlacementStatus(
$saloon,
$limit,
$loadedProfilesCount,
$profilesRotationSeed
);
return $this->render('SaloonPreview/_profiles_list.html.twig', [
'profiles' => $profiles,
]);
}
protected function countAverageRating(Saloon $saloon): float
{
$scores = 0;
$totalScore = 0;
$saloon->getComments()->map(function (CommentByCustomer $comment) use (&$scores, &$totalScore): void {
$scores++;
$totalScore += $comment->getMark();
});
$rating = $scores > 0 ? $totalScore / $scores : 0;
$whole = floor($rating);
$fraction = $rating - $whole;
$decimalRating = 0;
if ($fraction < 0.25)
$decimalRating = 0;
elseif ($fraction >= 0.25 && $fraction < 0.75)
$decimalRating = 0.5;
elseif ($fraction >= 0.75)
$decimalRating = 1;
$rating = $whole + $decimalRating;
return $rating;
}
}