SerializeAs annotation

This commit is contained in:
Kacper Donat 2020-01-21 21:46:27 +01:00
parent 02776c4c90
commit 2c9a795756
8 changed files with 161 additions and 16 deletions

View File

@ -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:

View File

@ -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'])
);
}
}

View File

@ -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);
}
}
}

View File

@ -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")

View File

@ -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),
]);
});
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}