From 2c9a795756ee1547a797135236524a901844d7b0 Mon Sep 17 00:00:00 2001 From: Kacper Donat <kadet1090@gmail.com> Date: Tue, 21 Jan 2020 21:46:27 +0100 Subject: [PATCH] SerializeAs annotation --- config/services.yaml | 10 +-- .../Api/v1/DeparturesController.php | 8 +- src/Controller/Controller.php | 17 ++-- src/Model/Departure.php | 40 ++++++++++ .../Database/GenericScheduleRepository.php | 6 +- .../ZtmGdanskDepartureRepository.php | 2 + src/Serialization/SerializeAs.php | 15 ++++ src/Service/SerializerContextFactory.php | 79 +++++++++++++++++++ 8 files changed, 161 insertions(+), 16 deletions(-) create mode 100644 src/Serialization/SerializeAs.php create mode 100644 src/Service/SerializerContextFactory.php diff --git a/config/services.yaml b/config/services.yaml index eb381fc..4bcbebc 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -69,14 +69,10 @@ services: ProxyManager\Configuration: '@proxy.config' # serializer configuration - serializer.datetime_normalizer: - class: Symfony\Component\Serializer\Normalizer\DateTimeNormalizer - arguments: [!php/const DateTime::ATOM] - tags: [serializer.normalizer] + App\Service\SerializerContextFactory: + arguments: + $factory: '@jms_serializer.metadata_factory' - App\Service\Normalizer\: - resource: '../src/Service/Normalizer' - tags: [serializer.normalizer] # other servces App\Service\ProviderResolver: diff --git a/src/Controller/Api/v1/DeparturesController.php b/src/Controller/Api/v1/DeparturesController.php index 3141006..410a753 100644 --- a/src/Controller/Api/v1/DeparturesController.php +++ b/src/Controller/Api/v1/DeparturesController.php @@ -7,6 +7,7 @@ use App\Controller\Controller; use App\Model\Departure; use App\Provider\DepartureRepository; use App\Provider\StopRepository; +use App\Service\SerializerContextFactory; use Nelmio\ApiDocBundle\Annotation\Model; use Swagger\Annotations as SWG; use Symfony\Component\HttpFoundation\Request; @@ -68,6 +69,11 @@ class DeparturesController extends Controller ->flatMap(ref([ $departures, 'getForStop' ])) ->sortBy(property('departure')); - return $this->json($stops->values()->slice(0, (int)$request->query->get('limit', 8))); + return $this->json( + $stops->values()->slice(0, (int)$request->query->get('limit', 8)), + 200, + [], + $this->serializerContextFactory->create(Departure::class, ['Default']) + ); } } diff --git a/src/Controller/Controller.php b/src/Controller/Controller.php index a2d04b4..1187604 100644 --- a/src/Controller/Controller.php +++ b/src/Controller/Controller.php @@ -4,21 +4,24 @@ namespace App\Controller; +use App\Service\SerializerContextFactory; use JMS\Serializer\SerializerInterface; -use Symfony\Bundle\FrameworkBundle\Controller\Controller as SymfonyController; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; -abstract class Controller extends SymfonyController +abstract class Controller extends AbstractController { - private $serializer; + protected $serializer; + protected $serializerContextFactory; - public function __construct(SerializerInterface $serializer) + public function __construct(SerializerInterface $serializer, SerializerContextFactory $serializerContextFactory) { $this->serializer = $serializer; + $this->serializerContextFactory = $serializerContextFactory; } - protected function json($data, int $status = 200, array $headers = [], array $context = []): JsonResponse + protected function json($data, int $status = 200, array $headers = [], $context = null): JsonResponse { - return new JsonResponse($this->serializer->serialize($data, "json"), $status, $headers, true); + return new JsonResponse($this->serializer->serialize($data, "json", $context), $status, $headers, true); } -} \ No newline at end of file +} diff --git a/src/Model/Departure.php b/src/Model/Departure.php index a4d8c51..30bb185 100644 --- a/src/Model/Departure.php +++ b/src/Model/Departure.php @@ -2,6 +2,7 @@ namespace App\Model; +use App\Serialization\SerializeAs; use Carbon\Carbon; use JMS\Serializer\Annotation as Serializer; use Nelmio\ApiDocBundle\Annotation\Model; @@ -22,11 +23,30 @@ class Departure implements Fillable * Information about line. * @var Line * @Serializer\Type(Line::class) + * @SerializeAs({"Default": "Default"}) * @SWG\Property(ref=@Model(type=Line::class, groups={"Default"})) * */ private $line; + /** + * Information about line. + * @var Track|null + * @Serializer\Type(Track::class) + * @SerializeAs({"Default": "Identity"}) + * @SWG\Property(ref=@Model(type=Track::class, groups={"Identity"})) + */ + private $track; + + /** + * Information about line. + * @var Trip|null + * @Serializer\Type(Trip::class) + * @SerializeAs({"Default": "Identity"}) + * @SWG\Property(ref=@Model(type=Trip::class, groups={"Identity"})) + */ + private $trip; + /** * Information about stop. * @var Stop @@ -140,6 +160,26 @@ class Departure implements Fillable $this->stop = $stop; } + public function getTrack(): ?Track + { + return $this->track; + } + + public function setTrack(?Track $track): void + { + $this->track = $track; + } + + public function getTrip(): ?Trip + { + return $this->trip; + } + + public function setTrip(?Trip $trip): void + { + $this->trip = $trip; + } + /** * @Serializer\VirtualProperty() * @Serializer\Type("int") diff --git a/src/Provider/Database/GenericScheduleRepository.php b/src/Provider/Database/GenericScheduleRepository.php index d0f12b2..dabcccf 100644 --- a/src/Provider/Database/GenericScheduleRepository.php +++ b/src/Provider/Database/GenericScheduleRepository.php @@ -53,7 +53,9 @@ class GenericScheduleRepository extends DatabaseRepository implements ScheduleRe ]); return $schedule->map(function (TripStopEntity $entity) use ($stop) { - $line = $entity->getTrip()->getTrack()->getLine(); + $trip = $entity->getTrip(); + $track = $trip->getTrack(); + $line = $track->getLine(); /** @var StopEntity $last */ $last = $entity->getTrip()->getTrack()->getStopsInTrack()->last()->getStop(); @@ -63,6 +65,8 @@ class GenericScheduleRepository extends DatabaseRepository implements ScheduleRe 'stop' => $stop, 'display' => $last->getName(), 'line' => $this->convert($line), + 'track' => $this->convert($track), + 'trip' => $this->convert($trip), ]); }); } diff --git a/src/Provider/ZtmGdansk/ZtmGdanskDepartureRepository.php b/src/Provider/ZtmGdansk/ZtmGdanskDepartureRepository.php index a13c296..d1c3b99 100644 --- a/src/Provider/ZtmGdansk/ZtmGdanskDepartureRepository.php +++ b/src/Provider/ZtmGdansk/ZtmGdanskDepartureRepository.php @@ -129,6 +129,8 @@ class ZtmGdanskDepartureRepository implements DepartureRepository $departure = clone $real; $departure->setDisplay($scheduled->getDisplay()); + $departure->setTrack($scheduled->getTrack()); + $departure->setTrip($scheduled->getTrip()); return $departure; } diff --git a/src/Serialization/SerializeAs.php b/src/Serialization/SerializeAs.php new file mode 100644 index 0000000..43bef9b --- /dev/null +++ b/src/Serialization/SerializeAs.php @@ -0,0 +1,15 @@ +<?php + +namespace App\Serialization; + +use Doctrine\Common\Annotations\Annotation\Required; + +/** + * @Annotation + * @Target({"PROPERTY","METHOD","ANNOTATION"}) + */ +class SerializeAs +{ + /** @var array<string, string> @Required() */ + public $map; +} diff --git a/src/Service/SerializerContextFactory.php b/src/Service/SerializerContextFactory.php new file mode 100644 index 0000000..04bff01 --- /dev/null +++ b/src/Service/SerializerContextFactory.php @@ -0,0 +1,79 @@ +<?php + +namespace App\Service; + +use App\Serialization\SerializeAs; +use Doctrine\Common\Annotations\Reader; +use JMS\Serializer\Metadata\PropertyMetadata; +use JMS\Serializer\SerializationContext; +use Metadata\AdvancedMetadataFactoryInterface; +use Metadata\ClassHierarchyMetadata; +use function Kadet\Functional\Transforms\property; + +final class SerializerContextFactory +{ + private $factory; + private $reader; + + public function __construct(AdvancedMetadataFactoryInterface $factory, Reader $reader) + { + $this->factory = $factory; + $this->reader = $reader; + } + + public function create($subject, array $groups) + { + return SerializationContext::create()->setGroups($this->groups($subject, $groups)); + } + + private function groups($subject, array $groups) + { + $metadata = $this->factory->getMetadataForClass(is_object($subject) ? get_class($subject) : $subject); + $properties = $metadata instanceof ClassHierarchyMetadata + ? collect($metadata->classMetadata)->flatMap(property('propertyMetadata')) + : $metadata->propertyMetadata; + + $fields = []; + /** @var PropertyMetadata $property */ + foreach ($properties as $property) { + try { + $annotation = $this->getAnnotationForProperty($property); + if ($annotation && !empty($fieldGroups = $this->map($annotation, $groups))) { + $type = $property->type; + $class = $type['name'] !== 'array' ? $type['name'] : $type['params'][0]; + + $fields[$property->name] = $this->groups($class, $fieldGroups); + } + } catch (\ReflectionException $e) { } + } + + return array_merge($groups, $fields); + } + + private function getAnnotationForProperty(PropertyMetadata $metadata) + { + $reflection = new \ReflectionClass($metadata->class); + + try { + $property = $reflection->getProperty($metadata->name); + /** @var SerializeAs $annotation */ + return $this->reader->getPropertyAnnotation($property, SerializeAs::class); + } catch (\ReflectionException $exception) { + $method = $reflection->getMethod($metadata->getter); + return $this->reader->getMethodAnnotation($method, SerializeAs::class); + } + } + + private function map(SerializeAs $annotation, array $groups) + { + $result = []; + + foreach ($groups as $group) { + if (array_key_exists($group, $annotation->map)) { + $result[] = $annotation->map[$group]; + } + } + + return $result; + } +}