Last active
December 13, 2019 20:44
-
-
Save benjaminrau/afdf2c53ac8f77346bbc6b66b2e10c2e to your computer and use it in GitHub Desktop.
Allow to have different serialization groups for root resource and embedded relations
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Emma\AppBundle\Serializer; | |
use ApiPlatform\Core\Util\ClassInfoTrait; | |
use Symfony\Component\EventDispatcher\EventDispatcherInterface; | |
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; | |
use Symfony\Component\Serializer\Normalizer\NormalizerInterface; | |
/** | |
* Copy from \ApiPlatform\Core\JsonLd\Serializer\ItemJsonNormalizer | |
*/ | |
class ItemJsonNormalizer implements NormalizerInterface, DenormalizerInterface | |
{ | |
use ClassInfoTrait; | |
private const ENTRY_POINT_OBJECT_GROUP_KEY = '_entry_point_object'; | |
private const NOT_ENTRY_POINT_OBJECT_GROUP_KEY = 'not' . self::ENTRY_POINT_OBJECT_GROUP_KEY; | |
/** | |
* @var NormalizerInterface | |
*/ | |
private $normalizer; | |
/** | |
* @var EventDispatcherInterface | |
*/ | |
private $eventDispatcher; | |
/** | |
* CustomItemNormalizer constructor. | |
* | |
* @param $normalizer NormalizerInterface | |
* @param $eventDispatcher EventDispatcherInterface | |
*/ | |
public function __construct(NormalizerInterface $normalizer, EventDispatcherInterface $eventDispatcher) | |
{ | |
if (!$normalizer instanceof DenormalizerInterface) { | |
throw new \InvalidArgumentException('The normalizer must implement the DenormalizerInterface'); | |
} | |
$this->normalizer = $normalizer; | |
$this->eventDispatcher = $eventDispatcher; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function denormalize($data, $class, $format = null, array $context = array()) | |
{ | |
return $this->normalizer->denormalize($data, $class, $format, $context); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function supportsDenormalization($data, $type, $format = null) | |
{ | |
return $this->normalizer->supportsDenormalization($data, $type, $format); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function normalize($object, $format = null, array $context = array()) | |
{ | |
$this->appendEntryPointObjectGroups($context); | |
$context['cache_key'] = md5(json_encode($context['groups'])); | |
if (!empty($context['parent_property']) && isset($context['property_path'])) { | |
$context['property_path'][] = explode('::', $context['parent_property'])[1]; | |
} elseif (!empty($context['parent_property'])) { | |
$context['property_path'] = [explode('::', $context['parent_property'])[1]]; | |
} | |
$data = $this->normalizer->normalize($object, $format, $context); | |
return $data; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function supportsNormalization($data, $format = null) | |
{ | |
return $this->normalizer->supportsNormalization($data, $format); | |
} | |
/** | |
* @param $context | |
*/ | |
private function appendEntryPointObjectGroups(&$context) | |
{ | |
if ($context['operation_type'] === 'subresource') { | |
return; | |
} | |
if (!isset($context['parent_property'])) { | |
foreach ($context['groups'] as $group) { | |
$context['groups'][] = $group . self::ENTRY_POINT_OBJECT_GROUP_KEY; | |
} | |
} else { | |
foreach ($context['groups'] as $key => $group) { | |
if (strpos($group, self::ENTRY_POINT_OBJECT_GROUP_KEY) !== false) { | |
unset($context['groups'][$key]); | |
} | |
if (strpos($group, self::NOT_ENTRY_POINT_OBJECT_GROUP_KEY) === false) { | |
$context['groups'][] = $group . self::NOT_ENTRY_POINT_OBJECT_GROUP_KEY; | |
} | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
namespace Emma\AppBundle\Serializer; | |
use ApiPlatform\Core\Api\IriConverterInterface; | |
use ApiPlatform\Core\Api\ResourceClassResolverInterface; | |
use ApiPlatform\Core\JsonLd\ContextBuilderInterface; | |
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; | |
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; | |
use ApiPlatform\Core\Metadata\Property\PropertyMetadata; | |
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; | |
use ApiPlatform\Core\Serializer\AbstractItemNormalizer; | |
use ApiPlatform\Core\JsonLd\Serializer\JsonLdContextTrait; | |
use ApiPlatform\Core\Serializer\ContextTrait; | |
use ApiPlatform\Core\Util\ClassInfoTrait; | |
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; | |
use Symfony\Component\PropertyAccess\PropertyAccessorInterface; | |
use Symfony\Component\Serializer\Exception\LogicException; | |
use Symfony\Component\Serializer\Exception\NotNormalizableValueException; | |
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; | |
use Symfony\Component\Serializer\NameConverter\NameConverterInterface; | |
use Symfony\Component\Serializer\Normalizer\NormalizerInterface; | |
/** | |
* Copy from \ApiPlatform\Core\JsonLd\Serializer\ItemNormalizer | |
*/ | |
final class ItemNormalizer extends AbstractItemNormalizer | |
{ | |
use ClassInfoTrait; | |
use ContextTrait; | |
use JsonLdContextTrait; | |
public const FORMAT = 'jsonld'; | |
private $contextBuilder; | |
public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, ContextBuilderInterface $contextBuilder, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, array $defaultContext = [], iterable $dataTransformers = []) | |
{ | |
parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, $classMetadataFactory, null, false, $defaultContext, $dataTransformers, $resourceMetadataFactory); | |
$this->contextBuilder = $contextBuilder; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function supportsNormalization($data, $format = null): bool | |
{ | |
return self::FORMAT === $format && parent::supportsNormalization($data, $format); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function normalize($object, $format = null, array $context = []) | |
{ | |
if (null !== $this->getOutputClass($this->getObjectClass($object), $context)) { | |
return parent::normalize($object, $format, $context); | |
} | |
$resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class'] ?? null); | |
$context = $this->initContext($resourceClass, $context); | |
$iri = $this->iriConverter->getIriFromItem($object); | |
$context['iri'] = $iri; | |
$context['api_normalize'] = true; | |
$metadata = $this->addJsonLdContext($this->contextBuilder, $resourceClass, $context); | |
$context['resource_class'] = $resourceClass; | |
$data = parent::normalize($object, $format, $context); | |
if (!\is_array($data)) { | |
return $data; | |
} | |
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); | |
$metadata['@id'] = $iri; | |
$metadata['@type'] = $resourceMetadata->getIri() ?: $resourceMetadata->getShortName(); | |
return $metadata + $data; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function supportsDenormalization($data, $type, $format = null): bool | |
{ | |
return self::FORMAT === $format && parent::supportsDenormalization($data, $type, $format); | |
} | |
/** | |
* {@inheritdoc} | |
* | |
* @throws NotNormalizableValueException | |
*/ | |
public function denormalize($data, $class, $format = null, array $context = []) | |
{ | |
// Avoid issues with proxies if we populated the object | |
if (isset($data['@id']) && !isset($context[self::OBJECT_TO_POPULATE])) { | |
if (true !== ($context['api_allow_update'] ?? true)) { | |
throw new NotNormalizableValueException('Update is not allowed for this operation.'); | |
} | |
$context[self::OBJECT_TO_POPULATE] = $this->iriConverter->getItemFromIri($data['@id'], $context + ['fetch_data' => true]); | |
} | |
return parent::denormalize($data, $class, $format, $context); | |
} | |
/** | |
* {@inheritdoc} | |
* | |
* @throws NoSuchPropertyException | |
*/ | |
protected function getAttributeValue($object, $attribute, $format = null, array $context = []) | |
{ | |
$context['api_attribute'] = $attribute; | |
$propertyMetadata = $this->propertyMetadataFactory->create($context['resource_class'], $attribute, $this->getFactoryOptions($context)); | |
try { | |
$attributeValue = $this->propertyAccessor->getValue($object, $attribute); | |
} catch (NoSuchPropertyException $e) { | |
if (!$propertyMetadata->hasChildInherited()) { | |
throw $e; | |
} | |
$attributeValue = null; | |
} | |
$type = $propertyMetadata->getType(); | |
if ( | |
is_iterable($attributeValue) && | |
$type && | |
$type->isCollection() && | |
($collectionValueType = $type->getCollectionValueType()) && | |
($className = $collectionValueType->getClassName()) && | |
$this->resourceClassResolver->isResourceClass($className) | |
) { | |
$resourceClass = $this->resourceClassResolver->getResourceClass($attributeValue, $className); | |
$childContext = $this->createChildContext($context, $attribute, $format); | |
$childContext['resource_class'] = $resourceClass; | |
unset($childContext['iri']); | |
// LOCC START | |
$context['parent_property'] = $this->getObjectClass($object) . '::'.$attribute; | |
// LOCC END | |
return $this->normalizeCollectionOfRelations($propertyMetadata, $attributeValue, $resourceClass, $format, $childContext); | |
} | |
if ( | |
$type && | |
($className = $type->getClassName()) && | |
$this->resourceClassResolver->isResourceClass($className) | |
) { | |
$resourceClass = $this->resourceClassResolver->getResourceClass($attributeValue, $className); | |
$childContext = $this->createChildContext($context, $attribute, $format); | |
$childContext['resource_class'] = $resourceClass; | |
unset($childContext['iri']); | |
// LOCC START | |
$context['parent_property'] = $this->getObjectClass($object) . '::'.$attribute; | |
// LOCC END | |
return $this->normalizeRelation($propertyMetadata, $attributeValue, $resourceClass, $format, $childContext); | |
} | |
if (!$this->serializer instanceof NormalizerInterface) { | |
throw new LogicException(sprintf('The injected serializer must be an instance of "%s".', NormalizerInterface::class)); | |
} | |
unset($context['resource_class']); | |
return $this->serializer->normalize($attributeValue, $format, $context); | |
} | |
/** | |
* @inheritDoc | |
*/ | |
protected function normalizeCollectionOfRelations(PropertyMetadata $propertyMetadata, $attributeValue, string $resourceClass, ?string $format, array $context): array | |
{ | |
return array_values( | |
parent::normalizeCollectionOfRelations($propertyMetadata, $attributeValue, $resourceClass, $format, $context) | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
api.serializer.normalizer.item: | |
public: false | |
class: Emma\AppBundle\Serializer\ItemNormalizer | |
arguments: [ '@api.serializer.normalizer.item.json', '@event_dispatcher'] | |
tags: [ { name: serializer.normalizer, priority: 9 } ] | |
api.serializer.normalizer.item.json: | |
public: false | |
class: Emma\AppBundle\Serializer\ItemJsonNormalizer | |
arguments: | |
- '@api_platform.metadata.resource.metadata_factory' | |
- '@api_platform.metadata.property.name_collection_factory' | |
- '@api_platform.metadata.property.metadata_factory' | |
- '@api_platform.iri_converter' | |
- '@api_platform.resource_class_resolver' | |
- '@api_platform.jsonld.context_builder' | |
- '@api_platform.property_accessor' | |
- ~ | |
- '@serializer.mapping.class_metadata_factory' | |
tags: [ { name: serializer.normalizer, priority: 8 } ] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment