[API] Remove ItemNormalizer class

This commit is contained in:
Wojdylak 2023-10-31 08:33:23 +01:00
parent 2e499193df
commit 86955aa06f
4 changed files with 2 additions and 337 deletions

View file

@ -30,7 +30,7 @@
"ext-intl": "*",
"ext-json": "*",
"ext-simplexml": "*",
"api-platform/core": "^3.1",
"api-platform/core": "^3.1.22",
"babdev/pagerfanta-bundle": "^3.0",
"behat/transliterator": "^1.3",
"doctrine/collections": "^1.6",
@ -129,7 +129,7 @@
"symfony/string": "^6.3.2",
"symfony/templating": "^6.3.0",
"symfony/translation": "^6.3.3",
"symfony/translation-contracts": "^2.5.2",
"symfony/translation-contracts": "^3.3",
"symfony/twig-bundle": "^6.3.0",
"symfony/validator": "^6.3.4",
"symfony/webpack-encore-bundle": "^1.17.1",

View file

@ -1,217 +0,0 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Sylius\Bundle\ApiBundle\ApiPlatform\JsonLd\Serializer;
use ApiPlatform\Api\IriConverterInterface;
use ApiPlatform\Api\ResourceClassResolverInterface;
use ApiPlatform\Api\UrlGeneratorInterface;
use ApiPlatform\JsonLd\AnonymousContextBuilderInterface;
use ApiPlatform\JsonLd\ContextBuilderInterface;
use ApiPlatform\JsonLd\Serializer\JsonLdContextTrait;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\HttpOperation;
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use ApiPlatform\Metadata\Util\ClassInfoTrait;
use ApiPlatform\Serializer\AbstractItemNormalizer;
use ApiPlatform\Serializer\ContextTrait;
use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\Serializer\Exception\LogicException;
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
/**
* Overridden class from the API Platform in order to pass the context to the IRI conversion
*
* Converts between objects and array including JSON-LD and Hydra metadata.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
final class ItemNormalizer extends AbstractItemNormalizer
{
use ClassInfoTrait;
use ContextTrait;
use JsonLdContextTrait;
public const FORMAT = 'jsonld';
public function __construct(
ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory,
PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory,
PropertyMetadataFactoryInterface $propertyMetadataFactory,
IriConverterInterface $iriConverter,
ResourceClassResolverInterface $resourceClassResolver,
private readonly ContextBuilderInterface $contextBuilder,
PropertyAccessorInterface $propertyAccessor = null,
NameConverterInterface $nameConverter = null,
ClassMetadataFactoryInterface $classMetadataFactory = null,
array $defaultContext = [],
ResourceAccessCheckerInterface $resourceAccessChecker = null
) {
parent::__construct(
$propertyNameCollectionFactory,
$propertyMetadataFactory,
$iriConverter,
$resourceClassResolver,
$propertyAccessor,
$nameConverter,
$classMetadataFactory,
$defaultContext,
$resourceMetadataCollectionFactory,
$resourceAccessChecker
);
}
/**
* {@inheritdoc}
*/
public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool
{
return self::FORMAT === $format && parent::supportsNormalization($data, $format, $context);
}
public function getSupportedTypes($format): array
{
return self::FORMAT === $format ? parent::getSupportedTypes($format) : [];
}
/**
* {@inheritdoc}
*
* @throws LogicException
*/
public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null
{
$resourceClass = $this->getObjectClass($object);
if ($this->getOutputClass($context)) {
return parent::normalize($object, $format, $context);
}
// TODO: we should not remove the resource_class in the normalizeRawCollection as we would find out anyway that it's not the same as the requested one
$previousResourceClass = $context['resource_class'] ?? null;
$metadata = [];
if ($isResourceClass = $this->resourceClassResolver->isResourceClass($resourceClass)) {
$resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class'] ?? null);
$context = $this->initContext($resourceClass, $context);
$metadata = $this->addJsonLdContext($this->contextBuilder, $resourceClass, $context);
} elseif ($this->contextBuilder instanceof AnonymousContextBuilderInterface) {
if ($context['api_collection_sub_level'] ?? false) {
unset($context['api_collection_sub_level']);
$context['output']['genid'] = true;
$context['output']['iri'] = null;
}
// We should improve what's behind the context creation, its probably more complicated then it should
$metadata = $this->createJsonLdContext($this->contextBuilder, $object, $context);
}
// maybe not needed anymore
if (isset($context['operation']) && $previousResourceClass !== $resourceClass) {
unset($context['operation'], $context['operation_name']);
}
if ($iri = $this->iriConverter->getIriFromResource($object, UrlGeneratorInterface::ABS_PATH, $context['operation'] ?? null, $context)) {
$context['iri'] = $iri;
$metadata['@id'] = $iri;
}
$context['api_normalize'] = true;
$data = parent::normalize($object, $format, $context);
if (!\is_array($data)) {
return $data;
}
if (!isset($metadata['@type']) && $isResourceClass) {
$operation = $context['operation'] ?? $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation();
$types = $operation instanceof HttpOperation ? $operation->getTypes() : null;
if (null === $types) {
$types = [$operation->getShortName()];
}
$metadata['@type'] = 1 === \count($types) ? $types[0] : $types;
}
return $metadata + $data;
}
/**
* {@inheritdoc}
*/
public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool
{
return self::FORMAT === $format && parent::supportsDenormalization($data, $type, $format, $context);
}
/**
* {@inheritdoc}
*
* @throws NotNormalizableValueException
*/
public function denormalize(mixed $data, string $class, string $format = null, array $context = []): mixed
{
// 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->getResourceFromIri($data['@id'], $context + ['fetch_data' => true]);
}
return parent::denormalize($data, $class, $format, $context);
}
protected function normalizeRelation(
ApiProperty $propertyMetadata,
?object $relatedObject,
string $resourceClass,
?string $format,
array $context
): \ArrayObject|array|string|null {
if (null === $relatedObject || !empty($context['attributes']) || $propertyMetadata->isReadableLink()) {
if (!$this->serializer instanceof NormalizerInterface) {
throw new LogicException(sprintf('The injected serializer must be an instance of "%s".', NormalizerInterface::class));
}
$relatedContext = $this->createOperationContext($context, $resourceClass);
$normalizedRelatedObject = $this->serializer->normalize($relatedObject, $format, $relatedContext);
if (!\is_string($normalizedRelatedObject) && !\is_array($normalizedRelatedObject) && !$normalizedRelatedObject instanceof \ArrayObject && null !== $normalizedRelatedObject) {
throw new UnexpectedValueException('Expected normalized relation to be an IRI, array, \ArrayObject or null');
}
return $normalizedRelatedObject;
}
// The line below has been modified to pass the context to the IriConverter
$iri = $this->iriConverter->getIriFromResource($relatedObject, UrlGeneratorInterface::ABS_PATH, null, $context);
if (isset($context['resources'])) {
$context['resources'][$iri] = $iri;
}
$push = $propertyMetadata->getPush() ?? false;
if (isset($context['resources_to_push']) && $push) {
$context['resources_to_push'][$iri] = $iri;
}
return $iri;
}
}

