Skip to content

Commit

Permalink
Merge 3.0 into 3.1
Browse files Browse the repository at this point in the history
  • Loading branch information
soyuka committed Apr 26, 2023
2 parents 4e1b0c9 + db6ea91 commit f0ca1b4
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 2 deletions.
12 changes: 10 additions & 2 deletions src/Serializer/AbstractItemNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use ApiPlatform\Metadata\Util\ClassInfoTrait;
use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface;
use ApiPlatform\Util\ClassInfoTrait;
use ApiPlatform\Util\CloneTrait;
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
Expand All @@ -51,6 +53,7 @@
abstract class AbstractItemNormalizer extends AbstractObjectNormalizer
{
use ClassInfoTrait;
use CloneTrait;
use ContextTrait;
use InputOutputMetadataTrait;

Expand Down Expand Up @@ -214,14 +217,19 @@ public function denormalize(mixed $data, string $class, string $format = null, a
throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" resource must be "array" (nested document) or "string" (IRI), "%s" given.', $resourceClass, \gettype($data)), $data, [Type::BUILTIN_TYPE_ARRAY, Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null);
}

$previousObject = isset($objectToPopulate) ? clone $objectToPopulate : null;

$previousObject = $this->clone($objectToPopulate);
$object = parent::denormalize($data, $class, $format, $context);

if (!$this->resourceClassResolver->isResourceClass($class)) {
return $object;
}

// Bypass the post-denormalize attribute revert logic if the object could not be
// cloned since we cannot possibly revert any changes made to it.
if (null !== $objectToPopulate && null === $previousObject) {
return $object;
}

$options = $this->getFactoryOptions($context);
$propertyNames = iterator_to_array($this->propertyNameCollectionFactory->create($resourceClass, $options));

Expand Down
77 changes: 77 additions & 0 deletions tests/Fixtures/TestBundle/Entity/NonCloneableDummy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?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\Entity;

use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
* Dummy class that cannot be cloned.
*
* @author Colin O'Dell <colinodell@gmail.com>
*
* @ApiResource
*
* @ORM\Entity
*/
class NonCloneableDummy
{
/**
* @var int|null The id
*
* @ORM\Column(type="integer", nullable=true)
*
* @ORM\Id
*
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;

/**
* @var string The dummy name
*
* @ORM\Column
*
* @Assert\NotBlank
*
* @ApiProperty(iri="http://schema.org/name")
*/
private $name;

public function getId()
{
return $this->id;
}

public function setId($id): void
{
$this->id = $id;
}

public function setName(string $name): void
{
$this->name = $name;
}

public function getName(): string
{
return $this->name;
}

private function __clone()
{
}
}
49 changes: 49 additions & 0 deletions tests/Serializer/AbstractItemNormalizerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyTableInheritance;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyTableInheritanceChild;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyTableInheritanceRelated;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\NonCloneableDummy;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\SecuredDummy;
use Doctrine\Common\Collections\ArrayCollection;
Expand Down Expand Up @@ -1296,6 +1297,54 @@ public function testDenormalizeCollectionDecodedFromXmlWithOneChild(): void

$normalizer->denormalize($data, Dummy::class, 'xml');
}

public function testDenormalizePopulatingNonCloneableObject(): void
{
$dummy = new NonCloneableDummy();
$dummy->setName('foo');

$data = [
'name' => 'bar',
];

$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
$propertyNameCollectionFactoryProphecy->create(NonCloneableDummy::class, [])->willReturn(new PropertyNameCollection(['name']));

$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
$propertyMetadataFactoryProphecy->create(NonCloneableDummy::class, 'name', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));

$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
$propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
$resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
$resourceClassResolverProphecy->getResourceClass(null, NonCloneableDummy::class)->willReturn(NonCloneableDummy::class);
$resourceClassResolverProphecy->getResourceClass($dummy, NonCloneableDummy::class)->willReturn(NonCloneableDummy::class);
$resourceClassResolverProphecy->isResourceClass(NonCloneableDummy::class)->willReturn(true);

$serializerProphecy = $this->prophesize(SerializerInterface::class);
$serializerProphecy->willImplement(NormalizerInterface::class);

$normalizer = $this->getMockForAbstractClass(AbstractItemNormalizer::class, [
$propertyNameCollectionFactoryProphecy->reveal(),
$propertyMetadataFactoryProphecy->reveal(),
$iriConverterProphecy->reveal(),
$resourceClassResolverProphecy->reveal(),
$propertyAccessorProphecy->reveal(),
null,
null,
[],
null,
null,
]);
$normalizer->setSerializer($serializerProphecy->reveal());
$normalizer->setSerializer($serializerProphecy->reveal());

$context = [AbstractItemNormalizer::OBJECT_TO_POPULATE => $dummy];
$actual = $normalizer->denormalize($data, NonCloneableDummy::class, null, $context);

$this->assertInstanceOf(NonCloneableDummy::class, $actual);
$this->assertSame($dummy, $actual);
$propertyAccessorProphecy->setValue($actual, 'name', 'bar')->shouldHaveBeenCalled();
}
}

class ObjectWithBasicProperties
Expand Down

0 comments on commit f0ca1b4

Please sign in to comment.