Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 31 additions & 38 deletions src/Serializer/AbstractItemNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -266,21 +266,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a
}

if (\is_string($data)) {
try {
return $this->iriConverter->getResourceFromIri($data, $context + ['fetch_data' => true]);
} catch (ItemNotFoundException $e) {
if (!isset($context['not_normalizable_value_exceptions'])) {
throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e);
}

throw NotNormalizableValueException::createForUnexpectedDataType($e->getMessage(), $data, [$resourceClass], $context['deserialization_path'] ?? null, true, $e->getCode(), $e);
} catch (InvalidArgumentException $e) {
if (!isset($context['not_normalizable_value_exceptions'])) {
throw new UnexpectedValueException(\sprintf('Invalid IRI "%s".', $data), $e->getCode(), $e);
}

throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('Invalid IRI "%s".', $data), $data, [$resourceClass], $context['deserialization_path'] ?? null, true, $e->getCode(), $e);
}
return $this->getResourceFromIri($data, $context, $resourceClass);
}

if (!\is_array($data)) {
Expand Down Expand Up @@ -699,33 +685,17 @@ protected function denormalizeObjectCollection(string $attribute, ApiProperty $p
*/
protected function denormalizeRelation(string $attributeName, ApiProperty $propertyMetadata, string $className, mixed $value, ?string $format, array $context): ?object
{
if (\is_string($value)) {
try {
return $this->iriConverter->getResourceFromIri($value, $context + ['fetch_data' => true]);
} catch (ItemNotFoundException $e) {
if (false === ($context['denormalize_throw_on_relation_not_found'] ?? true)) {
return null;
}

if (!isset($context['not_normalizable_value_exceptions'])) {
throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e);
}

throw NotNormalizableValueException::createForUnexpectedDataType($e->getMessage(), $value, [$className], $context['deserialization_path'] ?? null, true, $e->getCode(), $e);
} catch (InvalidArgumentException $e) {
if (!isset($context['not_normalizable_value_exceptions'])) {
throw new UnexpectedValueException(\sprintf('Invalid IRI "%s".', $value), $e->getCode(), $e);
if (\is_string($value) || $propertyMetadata->isWritableLink()) {
if (!$this->serializer instanceof DenormalizerInterface) {
if (\is_string($value)) {
return $this->getResourceFromIri($value, $context, $className);
}

throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('Invalid IRI "%s".', $value), $value, [$className], $context['deserialization_path'] ?? null, true, $e->getCode(), $e);
throw new LogicException(\sprintf('The injected serializer must be an instance of "%s".', DenormalizerInterface::class));
}
}

if ($propertyMetadata->isWritableLink()) {
$context['api_allow_update'] = true;

if (!$this->serializer instanceof DenormalizerInterface) {
throw new LogicException(\sprintf('The injected serializer must be an instance of "%s".', DenormalizerInterface::class));
if ($propertyMetadata->isWritableLink()) {
$context['api_allow_update'] = true;
}

$item = $this->serializer->denormalize($value, $className, $format, $context);
Expand All @@ -743,6 +713,29 @@ protected function denormalizeRelation(string $attributeName, ApiProperty $prope
throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('Nested documents for attribute "%s" are not allowed. Use IRIs instead.', $attributeName), $value, ['array', 'string'], $context['deserialization_path'] ?? null, true);
}

private function getResourceFromIri(string $data, array $context, string $resourceClass): ?object
{
try {
return $this->iriConverter->getResourceFromIri($data, $context + ['fetch_data' => true]);
} catch (ItemNotFoundException $e) {
if (false === ($context['denormalize_throw_on_relation_not_found'] ?? true)) {
return null;
}

if (!isset($context['not_normalizable_value_exceptions'])) {
throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e);
}

throw NotNormalizableValueException::createForUnexpectedDataType($e->getMessage(), $data, [$resourceClass], $context['deserialization_path'] ?? null, true, $e->getCode(), $e);
} catch (InvalidArgumentException $e) {
if (!isset($context['not_normalizable_value_exceptions'])) {
throw new UnexpectedValueException(\sprintf('Invalid IRI "%s".', $data), $e->getCode(), $e);
}

throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('Invalid IRI "%s".', $data), $data, [$resourceClass], $context['deserialization_path'] ?? null, true, $e->getCode(), $e);
}
}

/**
* Gets the options for the property name collection / property metadata factories.
*/
Expand Down
1 change: 0 additions & 1 deletion src/Serializer/Tests/AbstractItemNormalizerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1253,7 +1253,6 @@ public function testDeserializationPathForNotDenormalizableRelations(): void
$propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);

$serializerProphecy = $this->prophesize(SerializerInterface::class);
$serializerProphecy->willImplement(DenormalizerInterface::class);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the serializer is a denormalizer, it will call it instead of the iriConverter and the mock are not properly configured for this in this test.


$normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
$normalizer->setSerializer($serializerProphecy->reveal());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public function __construct(
public ?int $id = null,
public ?string $nameConverted = null,
public ?GenderTypeEnum $gender = null,
public ?self $childRelation = null,
) {
}

Expand All @@ -55,6 +56,6 @@ public static function provide(Operation $operation, array $uriVariables = [], a
*/
public static function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): self
{
return new self(id: 1, nameConverted: $data->nameConverted);
return new self(id: 1, nameConverted: $data->nameConverted, childRelation: $data->childRelation);
}
}
4 changes: 4 additions & 0 deletions tests/Fixtures/TestBundle/Dto/InputDtoWithNameConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@

namespace ApiPlatform\Tests\Fixtures\TestBundle\Dto;

use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\DummyDtoNameConverted;

class InputDtoWithNameConverter
{
public ?string $nameConverted = null;

public ?DummyDtoNameConverted $childRelation = null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Tests\Fixtures\TestBundle\Serializer\Denormalizer;

use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\DummyDtoNameConverted;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;

class InputDtoDenormalizer implements DenormalizerInterface
{
/**
* {@inheritdoc}
*/
public function denormalize($data, $class, $format = null, array $context = []): mixed
{
return new DummyDtoNameConverted(42);
}

/**
* {@inheritdoc}
*/
public function supportsDenormalization($data, $type, $format = null, array $context = []): bool
{
return DummyDtoNameConverted::class === $type && 'child_relation' === $data;
}

public function getSupportedTypes($format): array
{
return [
DummyDtoNameConverted::class => true,
];
}
}
5 changes: 5 additions & 0 deletions tests/Fixtures/app/config/config_common.yml
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@ services:
tags:
- name: 'serializer.normalizer'

app.serializer.denormalizer.input_dto:
class: 'ApiPlatform\Tests\Fixtures\TestBundle\Serializer\Denormalizer\InputDtoDenormalizer'
tags:
- name: 'serializer.normalizer'

app.name_converter:
class: 'ApiPlatform\Tests\Fixtures\TestBundle\Serializer\NameConverter\CustomConverter'

Expand Down
33 changes: 33 additions & 0 deletions tests/Functional/InputOutputNameConverterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,39 @@ public function testInputDtoNameConverterIsApplied(): void
$this->assertResponseStatusCodeSame(201);
$data = $response->toArray();
$this->assertSame('converted', $data['name_converted']);
$this->assertNull($data['childRelation']);
}

public function testInputDtoDenormalizerIsApplied(): void
{
$response = self::createClient()->request('POST', '/dummy_dto_name_converted', [
'headers' => ['Content-Type' => 'application/ld+json'],
'json' => [
'name_converted' => 'converted',
'childRelation' => 'child_relation',
],
]);

$this->assertResponseStatusCodeSame(201);
$data = $response->toArray();
$this->assertSame('converted', $data['name_converted']);
$this->assertSame('/dummy_dto_name_converted/42', $data['childRelation']);
}

public function testInputDtoIriConverterIsApplied(): void
{
$response = self::createClient()->request('POST', '/dummy_dto_name_converted', [
'headers' => ['Content-Type' => 'application/ld+json'],
'json' => [
'name_converted' => 'converted',
'childRelation' => '/dummy_dto_name_converted/child_relation',
],
]);

$this->assertResponseStatusCodeSame(201);
$data = $response->toArray();
$this->assertSame('converted', $data['name_converted']);
$this->assertSame('/dummy_dto_name_converted/1', $data['childRelation']);
}

public function testOutputDtoNameConverterIsApplied(): void
Expand Down
Loading