View file

@ -183,20 +183,5 @@
<argument type="tagged_iterator" tag="sylius_api.input_data_processor" />
<tag name="api_platform.state_processor" priority="-900" />
</service>
<service id="api_platform.jsonld.normalizer.item" class="Sylius\Bundle\ApiBundle\ApiPlatform\JsonLd\Serializer\ItemNormalizer" public="false">
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory" />
<argument type="service" id="api_platform.metadata.property.name_collection_factory" />
<argument type="service" id="api_platform.metadata.property.metadata_factory" />
<argument type="service" id="api_platform.iri_converter" />
<argument type="service" id="api_platform.resource_class_resolver" />
<argument type="service" id="api_platform.jsonld.context_builder" />
<argument type="service" id="api_platform.property_accessor" />
<argument type="service" id="api_platform.name_converter" on-invalid="ignore" />
<argument type="service" id="serializer.mapping.class_metadata_factory" on-invalid="ignore" />
<argument type="collection" />
<argument type="service" id="api_platform.security.resource_access_checker" on-invalid="ignore" />
<tag name="serializer.normalizer" priority="-890" />
</service>
</services>
</container>

View file

@ -1,103 +0,0 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Sylius\Bundle\ApiBundle\Tests\ApiPlatform\JsonLd\Serializer;
use ApiPlatform\Api\IriConverterInterface;
use ApiPlatform\Api\ResourceClassResolverInterface;
use ApiPlatform\Api\UrlGeneratorInterface;
use ApiPlatform\JsonLd\ContextBuilderInterface;
use ApiPlatform\JsonLd\Serializer\ItemNormalizer;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\Operations;
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
use ApiPlatform\Metadata\Property\PropertyNameCollection;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Sylius\Component\Addressing\Model\Country;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\SerializerInterface;
/**
* Copied and adjusted from API Platform
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
final class ItemNormalizerTest extends TestCase
{
use ProphecyTrait;
public function testNormalize(): void
{
$country = new Country();
$country->setCode('CODE');
$resourceMetadataCollectionFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
$resourceMetadataCollectionFactoryProphecy->create(Country::class)->willReturn(new ResourceMetadataCollection('Country', [
(new ApiResource())
->withShortName('Country')
->withOperations(new Operations(['get' => (new Get())->withShortName('Country')])),
]));
$propertyNameCollection = new PropertyNameCollection(['code']);
$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
$propertyNameCollectionFactoryProphecy->create(Country::class, Argument::any())->willReturn($propertyNameCollection);
$propertyMetadata = (new ApiProperty())->withReadable(true);
$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
$propertyMetadataFactoryProphecy->create(Country::class, 'code', Argument::any())->willReturn($propertyMetadata);
$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
$iriConverterProphecy->getIriFromResource($country, UrlGeneratorInterface::ABS_PATH, null, Argument::any())->willReturn('/countries/CODE');
$resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
$resourceClassResolverProphecy->getResourceClass($country, null)->willReturn(Country::class);
$resourceClassResolverProphecy->getResourceClass(null, Country::class)->willReturn(Country::class);
$resourceClassResolverProphecy->getResourceClass($country, Country::class)->willReturn(Country::class);
$resourceClassResolverProphecy->getResourceClass(null, Country::class)->willReturn(Country::class);
$resourceClassResolverProphecy->isResourceClass(Country::class)->willReturn(true);
$serializerProphecy = $this->prophesize(SerializerInterface::class);
$serializerProphecy->willImplement(NormalizerInterface::class);
$serializerProphecy->normalize('CODE', null, Argument::type('array'))->willReturn('CODE');
$contextBuilderProphecy = $this->prophesize(ContextBuilderInterface::class);
$contextBuilderProphecy->getResourceContextUri(Country::class)->willReturn('/contexts/Country');
$normalizer = new ItemNormalizer(
$resourceMetadataCollectionFactoryProphecy->reveal(),
$propertyNameCollectionFactoryProphecy->reveal(),
$propertyMetadataFactoryProphecy->reveal(),
$iriConverterProphecy->reveal(),
$resourceClassResolverProphecy->reveal(),
$contextBuilderProphecy->reveal(),
null,
null,
null,
[]
);
$normalizer->setSerializer($serializerProphecy->reveal());
$expected = [
'@context' => '/contexts/Country',
'@id' => '/countries/CODE',
'@type' => 'Country',
'code' => 'CODE',
];
$this->assertEquals($expected, $normalizer->normalize($country));
}
}