<?php
namespace App\Controller\Api;
use App\Constants;
use App\Entity\Itinerary;
use App\Entity\ItineraryVersion;
use App\Entity\ItineraryVersionTranslation;
use App\Entity\Player;
use App\Entity\PlayerVersion;
use App\Entity\PlayerVersionTranslation;
use App\Entity\Sound;
use App\Repository\ItineraryRepository;
use App\Service\GohApiService;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\NoResultException;
use Doctrine\Persistence\Mapping\MappingException;
use FOS\RestBundle\Controller\AbstractFOSRestController;
use FOS\RestBundle\Controller\Annotations\QueryParam;
use FOS\RestBundle\Request\ParamFetcher;
use JMS\Serializer\SerializationContext;
use JMS\Serializer\SerializerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Validator\Constraints;
/**
* Class ItineraryController
* @package App\Controller\Api
* @Route("/api/v4/itineraries")
*/
class ItineraryController extends AbstractFOSRestController
{
/**
* @var SerializerInterface
*/
private $serializer;
public function __construct(SerializerInterface $serializer)
{
$this->serializer = $serializer;
}
/**
* @Route("/test",name="hello_test")
*/
public function test(): Response
{
return JsonResponse::create("hello");
}
/**
* @Route("", name="api_v4_itineraries_index")
*
* @QueryParam(name="locale", strict=true, requirements=@Constraints\Locale(canonicalize=true))
* @QueryParam(name="identifier", strict=true, requirements=@Constraints\Type("string"))
* @QueryParam(name="latitude", strict=true, requirements=@App\Validator\Constraints\Latitude())
* @QueryParam(name="longitude", strict=true, requirements=@App\Validator\Constraints\Longitude())
*
* @param ParamFetcher $paramFetcher
* @param ItineraryRepository $itineraryRepository
* @return Response
* @throws MappingException
*/
public function index(ParamFetcher $paramFetcher, ItineraryRepository $itineraryRepository, GohApiService $apiService): Response
{
$isDev = $this->getParameter('includeDevApi') === 'true';
$locale = $paramFetcher->get('locale');
$identifier = $paramFetcher->get('identifier');
$latitude = $paramFetcher->get('latitude');
$longitude = $paramFetcher->get('longitude');
$serializationContext = SerializationContext::create()
->setGroups(Constants::SERIALIZE_GROUP_API_LIST)
->setAttribute('latitude', (float)$latitude)
->setAttribute('longitude', (float)$longitude)
->setAttribute('locale', $locale)
->setSerializeNull(true);
$liveItineraries = $itineraryRepository->getItinerariesForStateLocaleIdentifier(Constants::STATE_LIVE, $locale);
$liveItineraries = $this->serializeMany($liveItineraries, $serializationContext);
$liveItineraries = $this->filterDuplicates($liveItineraries, $locale);
$betaItineraries = $itineraryRepository->getItinerariesForStateLocaleIdentifier(Constants::STATE_BETA, $locale, $identifier);
$betaItineraries = $this->serializeMany($betaItineraries, $serializationContext);
$betaItineraries = $this->filterDuplicates($betaItineraries, $locale);
/** @var Itinerary[] $itineraries */
$itineraries = array_merge($liveItineraries, $betaItineraries);
$itineraries = $this->serializeOrdered($itineraries);
/*if($isDev) {
$itineraries = array_merge(
$itineraries,
$apiService->getItinerariesForStateLocaleIdentifier(Constants::STATE_LIVE, $locale, $latitude, $longitude, $identifier),
$apiService->getItinerariesForStateLocaleIdentifier(Constants::STATE_BETA, $locale, $latitude, $longitude, $identifier)
);
}*/
return Response::create(json_encode($itineraries));
}
/**
* @Route("/{slug}/{state}/{locale}", name="api_v4_itineraries_detail")
*
* @QueryParam(name="identifier", strict=true, requirements=@Constraints\Type("string"))
* @QueryParam(name="latitude", strict=true, requirements=@App\Validator\Constraints\Latitude())
* @QueryParam(name="longitude", strict=true, requirements=@App\Validator\Constraints\Longitude())
*
* @param string $slug
* @param string $state
* @param string $locale
* @param ParamFetcher $paramFetcher
* @param ItineraryRepository $itineraryRepository
* @param GohApiService $apiService
* @return Response
* @throws NonUniqueResultException
*/
public function detail(string $slug, string $state, string $locale, ParamFetcher $paramFetcher, ItineraryRepository $itineraryRepository, GohApiService $apiService): Response
{
$identifier = $paramFetcher->get('identifier');
$latitude = $paramFetcher->get('latitude');
$longitude = $paramFetcher->get('longitude');
$serializationContext = SerializationContext::create()
->setGroups(Constants::SERIALIZE_GROUP_API_DETAIL)
->setAttribute('latitude', (float)$latitude)
->setAttribute('longitude', (float)$longitude)
->setAttribute('locale', $locale)
->setSerializeNull(true);
try {
$itinerary = $itineraryRepository->getItineraryForStateLocaleSlug($state, $locale, $slug);
$serializedItineraries = $this->serializeMany([$itinerary], $serializationContext);
return Response::create(json_encode(current($serializedItineraries)));
}
catch (NoResultException $exception) {
$serializedItineraries = []; // $apiService->getItineraryForStateLocaleSlug($state, $locale, $slug, $latitude, $longitude, $identifier);
return Response::create(json_encode($serializedItineraries));
}
}
protected function serializeOrdered(array $itineraries): array
{
$itinerariesInside = array_filter($itineraries, function($itinerary) {
return $itinerary['distance'] === 'ICI';
});
usort($itinerariesInside, function($itinerary1, $itinerary2) {
return strnatcmp($itinerary1['name'], $itinerary2['name']);
});
$itinerariesNotInside = array_filter($itineraries, function($itinerary) {
return $itinerary['distance'] !== 'ICI';
});
usort($itinerariesNotInside, function ($itinerary1, $itinerary2) {
return $itinerary1['distance_raw'] - $itinerary2['distance_raw'];
});
return array_merge($itinerariesInside, $itinerariesNotInside);
}
/**
* @param array $itineraries
* @param SerializationContext $context
* @return array
*/
protected function serializeMany(array $itineraries, SerializationContext $context): array
{
$locale = $context->getAttribute('locale');
$withPlayers = in_array(Constants::SERIALIZE_GROUP_API_DETAIL, $context->getAttribute('groups'));
$serializedItineraries = [];
/** @var Itinerary $itinerary */
foreach ($itineraries as $itinerary) {
$serializedPlayers = [];
/** @var Player $player */
foreach ($itinerary->getPlayers() as $player) {
/** @var PlayerVersion $playerVersion */
foreach ($player->getVersions() as $playerVersion) {
/** @var PlayerVersionTranslation $playerVersionTranslation */
foreach ($playerVersion->getTranslations() as $playerVersionTranslation) {
$sortByNameCriteria = Criteria::create()->orderBy(['name' => Criteria::ASC]);
$playerVersionTranslationSounds = $playerVersionTranslation->getSounds()->matching($sortByNameCriteria);
$serializedSounds = [];
/** @var Sound $sound */
foreach ($playerVersionTranslationSounds as $sound) {
$serializedSounds[] = $this->serializeSound($sound, $context);
}
$serializedPlayers[] = array_merge(
$this->serializePlayer($player, $context),
$this->serializePlayerVersion($playerVersion, $context),
$this->serializePlayerVersionTranslation($playerVersionTranslation, $context),
[
"sounds" => $serializedSounds,
]
);
}
}
}
if($withPlayers) {
$serializedPlayers = $this->filterDuplicates($serializedPlayers, $locale);
}
/** @var ItineraryVersion $itineraryVersion */
foreach ($itinerary->getVersions() as $itineraryVersion) {
foreach ($itineraryVersion->getTranslations() as $itineraryVersionTranslation) {
$serializedItineraries[] = array_merge(
$this->serializeItinerary($itinerary, $context),
$this->serializeItineraryVersion($itineraryVersion, $context),
$this->serializeItineraryVersionTranslation($itineraryVersionTranslation, $context),
[
"players" => $withPlayers ? $serializedPlayers : [],
]
);
}
}
}
return $serializedItineraries;
}
protected function serializeItinerary(Itinerary $itinerary, SerializationContext $context): array
{
return $this->serialize($itinerary, $context);
}
protected function serializeItineraryVersion(ItineraryVersion $itineraryVersion, SerializationContext $context): array
{
return $this->serialize($itineraryVersion, $context);
}
protected function serializeItineraryVersionTranslation(ItineraryVersionTranslation $itineraryVersionTranslation, SerializationContext $context): array
{
return $this->serialize($itineraryVersionTranslation, $context);
}
protected function serializePlayer(Player $player, SerializationContext $context): array
{
return $this->serialize($player, $context);
}
protected function serializePlayerVersion(PlayerVersion $playerVersion, SerializationContext $context): array
{
return $this->serialize($playerVersion, $context);
}
protected function serializePlayerVersionTranslation(PlayerVersionTranslation $playerVersionTranslation, SerializationContext $context): array
{
return $this->serialize($playerVersionTranslation, $context);
}
protected function serializeSound(Sound $sound, SerializationContext $context): array
{
return $this->serialize($sound, $context);
}
protected function serialize($entity, SerializationContext $context): array
{
return json_decode($this->serializer->serialize($entity, 'json', clone $context), true);
}
protected function filterDuplicates(array $serializedEntities, string $expectedLocale): array
{
$filtered = [];
foreach($serializedEntities as $serializedEntityIndex => $serializedEntity) {
$exactEntityIndex = $this->getIndexFromExactEntityExistsInArray($serializedEntities, $serializedEntity['slug'], $serializedEntity['state'], $expectedLocale);
if($serializedEntityIndex !== $exactEntityIndex && $exactEntityIndex > 0) {
continue;
}
$filtered[] = $serializedEntity;
}
return $filtered;
}
protected function getIndexFromExactEntityExistsInArray(array $array, string $slug, string $state, string $locale): int {
foreach($array as $index => $item) {
if($item['slug'] === $slug && $item['state'] === $state && $item['locale'] === $locale) {
return $index;
}
}
return -1;
}
}