diff --git a/src/Doctrine/Odm/Metadata/Property/DoctrineMongoDbOdmPropertyMetadataFactory.php b/src/Doctrine/Odm/Metadata/Property/DoctrineMongoDbOdmPropertyMetadataFactory.php index 23150df996e..9e07b401b69 100644 --- a/src/Doctrine/Odm/Metadata/Property/DoctrineMongoDbOdmPropertyMetadataFactory.php +++ b/src/Doctrine/Odm/Metadata/Property/DoctrineMongoDbOdmPropertyMetadataFactory.php @@ -57,6 +57,10 @@ public function create(string $resourceClass, string $property, array $options = break; } + if ($options['api_allow_update'] ?? false) { + break; + } + $propertyMetadata = $propertyMetadata->withWritable(false); break; diff --git a/src/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php b/src/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php index c450e85c1d8..7706c3f31f2 100644 --- a/src/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php +++ b/src/Doctrine/Orm/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php @@ -56,6 +56,10 @@ public function create(string $resourceClass, string $property, array $options = break; } + if ($options['api_allow_update'] ?? false) { + break; + } + if ($doctrineClassMetadata instanceof ClassMetadata) { $writable = $doctrineClassMetadata->isIdentifierNatural(); } else { diff --git a/src/GraphQl/Tests/Serializer/ItemNormalizerTest.php b/src/GraphQl/Tests/Serializer/ItemNormalizerTest.php index 559d13b1e58..de8a4b2e8b8 100644 --- a/src/GraphQl/Tests/Serializer/ItemNormalizerTest.php +++ b/src/GraphQl/Tests/Serializer/ItemNormalizerTest.php @@ -78,11 +78,11 @@ public function testNormalize(): void $propertyNameCollection = new PropertyNameCollection(['name']); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn($propertyNameCollection); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn($propertyNameCollection); $propertyMetadata = (new ApiProperty())->withReadable(true); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn($propertyMetadata); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn($propertyMetadata); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); $iriConverterProphecy->getIriFromResource($dummy, UrlGeneratorInterface::ABS_URL, Argument::any(), Argument::type('array'))->willReturn('/dummies/1'); @@ -131,13 +131,13 @@ public function testNormalizeWithUnsafeCacheProperty(): void $propertyNameCollection = new PropertyNameCollection(['title', 'ownerOnlyProperty']); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, [])->willReturn($propertyNameCollection); + $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, Argument::type('array'))->willReturn($propertyNameCollection); $unsecuredPropertyMetadata = (new ApiProperty())->withReadable(true); $securedPropertyMetadata = (new ApiProperty())->withReadable(true)->withSecurity('object == null or object.getOwner() == user'); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', [])->willReturn($unsecuredPropertyMetadata); - $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', [])->willReturn($securedPropertyMetadata); + $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn($unsecuredPropertyMetadata); + $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', Argument::type('array'))->willReturn($securedPropertyMetadata); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); $iriConverterProphecy->getIriFromResource($securedDummyWithOwnerOnlyPropertyAllowed, UrlGeneratorInterface::ABS_URL, Argument::any(), Argument::type('array'))->willReturn('/dummies/1'); @@ -216,11 +216,11 @@ public function testNormalizeNoResolverData(): void $propertyNameCollection = new PropertyNameCollection(['name']); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn($propertyNameCollection); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn($propertyNameCollection); $propertyMetadata = (new ApiProperty())->withWritable(true)->withReadable(true); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn($propertyMetadata); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn($propertyMetadata); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); $iriConverterProphecy->getIriFromResource($dummy, UrlGeneratorInterface::ABS_URL, Argument::any(), Argument::type('array'))->willReturn('/dummies/1'); @@ -260,11 +260,11 @@ public function testDenormalize(): void $propertyNameCollection = new PropertyNameCollection(['name']); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn($propertyNameCollection)->shouldBeCalled(); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn($propertyNameCollection)->shouldBeCalled(); $propertyMetadata = (new ApiProperty())->withWritable(true)->withReadable(true); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn($propertyMetadata)->shouldBeCalled(); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn($propertyMetadata)->shouldBeCalled(); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); diff --git a/src/Hal/.gitignore b/src/Hal/.gitignore new file mode 100644 index 00000000000..eb0a8e7b262 --- /dev/null +++ b/src/Hal/.gitignore @@ -0,0 +1,3 @@ +/composer.lock +/vendor +/.phpunit.result.cache diff --git a/src/Hal/Tests/Fixtures/ApiResource/Issue5452/ActivableInterface.php b/src/Hal/Tests/Fixtures/ApiResource/Issue5452/ActivableInterface.php new file mode 100644 index 00000000000..7154123e0bc --- /dev/null +++ b/src/Hal/Tests/Fixtures/ApiResource/Issue5452/ActivableInterface.php @@ -0,0 +1,18 @@ + + * + * 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\Hal\Tests\Fixtures\ApiResource\Issue5452; + +interface ActivableInterface +{ +} diff --git a/src/Hal/Tests/Fixtures/ApiResource/Issue5452/Author.php b/src/Hal/Tests/Fixtures/ApiResource/Issue5452/Author.php new file mode 100644 index 00000000000..c4bd356a63f --- /dev/null +++ b/src/Hal/Tests/Fixtures/ApiResource/Issue5452/Author.php @@ -0,0 +1,34 @@ + + * + * 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\Hal\Tests\Fixtures\ApiResource\Issue5452; + +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Tests\Fixtures\TestBundle\State\Issue5452\AuthorItemProvider; + +#[ApiResource( + operations: [ + new Get(uriTemplate: '/issue-5452/authors/{id}{._format}', provider: AuthorItemProvider::class), + ] +)] +class Author implements ActivableInterface, TimestampableInterface +{ + public function __construct( + #[ApiProperty(identifier: true)] + public readonly string|int $id, + public readonly string $name, + ) { + } +} diff --git a/src/Hal/Tests/Fixtures/ApiResource/Issue5452/Book.php b/src/Hal/Tests/Fixtures/ApiResource/Issue5452/Book.php new file mode 100644 index 00000000000..6650738a741 --- /dev/null +++ b/src/Hal/Tests/Fixtures/ApiResource/Issue5452/Book.php @@ -0,0 +1,38 @@ + + * + * 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\Hal\Tests\Fixtures\ApiResource\Issue5452; + +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Tests\Fixtures\TestBundle\State\Issue5452\BookCollectionProvider; + +#[GetCollection(uriTemplate: '/issue-5452/books{._format}', provider: BookCollectionProvider::class)] +#[Post(uriTemplate: '/issue-5452/books{._format}')] +class Book +{ + // union types + public string|int|null $number = null; + + // simple types + public ?string $isbn = null; + + // intersect types without specific typehint (throw an error: AbstractItemNormalizer line 872) + public ActivableInterface&TimestampableInterface $library; + + /** + * @var Author + */ + // intersect types with PHPDoc + public ActivableInterface&TimestampableInterface $author; +} diff --git a/src/Hal/Tests/Fixtures/ApiResource/Issue5452/Library.php b/src/Hal/Tests/Fixtures/ApiResource/Issue5452/Library.php new file mode 100644 index 00000000000..e22f101bd89 --- /dev/null +++ b/src/Hal/Tests/Fixtures/ApiResource/Issue5452/Library.php @@ -0,0 +1,34 @@ + + * + * 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\Hal\Tests\Fixtures\ApiResource\Issue5452; + +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Tests\Fixtures\TestBundle\State\Issue5452\LibraryItemProvider; + +#[ApiResource( + operations: [ + new Get(uriTemplate: '/issue-5452/libraries/{id}{._format}', provider: LibraryItemProvider::class), + ] +)] +class Library implements ActivableInterface, TimestampableInterface +{ + public function __construct( + #[ApiProperty(identifier: true)] + public readonly string|int $id, + public readonly string $name, + ) { + } +} diff --git a/src/Hal/Tests/Fixtures/ApiResource/Issue5452/TimestampableInterface.php b/src/Hal/Tests/Fixtures/ApiResource/Issue5452/TimestampableInterface.php new file mode 100644 index 00000000000..55f2364f0d4 --- /dev/null +++ b/src/Hal/Tests/Fixtures/ApiResource/Issue5452/TimestampableInterface.php @@ -0,0 +1,18 @@ + + * + * 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\Hal\Tests\Fixtures\ApiResource\Issue5452; + +interface TimestampableInterface +{ +} diff --git a/src/Hal/Tests/Fixtures/Dummy.php b/src/Hal/Tests/Fixtures/Dummy.php new file mode 100644 index 00000000000..06e6b84cf32 --- /dev/null +++ b/src/Hal/Tests/Fixtures/Dummy.php @@ -0,0 +1,41 @@ + + * + * 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\Hal\Tests\Fixtures; + +class Dummy +{ + public int $id; + private $relatedDummy; + private $name; + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): void + { + $this->name = $name; + } + + public function getRelatedDummy(): RelatedDummy + { + return $this->relatedDummy; + } + + public function setRelatedDummy(RelatedDummy $relatedDummy): void + { + $this->relatedDummy = $relatedDummy; + } +} diff --git a/src/Hal/Tests/Fixtures/MaxDepthDummy.php b/src/Hal/Tests/Fixtures/MaxDepthDummy.php new file mode 100644 index 00000000000..40eafc527fb --- /dev/null +++ b/src/Hal/Tests/Fixtures/MaxDepthDummy.php @@ -0,0 +1,37 @@ + + * + * 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\Hal\Tests\Fixtures; + +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Annotation\MaxDepth; + +/** + * @author Brian Fox + */ +#[ApiResource(normalizationContext: ['groups' => ['default'], 'enable_max_depth' => true], denormalizationContext: ['groups' => ['default'], 'enable_max_depth' => true], graphQlOperations: [])] +class MaxDepthDummy +{ + #[Groups(['default'])] + public $id; + + #[Groups(['default'])] + public $name; + + #[ApiProperty(fetchEager: false)] + #[Groups(['default'])] + #[MaxDepth(1)] + public $child; +} diff --git a/src/Hal/Tests/Fixtures/RelatedDummy.php b/src/Hal/Tests/Fixtures/RelatedDummy.php new file mode 100644 index 00000000000..f5baf362add --- /dev/null +++ b/src/Hal/Tests/Fixtures/RelatedDummy.php @@ -0,0 +1,19 @@ + + * + * 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\Hal\Tests\Fixtures; + +class RelatedDummy +{ + public int $id; +} diff --git a/tests/Hal/JsonSchema/SchemaFactoryTest.php b/src/Hal/Tests/JsonSchema/SchemaFactoryTest.php similarity index 79% rename from tests/Hal/JsonSchema/SchemaFactoryTest.php rename to src/Hal/Tests/JsonSchema/SchemaFactoryTest.php index 8868ab3d0cf..0dfe67dcce1 100644 --- a/tests/Hal/JsonSchema/SchemaFactoryTest.php +++ b/src/Hal/Tests/JsonSchema/SchemaFactoryTest.php @@ -11,10 +11,10 @@ declare(strict_types=1); -namespace ApiPlatform\Tests\Hal\JsonSchema; +namespace ApiPlatform\Hal\Tests\JsonSchema; use ApiPlatform\Hal\JsonSchema\SchemaFactory; -use ApiPlatform\Hydra\JsonSchema\SchemaFactory as HydraSchemaFactory; +use ApiPlatform\Hal\Tests\Fixtures\Dummy; use ApiPlatform\JsonSchema\DefinitionNameFactory; use ApiPlatform\JsonSchema\Schema; use ApiPlatform\JsonSchema\SchemaFactory as BaseSchemaFactory; @@ -27,41 +27,42 @@ use ApiPlatform\Metadata\Property\PropertyNameCollection; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; class SchemaFactoryTest extends TestCase { - use ProphecyTrait; - private SchemaFactory $schemaFactory; protected function setUp(): void { - $resourceMetadataFactory = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); - $resourceMetadataFactory->create(Dummy::class)->willReturn( - new ResourceMetadataCollection(Dummy::class, [ - (new ApiResource())->withOperations(new Operations([ - 'get' => (new Get())->withName('get'), - ])), - ])); - $propertyNameCollectionFactory = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactory->create(Dummy::class, ['enable_getter_setter_extraction' => true, 'schema_type' => Schema::TYPE_OUTPUT])->willReturn(new PropertyNameCollection()); - $propertyMetadataFactory = $this->prophesize(PropertyMetadataFactoryInterface::class); + $resourceMetadataFactory = $this->createMock(ResourceMetadataCollectionFactoryInterface::class); + $resourceMetadataFactory->method('create') + ->with(Dummy::class) + ->willReturn( + new ResourceMetadataCollection(Dummy::class, [ + (new ApiResource())->withOperations(new Operations([ + 'get' => (new Get())->withName('get'), + ])), + ]) + ); + + $propertyNameCollectionFactory = $this->createMock(PropertyNameCollectionFactoryInterface::class); + $propertyNameCollectionFactory->method('create') + ->with(Dummy::class, ['enable_getter_setter_extraction' => true, 'schema_type' => Schema::TYPE_OUTPUT]) + ->willReturn(new PropertyNameCollection()); + + $propertyMetadataFactory = $this->createMock(PropertyMetadataFactoryInterface::class); $definitionNameFactory = new DefinitionNameFactory(['jsonapi' => true, 'jsonhal' => true, 'jsonld' => true]); $baseSchemaFactory = new BaseSchemaFactory( - resourceMetadataFactory: $resourceMetadataFactory->reveal(), - propertyNameCollectionFactory: $propertyNameCollectionFactory->reveal(), - propertyMetadataFactory: $propertyMetadataFactory->reveal(), + resourceMetadataFactory: $resourceMetadataFactory, + propertyNameCollectionFactory: $propertyNameCollectionFactory, + propertyMetadataFactory: $propertyMetadataFactory, definitionNameFactory: $definitionNameFactory, ); - $hydraSchemaFactory = new HydraSchemaFactory($baseSchemaFactory); - - $this->schemaFactory = new SchemaFactory($hydraSchemaFactory); + $this->schemaFactory = new SchemaFactory($baseSchemaFactory); } public function testBuildSchema(): void diff --git a/tests/Hal/Serializer/CollectionNormalizerTest.php b/src/Hal/Tests/Serializer/CollectionNormalizerTest.php similarity index 71% rename from tests/Hal/Serializer/CollectionNormalizerTest.php rename to src/Hal/Tests/Serializer/CollectionNormalizerTest.php index c08caaffb6d..504f96674ce 100644 --- a/tests/Hal/Serializer/CollectionNormalizerTest.php +++ b/src/Hal/Tests/Serializer/CollectionNormalizerTest.php @@ -23,7 +23,6 @@ use ApiPlatform\State\Pagination\PaginatorInterface; use ApiPlatform\State\Pagination\PartialPaginatorInterface; use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; /** @@ -31,14 +30,12 @@ */ class CollectionNormalizerTest extends TestCase { - use ProphecyTrait; - #[\PHPUnit\Framework\Attributes\Group('legacy')] public function testSupportsNormalize(): void { - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); - $normalizer = new CollectionNormalizer($resourceClassResolverProphecy->reveal(), 'page', $resourceMetadataFactoryProphecy->reveal()); + $resourceClassResolverMock = $this->createMock(ResourceClassResolverInterface::class); + $resourceMetadataFactoryMock = $this->createMock(ResourceMetadataCollectionFactoryInterface::class); + $normalizer = new CollectionNormalizer($resourceClassResolverMock, 'page', $resourceMetadataFactoryMock); $this->assertTrue($normalizer->supportsNormalization([], CollectionNormalizer::FORMAT, ['resource_class' => 'Foo'])); $this->assertTrue($normalizer->supportsNormalization([], CollectionNormalizer::FORMAT, ['resource_class' => 'Foo', 'api_sub_level' => true])); @@ -115,46 +112,45 @@ public function testNormalizePartialPaginator(): void private function normalizePaginator(bool $partial = false) { - $paginatorProphecy = $this->prophesize(PaginatorInterface::class); if ($partial) { - $paginatorProphecy = $this->prophesize(PartialPaginatorInterface::class); + $paginator = $this->createMock(PartialPaginatorInterface::class); + } else { + $paginator = $this->createMock(PaginatorInterface::class); } - $paginatorProphecy->getCurrentPage()->willReturn(3); - $paginatorProphecy->getItemsPerPage()->willReturn(12); - $paginatorProphecy->rewind()->will(function (): void {}); - $paginatorProphecy->valid()->willReturn(true, false); - $paginatorProphecy->current()->willReturn('foo'); - $paginatorProphecy->next()->will(function (): void {}); + $paginator->method('getCurrentPage')->willReturn(3.); + $paginator->method('getItemsPerPage')->willReturn(12.); + $paginator->method('valid')->willReturnOnConsecutiveCalls(true, false); // @phpstan-ignore-line + $paginator->method('current')->willReturn('foo'); // @phpstan-ignore-line if (!$partial) { - $paginatorProphecy->getLastPage()->willReturn(7); - $paginatorProphecy->getTotalItems()->willReturn(1312); + $paginator->method('getLastPage')->willReturn(7.); // @phpstan-ignore-line + $paginator->method('getTotalItems')->willReturn(1312.); // @phpstan-ignore-line } else { - $paginatorProphecy->count()->willReturn(12); + $paginator->method('count')->willReturn(12); } - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $resourceClassResolverProphecy->getResourceClass($paginatorProphecy, 'Foo')->willReturn('Foo'); + $resourceClassResolverMock = $this->createMock(ResourceClassResolverInterface::class); + $resourceClassResolverMock->method('getResourceClass')->with($paginator, 'Foo')->willReturn('Foo'); - $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); - $resourceMetadataFactoryProphecy->create('Foo')->willReturn(new ResourceMetadataCollection('Foo', [ + $resourceMetadataFactoryMock = $this->createMock(ResourceMetadataCollectionFactoryInterface::class); + $resourceMetadataFactoryMock->method('create')->with('Foo')->willReturn(new ResourceMetadataCollection('Foo', [ (new ApiResource())->withShortName('Foo')->withOperations(new Operations([ 'bar' => (new GetCollection())->withShortName('Foo'), ])), ])); - $itemNormalizer = $this->prophesize(NormalizerInterface::class); - $itemNormalizer->normalize('foo', CollectionNormalizer::FORMAT, [ + $itemNormalizer = $this->createMock(NormalizerInterface::class); + $itemNormalizer->method('normalize')->with('foo', CollectionNormalizer::FORMAT, [ 'resource_class' => 'Foo', 'api_sub_level' => true, 'root_operation_name' => 'bar', ])->willReturn(['_links' => ['self' => '/me'], 'name' => 'Kévin']); - $normalizer = new CollectionNormalizer($resourceClassResolverProphecy->reveal(), 'page', $resourceMetadataFactoryProphecy->reveal()); - $normalizer->setNormalizer($itemNormalizer->reveal()); + $normalizer = new CollectionNormalizer($resourceClassResolverMock, 'page', $resourceMetadataFactoryMock); + $normalizer->setNormalizer($itemNormalizer); - return $normalizer->normalize($paginatorProphecy->reveal(), CollectionNormalizer::FORMAT, [ + return $normalizer->normalize($paginator, CollectionNormalizer::FORMAT, [ 'resource_class' => 'Foo', 'operation_name' => 'bar', ]); diff --git a/tests/Hal/Serializer/EntrypointNormalizerTest.php b/src/Hal/Tests/Serializer/EntrypointNormalizerTest.php similarity index 66% rename from tests/Hal/Serializer/EntrypointNormalizerTest.php rename to src/Hal/Tests/Serializer/EntrypointNormalizerTest.php index e4ac3eaeadf..1bc0d59972c 100644 --- a/tests/Hal/Serializer/EntrypointNormalizerTest.php +++ b/src/Hal/Tests/Serializer/EntrypointNormalizerTest.php @@ -25,25 +25,22 @@ use ApiPlatform\Metadata\UrlGeneratorInterface; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; /** * @author Kévin Dunglas */ class EntrypointNormalizerTest extends TestCase { - use ProphecyTrait; - public function testSupportNormalization(): void { $collection = new ResourceNameCollection(); $entrypoint = new Entrypoint($collection); - $factoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); - $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - $urlGeneratorProphecy = $this->prophesize(UrlGeneratorInterface::class); + $factoryMock = $this->createMock(ResourceMetadataCollectionFactoryInterface::class); + $iriConverterMock = $this->createMock(IriConverterInterface::class); + $urlGeneratorMock = $this->createMock(UrlGeneratorInterface::class); - $normalizer = new EntrypointNormalizer($factoryProphecy->reveal(), $iriConverterProphecy->reveal(), $urlGeneratorProphecy->reveal()); + $normalizer = new EntrypointNormalizer($factoryMock, $iriConverterMock, $urlGeneratorMock); $this->assertTrue($normalizer->supportsNormalization($entrypoint, EntrypointNormalizer::FORMAT)); $this->assertFalse($normalizer->supportsNormalization($entrypoint, 'json')); @@ -57,23 +54,23 @@ public function testNormalize(): void { $collection = new ResourceNameCollection([Dummy::class]); $entrypoint = new Entrypoint($collection); - $factoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class); + $factoryMock = $this->createMock(ResourceMetadataCollectionFactoryInterface::class); $operation = (new GetCollection())->withShortName('Dummy')->withClass(Dummy::class); - $factoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadataCollection('Dummy', [ + $factoryMock->expects($this->once())->method('create')->with(Dummy::class)->willReturn(new ResourceMetadataCollection('Dummy', [ (new ApiResource('Dummy')) ->withShortName('Dummy') ->withOperations(new Operations([ 'get' => $operation, ])), - ]))->shouldBeCalled(); + ])); - $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - $iriConverterProphecy->getIriFromResource(Dummy::class, UrlGeneratorInterface::ABS_PATH, $operation)->willReturn('/api/dummies')->shouldBeCalled(); + $iriConverterMock = $this->createMock(IriConverterInterface::class); + $iriConverterMock->expects($this->once())->method('getIriFromResource')->with(Dummy::class, UrlGeneratorInterface::ABS_PATH, $operation)->willReturn('/api/dummies'); - $urlGeneratorProphecy = $this->prophesize(UrlGeneratorInterface::class); - $urlGeneratorProphecy->generate('api_entrypoint')->willReturn('/api')->shouldBeCalled(); + $urlGeneratorMock = $this->createMock(UrlGeneratorInterface::class); + $urlGeneratorMock->expects($this->once())->method('generate')->with('api_entrypoint')->willReturn('/api'); - $normalizer = new EntrypointNormalizer($factoryProphecy->reveal(), $iriConverterProphecy->reveal(), $urlGeneratorProphecy->reveal()); + $normalizer = new EntrypointNormalizer($factoryMock, $iriConverterMock, $urlGeneratorMock); $expected = [ '_links' => [ diff --git a/src/Hal/Tests/Serializer/ItemNormalizerTest.php b/src/Hal/Tests/Serializer/ItemNormalizerTest.php new file mode 100644 index 00000000000..6156f52b2f5 --- /dev/null +++ b/src/Hal/Tests/Serializer/ItemNormalizerTest.php @@ -0,0 +1,465 @@ + + * + * 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\Hal\Serializer; + +use ApiPlatform\Hal\Serializer\ItemNormalizer; +use ApiPlatform\Hal\Tests\Fixtures\ApiResource\Issue5452\ActivableInterface; +use ApiPlatform\Hal\Tests\Fixtures\ApiResource\Issue5452\Author; +use ApiPlatform\Hal\Tests\Fixtures\ApiResource\Issue5452\Book; +use ApiPlatform\Hal\Tests\Fixtures\ApiResource\Issue5452\Library; +use ApiPlatform\Hal\Tests\Fixtures\ApiResource\Issue5452\TimestampableInterface; +use ApiPlatform\Hal\Tests\Fixtures\Dummy; +use ApiPlatform\Hal\Tests\Fixtures\MaxDepthDummy; +use ApiPlatform\Hal\Tests\Fixtures\RelatedDummy; +use ApiPlatform\Metadata\ApiProperty; +use ApiPlatform\Metadata\IriConverterInterface; +use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; +use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; +use ApiPlatform\Metadata\Property\PropertyNameCollection; +use ApiPlatform\Metadata\ResourceClassResolverInterface; +use PHPUnit\Framework\TestCase; +use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\Serializer\Exception\LogicException; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; +use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; +use Symfony\Component\Serializer\NameConverter\NameConverterInterface; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; +use Symfony\Component\Serializer\Serializer; + +/** + * @author Kévin Dunglas + */ +class ItemNormalizerTest extends TestCase +{ + public function testDoesNotSupportDenormalization(): void + { + $this->expectException(LogicException::class); + $this->expectExceptionMessage('jsonhal is a read-only format.'); + + $propertyNameCollectionFactoryMock = $this->createMock(PropertyNameCollectionFactoryInterface::class); + $propertyMetadataFactoryMock = $this->createMock(PropertyMetadataFactoryInterface::class); + $iriConverterMock = $this->createMock(IriConverterInterface::class); + $resourceClassResolverMock = $this->createMock(ResourceClassResolverInterface::class); + $nameConverter = $this->createMock(NameConverterInterface::class); + + $normalizer = new ItemNormalizer( + $propertyNameCollectionFactoryMock, + $propertyMetadataFactoryMock, + $iriConverterMock, + $resourceClassResolverMock, + null, + $nameConverter + ); + + $this->assertFalse($normalizer->supportsDenormalization('foo', ItemNormalizer::FORMAT)); + $normalizer->denormalize(['foo'], 'Foo'); + } + + #[\PHPUnit\Framework\Attributes\Group('legacy')] + public function testSupportsNormalization(): void + { + $std = new \stdClass(); + $dummy = new Dummy(); + + $propertyNameCollectionFactoryMock = $this->createMock(PropertyNameCollectionFactoryInterface::class); + $propertyMetadataFactoryMock = $this->createMock(PropertyMetadataFactoryInterface::class); + $iriConverterMock = $this->createMock(IriConverterInterface::class); + + $resourceClassResolverMock = $this->createMock(ResourceClassResolverInterface::class); + $resourceClassResolverMock->method('isResourceClass')->willReturnMap([ + [Dummy::class, true], + [\stdClass::class, false], + ]); + + $nameConverter = $this->createMock(NameConverterInterface::class); + + $normalizer = new ItemNormalizer( + $propertyNameCollectionFactoryMock, + $propertyMetadataFactoryMock, + $iriConverterMock, + $resourceClassResolverMock, + null, + $nameConverter + ); + + $this->assertTrue($normalizer->supportsNormalization($dummy, $normalizer::FORMAT)); + $this->assertFalse($normalizer->supportsNormalization($dummy, 'xml')); + $this->assertFalse($normalizer->supportsNormalization($std, $normalizer::FORMAT)); + $this->assertEmpty($normalizer->getSupportedTypes('xml')); + $this->assertSame(['object' => true], $normalizer->getSupportedTypes($normalizer::FORMAT)); + } + + public function testNormalize(): void + { + $relatedDummy = new RelatedDummy(); + $dummy = new Dummy(); + $dummy->setName('hello'); + $dummy->setRelatedDummy($relatedDummy); + + $propertyNameCollection = new PropertyNameCollection(['name', 'relatedDummy']); + $propertyNameCollectionFactoryMock = $this->createMock(PropertyNameCollectionFactoryInterface::class); + $propertyNameCollectionFactoryMock->method('create')->with(Dummy::class, ['api_allow_update' => false])->willReturn($propertyNameCollection); + + $propertyMetadataFactoryMock = $this->createMock(PropertyMetadataFactoryInterface::class); + $propertyMetadataFactoryMock->method('create')->willReturnMap([ + [Dummy::class, 'name', ['api_allow_update' => false], (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)], + [Dummy::class, 'relatedDummy', ['api_allow_update' => false], (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class)])->withDescription('')->withReadable(true)->withWritable(false)->withWritableLink(false)], + ]); + + $iriConverterMock = $this->createMock(IriConverterInterface::class); + $iriConverterMock->method('getIriFromResource')->willReturnCallback( + function ($resource) use ($dummy, $relatedDummy) { + if ($resource === $dummy) { + return '/dummies/1'; + } + if ($resource === $relatedDummy) { + return '/related-dummies/2'; + } + + return null; + } + ); + + $resourceClassResolverMock = $this->createMock(ResourceClassResolverInterface::class); + $resourceClassResolverMock->method('isResourceClass')->willReturn(true); + $resourceClassResolverMock->method('getResourceClass')->willReturnMap([ + [$dummy, null, Dummy::class], + [null, Dummy::class, Dummy::class], + [$dummy, Dummy::class, Dummy::class], + [$relatedDummy, RelatedDummy::class, RelatedDummy::class], + ]); + + $serializerMock = $this->createMock(Serializer::class); + $serializerMock->method('normalize')->with('hello', null, $this->isType('array'))->willReturn('hello'); + + $nameConverter = $this->createMock(NameConverterInterface::class); + $nameConverter->method('normalize')->willReturnCallback( + static function (string $propertyName): string { + if ('relatedDummy' === $propertyName) { + return 'related_dummy'; + } + + return $propertyName; + } + ); + + $normalizer = new ItemNormalizer( + $propertyNameCollectionFactoryMock, + $propertyMetadataFactoryMock, + $iriConverterMock, + $resourceClassResolverMock, + null, + $nameConverter + ); + $normalizer->setSerializer($serializerMock); + + $expected = [ + '_links' => [ + 'self' => [ + 'href' => '/dummies/1', + ], + 'related_dummy' => [ + 'href' => '/related-dummies/2', + ], + ], + 'name' => 'hello', + ]; + $this->assertEquals($expected, $normalizer->normalize($dummy)); + } + + public function testNormalizeWithUnionIntersectTypes(): void + { + $author = new Author(id: 2, name: 'Isaac Asimov'); + $library = new Library(id: 3, name: 'Le Bâteau Livre'); + $book = new Book(); + $book->author = $author; + $book->library = $library; + + $propertyNameCollection = new PropertyNameCollection(['author', 'library']); + $propertyNameCollectionFactoryMock = $this->createMock(PropertyNameCollectionFactoryInterface::class); + $propertyNameCollectionFactoryMock->method('create')->with(Book::class, ['api_allow_update' => false])->willReturn($propertyNameCollection); + + $propertyMetadataFactoryMock = $this->createMock(PropertyMetadataFactoryInterface::class); + $propertyMetadataFactoryMock->method('create')->willReturnMap([ + [Book::class, 'author', ['api_allow_update' => false], (new ApiProperty())->withBuiltinTypes([ + new Type(Type::BUILTIN_TYPE_OBJECT, false, ActivableInterface::class), + new Type(Type::BUILTIN_TYPE_OBJECT, false, TimestampableInterface::class), + ])->withReadable(true)], + [Book::class, 'library', ['api_allow_update' => false], (new ApiProperty())->withBuiltinTypes([ + new Type(Type::BUILTIN_TYPE_OBJECT, false, ActivableInterface::class), + new Type(Type::BUILTIN_TYPE_OBJECT, false, TimestampableInterface::class), + ])->withReadable(true)], + ]); + + $iriConverterMock = $this->createMock(IriConverterInterface::class); + $iriConverterMock->method('getIriFromResource')->with($book)->willReturn('/books/1'); + + $resourceClassResolverMock = $this->createMock(ResourceClassResolverInterface::class); + $resourceClassResolverMock->method('isResourceClass')->willReturnMap([ + [Book::class, true], + [ActivableInterface::class, false], + [TimestampableInterface::class, false], + ]); + $resourceClassResolverMock->method('getResourceClass')->willReturnMap([ + [$book, null, Book::class], + [null, Book::class, Book::class], + ]); + + $serializerMock = $this->createMock(Serializer::class); + + $nameConverter = $this->createMock(NameConverterInterface::class); + $nameConverter->method('normalize')->willReturnCallback( + static fn (string $propertyName): string => $propertyName + ); + + $normalizer = new ItemNormalizer( + $propertyNameCollectionFactoryMock, + $propertyMetadataFactoryMock, + $iriConverterMock, + $resourceClassResolverMock, + null, + $nameConverter + ); + $normalizer->setSerializer($serializerMock); + + $expected = [ + '_links' => [ + 'self' => [ + 'href' => '/books/1', + ], + ], + 'author' => null, + 'library' => null, + ]; + $this->assertEquals($expected, $normalizer->normalize($book)); + } + + public function testNormalizeWithoutCache(): void + { + $relatedDummy = new RelatedDummy(); + $dummy = new Dummy(); + $dummy->setName('hello'); + $dummy->setRelatedDummy($relatedDummy); + + $propertyNameCollection = new PropertyNameCollection(['name', 'relatedDummy']); + $propertyNameCollectionFactoryMock = $this->createMock(PropertyNameCollectionFactoryInterface::class); + $propertyNameCollectionFactoryMock->method('create')->with(Dummy::class, ['api_allow_update' => false])->willReturn($propertyNameCollection); + + $propertyMetadataFactoryMock = $this->createMock(PropertyMetadataFactoryInterface::class); + $propertyMetadataFactoryMock->method('create')->willReturnMap([ + [Dummy::class, 'name', ['api_allow_update' => false], (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)], + [Dummy::class, 'relatedDummy', ['api_allow_update' => false], (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class)])->withDescription('')->withReadable(true)->withWritable(false)->withReadableLink(false)], + ]); + + $iriConverterMock = $this->createMock(IriConverterInterface::class); + $iriConverterMock->method('getIriFromResource')->willReturnCallback( + function ($resource) use ($dummy, $relatedDummy) { + if ($resource === $dummy) { + return '/dummies/1'; + } + if ($resource === $relatedDummy) { + return '/related-dummies/2'; + } + + return null; + } + ); + + $resourceClassResolverMock = $this->createMock(ResourceClassResolverInterface::class); + $resourceClassResolverMock->method('getResourceClass')->willReturnMap([ + [$dummy, null, Dummy::class], + [$dummy, Dummy::class, Dummy::class], + [null, Dummy::class, Dummy::class], + [$relatedDummy, RelatedDummy::class, RelatedDummy::class], + ]); + $resourceClassResolverMock->method('isResourceClass')->willReturn(true); + + $serializerMock = $this->createMock(Serializer::class); + $serializerMock->method('normalize')->with('hello', null, $this->isType('array'))->willReturn('hello'); + + $nameConverter = $this->createMock(NameConverterInterface::class); + $nameConverter->method('normalize')->willReturnCallback( + static function (string $propertyName): string { + if ('relatedDummy' === $propertyName) { + return 'related_dummy'; + } + + return $propertyName; + } + ); + + $normalizer = new ItemNormalizer( + $propertyNameCollectionFactoryMock, + $propertyMetadataFactoryMock, + $iriConverterMock, + $resourceClassResolverMock, + null, + $nameConverter + ); + $normalizer->setSerializer($serializerMock); + + $expected = [ + '_links' => [ + 'self' => [ + 'href' => '/dummies/1', + ], + 'related_dummy' => [ + 'href' => '/related-dummies/2', + ], + ], + 'name' => 'hello', + ]; + $this->assertEquals($expected, $normalizer->normalize($dummy, null, ['not_serializable' => fn () => null])); + } + + public function testMaxDepth(): void + { + $setId = function (MaxDepthDummy $dummy, int $id): void { + $prop = new \ReflectionProperty($dummy, 'id'); + $prop->setAccessible(true); + $prop->setValue($dummy, $id); + }; + + $level1 = new MaxDepthDummy(); + $setId($level1, 1); + $level1->name = 'level 1'; + + $level2 = new MaxDepthDummy(); + $setId($level2, 2); + $level2->name = 'level 2'; + $level1->child = $level2; + + $level3 = new MaxDepthDummy(); + $setId($level3, 3); + $level3->name = 'level 3'; + $level2->child = $level3; + + $propertyNameCollection = new PropertyNameCollection(['id', 'name', 'child']); + $propertyNameCollectionFactoryMock = $this->createMock(PropertyNameCollectionFactoryInterface::class); + $propertyNameCollectionFactoryMock->method('create')->with(MaxDepthDummy::class, ['api_allow_update' => false])->willReturn($propertyNameCollection); + + $propertyMetadataFactoryMock = $this->createMock(PropertyMetadataFactoryInterface::class); + $propertyMetadataFactoryMock->method('create')->willReturnMap([ + [MaxDepthDummy::class, 'id', ['api_allow_update' => false], (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)])->withDescription('')->withReadable(true)], + [MaxDepthDummy::class, 'name', ['api_allow_update' => false], (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)], + [MaxDepthDummy::class, 'child', ['api_allow_update' => false], (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, false, MaxDepthDummy::class)])->withDescription('')->withReadable(true)->withWritable(false)->withReadableLink(true)], + ]); + + $iriConverterMock = $this->createMock(IriConverterInterface::class); + $iriConverterMock->method('getIriFromResource')->willReturnCallback( + function ($resource) use ($level1, $level2, $level3) { + if ($resource === $level1) { + return '/max_depth_dummies/1'; + } + if ($resource === $level2) { + return '/max_depth_dummies/2'; + } + if ($resource === $level3) { + return '/max_depth_dummies/3'; + } + + return null; + } + ); + + $resourceClassResolverMock = $this->createMock(ResourceClassResolverInterface::class); + $resourceClassResolverMock->method('getResourceClass')->willReturnMap([ + [$level1, null, MaxDepthDummy::class], + [$level1, MaxDepthDummy::class, MaxDepthDummy::class], + [$level2, MaxDepthDummy::class, MaxDepthDummy::class], + [$level3, MaxDepthDummy::class, MaxDepthDummy::class], + [null, MaxDepthDummy::class, MaxDepthDummy::class], + ]); + $resourceClassResolverMock->method('isResourceClass')->with(MaxDepthDummy::class)->willReturn(true); + + $normalizer = new ItemNormalizer( + $propertyNameCollectionFactoryMock, + $propertyMetadataFactoryMock, + $iriConverterMock, + $resourceClassResolverMock, + null, + null, + new ClassMetadataFactory(new AttributeLoader()) + ); + $serializer = new Serializer([$normalizer]); + $normalizer->setSerializer($serializer); + + $expected = [ + '_links' => [ + 'self' => [ + 'href' => '/max_depth_dummies/1', + ], + 'child' => [ + 'href' => '/max_depth_dummies/2', + ], + ], + '_embedded' => [ + 'child' => [ + '_links' => [ + 'self' => [ + 'href' => '/max_depth_dummies/2', + ], + 'child' => [ + 'href' => '/max_depth_dummies/3', + ], + ], + '_embedded' => [ + 'child' => [ + '_links' => [ + 'self' => [ + 'href' => '/max_depth_dummies/3', + ], + ], + 'id' => 3, + 'name' => 'level 3', + ], + ], + 'id' => 2, + 'name' => 'level 2', + ], + ], + 'id' => 1, + 'name' => 'level 1', + ]; + + $this->assertEquals($expected, $normalizer->normalize($level1, ItemNormalizer::FORMAT)); + $this->assertEquals($expected, $normalizer->normalize($level1, ItemNormalizer::FORMAT, [ObjectNormalizer::ENABLE_MAX_DEPTH => false])); + + $expected = [ + '_links' => [ + 'self' => [ + 'href' => '/max_depth_dummies/1', + ], + 'child' => [ + 'href' => '/max_depth_dummies/2', + ], + ], + '_embedded' => [ + 'child' => [ + '_links' => [ + 'self' => [ + 'href' => '/max_depth_dummies/2', + ], + ], + 'id' => 2, + 'name' => 'level 2', + ], + ], + 'id' => 1, + 'name' => 'level 1', + ]; + + $this->assertEquals($expected, $normalizer->normalize($level1, ItemNormalizer::FORMAT, [ObjectNormalizer::ENABLE_MAX_DEPTH => true])); + } +} diff --git a/tests/Hal/Serializer/ObjectNormalizerTest.php b/src/Hal/Tests/Serializer/ObjectNormalizerTest.php similarity index 60% rename from tests/Hal/Serializer/ObjectNormalizerTest.php rename to src/Hal/Tests/Serializer/ObjectNormalizerTest.php index ed89b9fe812..8053dde307f 100644 --- a/tests/Hal/Serializer/ObjectNormalizerTest.php +++ b/src/Hal/Tests/Serializer/ObjectNormalizerTest.php @@ -14,10 +14,9 @@ namespace ApiPlatform\Tests\Hal\Serializer; use ApiPlatform\Hal\Serializer\ObjectNormalizer; +use ApiPlatform\Hal\Tests\Fixtures\Dummy; use ApiPlatform\Metadata\IriConverterInterface; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; @@ -26,19 +25,17 @@ */ class ObjectNormalizerTest extends TestCase { - use ProphecyTrait; - public function testDoesNotSupportDenormalization(): void { $this->expectException(LogicException::class); $this->expectExceptionMessage('jsonhal is a read-only format.'); - $normalizerInterfaceProphecy = $this->prophesize(NormalizerInterface::class); - $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); + $normalizerInterfaceMock = $this->createMock(NormalizerInterface::class); + $iriConverterMock = $this->createMock(IriConverterInterface::class); $normalizer = new ObjectNormalizer( - $normalizerInterfaceProphecy->reveal(), - $iriConverterProphecy->reveal() + $normalizerInterfaceMock, + $iriConverterMock ); $this->assertFalse($normalizer->supportsDenormalization('foo', 'type', 'format')); @@ -50,18 +47,15 @@ public function testSupportsNormalization(): void { $std = new \stdClass(); $dummy = new Dummy(); - $dummy->setDescription('hello'); - $normalizerInterfaceProphecy = $this->prophesize(NormalizerInterface::class); - $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); + $normalizerInterfaceMock = $this->createMock(NormalizerInterface::class); + $iriConverterMock = $this->createMock(IriConverterInterface::class); $normalizer = new ObjectNormalizer( - $normalizerInterfaceProphecy->reveal(), - $iriConverterProphecy->reveal() + $normalizerInterfaceMock, + $iriConverterMock ); - $normalizerInterfaceProphecy->supportsNormalization($dummy, 'xml', [])->willReturn(true); - $normalizerInterfaceProphecy->supportsNormalization($std, ObjectNormalizer::FORMAT, [])->willReturn(true); - $normalizerInterfaceProphecy->supportsNormalization($dummy, ObjectNormalizer::FORMAT, [])->willReturn(true); + $normalizerInterfaceMock->method('supportsNormalization')->willReturn(true); $this->assertFalse($normalizer->supportsNormalization($dummy, 'xml')); $this->assertTrue($normalizer->supportsNormalization($std, ObjectNormalizer::FORMAT)); diff --git a/src/Hal/composer.json b/src/Hal/composer.json index 410c010d764..024c3e71b3e 100644 --- a/src/Hal/composer.json +++ b/src/Hal/composer.json @@ -24,6 +24,7 @@ "php": ">=8.2", "api-platform/state": "^4.1.11", "api-platform/metadata": "^4.1.11", + "api-platform/documentation": "^4.1.11", "api-platform/serializer": "^4.1.11" }, "autoload": { @@ -62,7 +63,8 @@ "test": "./vendor/bin/phpunit" }, "require-dev": { - "phpunit/phpunit": "11.5.x-dev" + "phpunit/phpunit": "11.5.x-dev", + "api-platform/json-schema": "^4.1.11" }, "repositories": [ { diff --git a/src/Hal/phpunit.baseline.xml b/src/Hal/phpunit.baseline.xml new file mode 100644 index 00000000000..e3ef6196399 --- /dev/null +++ b/src/Hal/phpunit.baseline.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/Hal/phpunit.xml.dist b/src/Hal/phpunit.xml.dist new file mode 100644 index 00000000000..5aa43a61149 --- /dev/null +++ b/src/Hal/phpunit.xml.dist @@ -0,0 +1,23 @@ + + + + + + + + ./Tests/ + + + + + trigger_deprecation + + + ./ + + + ./Tests + ./vendor + + + diff --git a/src/JsonApi/Tests/Serializer/ItemNormalizerTest.php b/src/JsonApi/Tests/Serializer/ItemNormalizerTest.php index 610b264b126..bb1c010549a 100644 --- a/src/JsonApi/Tests/Serializer/ItemNormalizerTest.php +++ b/src/JsonApi/Tests/Serializer/ItemNormalizerTest.php @@ -61,7 +61,7 @@ public function testNormalize(): void $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::any())->willReturn((new ApiProperty())->withReadable(true)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'id', Argument::any())->willReturn((new ApiProperty())->withReadable(true)->withIdentifier(true)); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'id', Argument::type('array'))->willReturn((new ApiProperty())->withReadable(true)->withIdentifier(true)); $propertyMetadataFactoryProphecy->create(Dummy::class, '\bad_property', Argument::any())->willReturn((new ApiProperty())->withReadable(true)); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); @@ -351,12 +351,12 @@ public function testDenormalizeCollectionIsNotArray(): void ]; $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['relatedDummies'])); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['relatedDummies'])); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); $type = new Type(Type::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn((new ApiProperty()) + $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty()) ->withBuiltinTypes([$type]) ->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false) ); @@ -407,11 +407,11 @@ public function testDenormalizeCollectionWithInvalidKey(): void ]; $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['relatedDummies'])); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['relatedDummies'])); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); $type = new Type(Type::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new Type(Type::BUILTIN_TYPE_STRING), new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn( + $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn( (new ApiProperty())->withBuiltinTypes([$type])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false) ); @@ -456,10 +456,10 @@ public function testDenormalizeRelationIsNotResourceLinkage(): void ]; $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['relatedDummy'])); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['relatedDummy'])); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn( + $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn( (new ApiProperty())->withBuiltinTypes([ new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class), ])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false) ); diff --git a/src/Metadata/Property/Factory/PropertyInfoPropertyMetadataFactory.php b/src/Metadata/Property/Factory/PropertyInfoPropertyMetadataFactory.php index c15072345d8..39d9c8d7399 100644 --- a/src/Metadata/Property/Factory/PropertyInfoPropertyMetadataFactory.php +++ b/src/Metadata/Property/Factory/PropertyInfoPropertyMetadataFactory.php @@ -66,7 +66,9 @@ public function create(string $resourceClass, string $property, array $options = $propertyMetadata = $propertyMetadata->withReadable($readable); } - if (null === $propertyMetadata->isWritable() && null !== $writable = $this->propertyInfo->isWritable($resourceClass, $property, $options)) { + // A property might not be writable and we can still want to update it, + // this leaves the choice to the SerializerPropertyMetadataFactory + if (false === ($options['api_allow_update'] ?? false) && null === $propertyMetadata->isWritable() && null !== $writable = $this->propertyInfo->isWritable($resourceClass, $property, $options)) { $propertyMetadata = $propertyMetadata->withWritable($writable); } diff --git a/src/Serializer/AbstractItemNormalizer.php b/src/Serializer/AbstractItemNormalizer.php index db98f1358ef..cf1f351c4f6 100644 --- a/src/Serializer/AbstractItemNormalizer.php +++ b/src/Serializer/AbstractItemNormalizer.php @@ -622,7 +622,7 @@ protected function denormalizeRelation(string $attributeName, ApiProperty $prope */ protected function getFactoryOptions(array $context): array { - $options = []; + $options = ['api_allow_update' => $context['api_allow_update'] ?? false]; if (isset($context[self::GROUPS])) { /* @see https://github.com/symfony/symfony/blob/v4.2.6/src/Symfony/Component/PropertyInfo/Extractor/SerializerExtractor.php */ $options['serializer_groups'] = (array) $context[self::GROUPS]; diff --git a/src/Serializer/ItemNormalizer.php b/src/Serializer/ItemNormalizer.php index 88466bc29a6..2f678d0073a 100644 --- a/src/Serializer/ItemNormalizer.php +++ b/src/Serializer/ItemNormalizer.php @@ -115,15 +115,4 @@ private function getContextUriVariables(array $data, $operation, array $context) return $uriVariables; } - - protected function getAllowedAttributes(string|object $classOrObject, array $context, bool $attributesAsString = false): array|bool - { - $allowedAttributes = parent::getAllowedAttributes($classOrObject, $context, $attributesAsString); - // id is a special case handled above it causes issues not allowing it - if (\is_array($allowedAttributes) && ($context['api_denormalize'] ?? false) && !\in_array('id', $allowedAttributes, true)) { - $allowedAttributes[] = 'id'; - } - - return $allowedAttributes; - } } diff --git a/src/Serializer/Tests/AbstractItemNormalizerTest.php b/src/Serializer/Tests/AbstractItemNormalizerTest.php index 799fed7a391..020b8beb9ed 100644 --- a/src/Serializer/Tests/AbstractItemNormalizerTest.php +++ b/src/Serializer/Tests/AbstractItemNormalizerTest.php @@ -99,16 +99,16 @@ public function testNormalize(): void $relatedDummies = new ArrayCollection([$relatedDummy]); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['name', 'alias', 'relatedDummy', 'relatedDummies'])); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['name', 'alias', 'relatedDummy', 'relatedDummies'])); $relatedDummyType = new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class); $relatedDummiesType = new Type(Type::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new Type(Type::BUILTIN_TYPE_INT), $relatedDummyType); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'alias', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withDescription('')->withReadable(true)->withWritable(false)->withReadableLink(false)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(true)->withWritable(false)->withReadableLink(false)); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'alias', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withDescription('')->withReadable(true)->withWritable(false)->withReadableLink(false)); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(true)->withWritable(false)->withReadableLink(false)); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); $iriConverterProphecy->getIriFromResource($dummy, Argument::cetera())->willReturn('/dummies/1'); @@ -153,11 +153,11 @@ public function testNormalizeWithSecuredProperty(): void $dummy->setAdminOnlyProperty('secret'); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, [])->willReturn(new PropertyNameCollection(['title', 'adminOnlyProperty'])); + $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['title', 'adminOnlyProperty'])); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)); - $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'adminOnlyProperty', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withSecurity('is_granted(\'ROLE_ADMIN\')')); + $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)); + $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'adminOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withSecurity('is_granted(\'ROLE_ADMIN\')')); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); $iriConverterProphecy->getIriFromResource($dummy, Argument::cetera())->willReturn('/secured_dummies/1'); @@ -213,24 +213,24 @@ public function testNormalizePropertyAsIriWithUriTemplate(): void $resourceMetadataCollectionFactoryProphecy->create(PropertyCollectionIriOnlyRelation::class)->willReturn($resourceRelationMetadataCollection); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(PropertyCollectionIriOnly::class, ['normalization_groups' => null, 'denormalization_groups' => null, 'operation_name' => null])->willReturn( + $propertyNameCollectionFactoryProphecy->create(PropertyCollectionIriOnly::class, ['normalization_groups' => null, 'denormalization_groups' => null, 'operation_name' => null, 'api_allow_update' => false])->willReturn( new PropertyNameCollection(['propertyCollectionIriOnlyRelation', 'iterableIri', 'toOneRelation']) ); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(PropertyCollectionIriOnly::class, 'propertyCollectionIriOnlyRelation', ['normalization_groups' => null, 'denormalization_groups' => null, 'operation_name' => null])->willReturn( + $propertyMetadataFactoryProphecy->create(PropertyCollectionIriOnly::class, 'propertyCollectionIriOnlyRelation', ['normalization_groups' => null, 'denormalization_groups' => null, 'operation_name' => null, 'api_allow_update' => false])->willReturn( (new ApiProperty())->withReadable(true)->withUriTemplate('/property-collection-relations')->withBuiltinTypes([ new Type('iterable', false, null, true, new Type('int', false, null, false), new Type('object', false, PropertyCollectionIriOnlyRelation::class, false)), ]) ); - $propertyMetadataFactoryProphecy->create(PropertyCollectionIriOnly::class, 'iterableIri', ['normalization_groups' => null, 'denormalization_groups' => null, 'operation_name' => null])->willReturn( + $propertyMetadataFactoryProphecy->create(PropertyCollectionIriOnly::class, 'iterableIri', ['normalization_groups' => null, 'denormalization_groups' => null, 'operation_name' => null, 'api_allow_update' => false])->willReturn( (new ApiProperty())->withReadable(true)->withUriTemplate('/parent/{parentId}/another-collection-operations')->withBuiltinTypes([ new Type('iterable', false, null, true, new Type('int', false, null, false), new Type('object', false, PropertyCollectionIriOnlyRelation::class, false)), ]) ); - $propertyMetadataFactoryProphecy->create(PropertyCollectionIriOnly::class, 'toOneRelation', ['normalization_groups' => null, 'denormalization_groups' => null, 'operation_name' => null])->willReturn( + $propertyMetadataFactoryProphecy->create(PropertyCollectionIriOnly::class, 'toOneRelation', ['normalization_groups' => null, 'denormalization_groups' => null, 'operation_name' => null, 'api_allow_update' => false])->willReturn( (new ApiProperty())->withReadable(true)->withUriTemplate('/parent/{parentId}/another-collection-operations/{id}')->withBuiltinTypes([ new Type('object', false, PropertyCollectionIriOnlyRelation::class, false), ]) @@ -281,11 +281,11 @@ public function testDenormalizeWithSecuredProperty(): void ]; $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, [])->willReturn(new PropertyNameCollection(['title', 'adminOnlyProperty'])); + $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['title', 'adminOnlyProperty'])); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true)); - $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'adminOnlyProperty', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withSecurity('is_granted(\'ROLE_ADMIN\')')); + $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'adminOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withSecurity('is_granted(\'ROLE_ADMIN\')')); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); @@ -324,11 +324,11 @@ public function testDenormalizeCreateWithDeniedPostDenormalizeSecuredProperty(): ]; $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, [])->willReturn(new PropertyNameCollection(['title', 'ownerOnlyProperty'])); + $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['title', 'ownerOnlyProperty'])); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true)); - $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withWritable(true)->withSecurityPostDenormalize('false')->withDefault('')); + $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withWritable(true)->withSecurityPostDenormalize('false')->withDefault('')); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); @@ -370,11 +370,11 @@ public function testDenormalizeUpdateWithSecuredProperty(): void ]; $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, [])->willReturn(new PropertyNameCollection(['title', 'ownerOnlyProperty'])); + $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['title', 'ownerOnlyProperty'])); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true)); - $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withWritable(true)->withSecurity('true')); + $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withWritable(true)->withSecurity('true')); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); @@ -423,11 +423,11 @@ public function testDenormalizeUpdateWithDeniedSecuredProperty(): void ]; $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, [])->willReturn(new PropertyNameCollection(['title', 'ownerOnlyProperty'])); + $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['title', 'ownerOnlyProperty'])); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true)); - $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withWritable(true)->withSecurity('false')); + $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withWritable(true)->withSecurity('false')); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); @@ -476,11 +476,11 @@ public function testDenormalizeUpdateWithDeniedPostDenormalizeSecuredProperty(): ]; $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, [])->willReturn(new PropertyNameCollection(['title', 'ownerOnlyProperty'])); + $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['title', 'ownerOnlyProperty'])); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true)); - $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withWritable(true)->withSecurityPostDenormalize('false')); + $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withWritable(true)->withSecurityPostDenormalize('false')); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); @@ -526,14 +526,14 @@ public function testNormalizeReadableLinks(): void $relatedDummies = new ArrayCollection([$relatedDummy]); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['relatedDummy', 'relatedDummies'])); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['relatedDummy', 'relatedDummies'])); $relatedDummyType = new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class); $relatedDummiesType = new Type(Type::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new Type(Type::BUILTIN_TYPE_INT), $relatedDummyType); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withReadable(true)->withWritable(false)->withReadableLink(true)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(true)->withWritable(false)->withReadableLink(true)); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withReadable(true)->withWritable(false)->withReadableLink(true)); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(true)->withWritable(false)->withReadableLink(true)); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); $iriConverterProphecy->getIriFromResource($dummy, Argument::cetera())->willReturn('/dummies/1'); @@ -585,13 +585,13 @@ public function testNormalizePolymorphicRelations(): void $abstractDummies = new ArrayCollection([$concreteDummy]); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(DummyTableInheritanceRelated::class, [])->willReturn(new PropertyNameCollection(['children'])); + $propertyNameCollectionFactoryProphecy->create(DummyTableInheritanceRelated::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['children'])); $abstractDummyType = new Type(Type::BUILTIN_TYPE_OBJECT, false, DummyTableInheritance::class); $abstractDummiesType = new Type(Type::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new Type(Type::BUILTIN_TYPE_INT), $abstractDummyType); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(DummyTableInheritanceRelated::class, 'children', [])->willReturn((new ApiProperty())->withBuiltinTypes([$abstractDummiesType])->withReadable(true)->withWritable(false)->withReadableLink(true)); + $propertyMetadataFactoryProphecy->create(DummyTableInheritanceRelated::class, 'children', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$abstractDummiesType])->withReadable(true)->withWritable(false)->withReadableLink(true)); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); $iriConverterProphecy->getIriFromResource($dummy, Argument::cetera())->willReturn('/dummies/1'); @@ -639,16 +639,16 @@ public function testDenormalize(): void $relatedDummy2 = new RelatedDummy(); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['name', 'relatedDummy', 'relatedDummies'])); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['name', 'relatedDummy', 'relatedDummies'])); $relatedDummyType = new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class); $relatedDummiesType = new Type(Type::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new Type(Type::BUILTIN_TYPE_INT), $relatedDummyType); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); $iriConverterProphecy->getResourceFromIri('/dummies/1', Argument::type('array'))->willReturn($relatedDummy1); @@ -749,7 +749,7 @@ public function testDenormalizeWritableLinks(): void $relatedDummy4 = new RelatedDummy(); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['name', 'relatedDummy', 'relatedDummies', 'relatedDummiesWithUnionTypes'])); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['name', 'relatedDummy', 'relatedDummies', 'relatedDummiesWithUnionTypes'])); $relatedDummyType = new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class); $relatedDummiesType = new Type(Type::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new Type(Type::BUILTIN_TYPE_INT), $relatedDummyType); @@ -757,10 +757,10 @@ public function testDenormalizeWritableLinks(): void $relatedDummiesWithUnionTypesFloatType = new Type(Type::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new Type(Type::BUILTIN_TYPE_FLOAT), $relatedDummyType); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummiesWithUnionTypes', [])->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesWithUnionTypesIntType, $relatedDummiesWithUnionTypesFloatType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true)); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true)); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true)); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummiesWithUnionTypes', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesWithUnionTypesIntType, $relatedDummiesWithUnionTypesFloatType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true)); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); @@ -802,10 +802,10 @@ public function testBadRelationType(): void ]; $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['relatedDummy'])); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['relatedDummy'])); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn( + $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn( (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class)])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false) ); @@ -835,10 +835,10 @@ public function testBadRelationTypeWithExceptionToValidationErrors(): void ]; $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['relatedDummy'])); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['relatedDummy'])); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn( + $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn( (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class)])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false) ); @@ -870,10 +870,10 @@ public function testDeserializationPathForNotDenormalizableRelations(): void ]; $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['relatedDummies'])); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['relatedDummies'])); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn( + $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn( (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, false, null, true, null, new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class))])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true) ); @@ -909,6 +909,7 @@ public function testDeserializationPathForNotDenormalizableResource(): void $this->expectExceptionMessage('Invalid IRI "wrong IRI".'); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + $propertyNameCollectionFactoryProphecy->create(Argument::cetera())->willReturn(new PropertyNameCollection()); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); @@ -969,10 +970,10 @@ public function testInnerDocumentNotAllowed(): void ]; $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['relatedDummy'])); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['relatedDummy'])); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn( + $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn( (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class)])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false) ); @@ -1005,10 +1006,10 @@ public function testBadType(): void ]; $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['foo'])); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['foo'])); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); @@ -1034,10 +1035,10 @@ public function testTypeChecksCanBeDisabled(): void ]; $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['foo'])); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['foo'])); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); @@ -1067,10 +1068,10 @@ public function testJsonAllowIntAsFloat(): void ]; $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['foo'])); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['foo'])); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); @@ -1116,12 +1117,11 @@ public function testDenormalizeBadKeyType(): void ]; $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['relatedDummies'])); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['relatedDummies'])); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true)); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)); - + $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)); $type = new Type( Type::BUILTIN_TYPE_OBJECT, false, @@ -1130,7 +1130,7 @@ public function testDenormalizeBadKeyType(): void new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class) ); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn((new ApiProperty())->withBuiltinTypes([$type])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true)); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$type])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true)); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); @@ -1158,10 +1158,10 @@ public function testNullable(): void ]; $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['name'])); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['name'])); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING, true)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING, true)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); @@ -1187,7 +1187,7 @@ public function testNullable(): void public function testDenormalizeBasicTypePropertiesFromXml(): void { $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(ObjectWithBasicProperties::class, [])->willReturn(new PropertyNameCollection([ + $propertyNameCollectionFactoryProphecy->create(ObjectWithBasicProperties::class, Argument::type('array'))->willReturn(new PropertyNameCollection([ 'boolTrue1', 'boolFalse1', 'boolTrue2', @@ -1203,18 +1203,18 @@ public function testDenormalizeBasicTypePropertiesFromXml(): void ])); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolTrue1', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withDescription('')->withReadable(false)->withWritable(true)); - $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolFalse1', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withDescription('')->withReadable(false)->withWritable(true)); - $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolTrue2', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withDescription('')->withReadable(false)->withWritable(true)); - $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolFalse2', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withDescription('')->withReadable(false)->withWritable(true)); - $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'int1', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)])->withDescription('')->withReadable(false)->withWritable(true)); - $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'int2', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)])->withDescription('')->withReadable(false)->withWritable(true)); - $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'float1', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)); - $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'float2', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)); - $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'float3', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)); - $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'floatNaN', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)); - $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'floatInf', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)); - $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'floatNegInf', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolTrue1', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withDescription('')->withReadable(false)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolFalse1', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withDescription('')->withReadable(false)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolTrue2', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withDescription('')->withReadable(false)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolFalse2', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withDescription('')->withReadable(false)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'int1', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)])->withDescription('')->withReadable(false)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'int2', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)])->withDescription('')->withReadable(false)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'float1', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'float2', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'float3', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'floatNaN', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'floatInf', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'floatNegInf', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); @@ -1276,13 +1276,13 @@ public function testDenormalizeCollectionDecodedFromXmlWithOneChild(): void $relatedDummy->setName('foo'); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['relatedDummies'])); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['relatedDummies'])); $relatedDummyType = new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class); $relatedDummiesType = new Type(Type::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new Type(Type::BUILTIN_TYPE_INT), $relatedDummyType); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true)); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true)); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); @@ -1315,10 +1315,10 @@ public function testDenormalizePopulatingNonCloneableObject(): void ]; $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(NonCloneableDummy::class, [])->willReturn(new PropertyNameCollection(['name'])); + $propertyNameCollectionFactoryProphecy->create(NonCloneableDummy::class, Argument::type('array'))->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)); + $propertyMetadataFactoryProphecy->create(NonCloneableDummy::class, 'name', Argument::type('array'))->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); @@ -1349,10 +1349,10 @@ public function testDenormalizeObjectWithNullDisabledTypeEnforcement(): void ]; $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(DtoWithNullValue::class, [])->willReturn(new PropertyNameCollection(['dummy'])); + $propertyNameCollectionFactoryProphecy->create(DtoWithNullValue::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['dummy'])); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(DtoWithNullValue::class, 'dummy', [])->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, nullable: true)])->withDescription('')->withReadable(true)->withWritable(true)); + $propertyMetadataFactoryProphecy->create(DtoWithNullValue::class, 'dummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, nullable: true)])->withDescription('')->withReadable(true)->withWritable(true)); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class); @@ -1437,7 +1437,7 @@ public function testCacheKey(): void $operationCacheKey = (new \ReflectionClass($normalizer))->getProperty('localFactoryOptionsCache')->getValue($normalizer); $this->assertEquals(array_keys($operationCacheKey), [\sprintf('%s%s%s%s', Dummy::class, 'operation_name', 'root_operation_name', 'n')]); - $this->assertEquals(current($operationCacheKey), ['serializer_groups' => ['group']]); + $this->assertEquals(current($operationCacheKey), ['serializer_groups' => ['group'], 'api_allow_update' => false]); } } diff --git a/src/Serializer/Tests/ItemNormalizerTest.php b/src/Serializer/Tests/ItemNormalizerTest.php index 993b93ebdeb..6f19433a177 100644 --- a/src/Serializer/Tests/ItemNormalizerTest.php +++ b/src/Serializer/Tests/ItemNormalizerTest.php @@ -81,14 +81,14 @@ public function testNormalize(): void $propertyNameCollection = new PropertyNameCollection(['name']); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn($propertyNameCollection); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn($propertyNameCollection); $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); $propertyMetadata = (new ApiProperty())->withReadable(true); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn($propertyMetadata); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn($propertyMetadata); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); $iriConverterProphecy->getIriFromResource($dummy, Argument::cetera())->willReturn('/dummies/1'); @@ -119,11 +119,11 @@ public function testDenormalize(): void $propertyNameCollection = new PropertyNameCollection(['name']); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn($propertyNameCollection)->shouldBeCalled(); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn($propertyNameCollection)->shouldBeCalled(); $propertyMetadata = (new ApiProperty())->withReadable(true)->withWritable(true); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn($propertyMetadata)->shouldBeCalled(); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn($propertyMetadata)->shouldBeCalled(); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); @@ -150,12 +150,12 @@ public function testDenormalizeWithIri(): void $propertyNameCollection = new PropertyNameCollection(['id', 'name']); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn($propertyNameCollection)->shouldBeCalled(); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn($propertyNameCollection)->shouldBeCalled(); $propertyMetadata = (new ApiProperty())->withReadable(true)->withWritable(true); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'id', [])->willReturn($propertyMetadata)->shouldBeCalled(); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn($propertyMetadata)->shouldBeCalled(); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'id', Argument::type('array'))->willReturn($propertyMetadata)->shouldBeCalled(); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn($propertyMetadata)->shouldBeCalled(); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); $iriConverterProphecy->getResourceFromIri('/dummies/12', ['resource_class' => Dummy::class, 'api_allow_update' => true, 'fetch_data' => true])->shouldBeCalled(); @@ -213,14 +213,14 @@ public function testDenormalizeWithDefinedIri(): void $propertyNameCollection = new PropertyNameCollection(['name']); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn($propertyNameCollection); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn($propertyNameCollection); $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); $propertyMetadata = (new ApiProperty())->withReadable(true); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn($propertyMetadata); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn($propertyMetadata); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); $iriConverterProphecy->getIriFromResource($dummy)->shouldNotBeCalled(); @@ -251,12 +251,12 @@ public function testDenormalizeWithIdAndNoResourceClass(): void $propertyNameCollection = new PropertyNameCollection(['id', 'name']); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn($propertyNameCollection)->shouldBeCalled(); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn($propertyNameCollection)->shouldBeCalled(); $propertyMetadata = (new ApiProperty())->withReadable(true)->withWritable(true); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'id', [])->willReturn($propertyMetadata)->shouldBeCalled(); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn($propertyMetadata)->shouldBeCalled(); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'id', Argument::type('array'))->willReturn($propertyMetadata)->shouldBeCalled(); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn($propertyMetadata)->shouldBeCalled(); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); @@ -318,12 +318,12 @@ public function testDenormalizeWithWrongId(): void $propertyNameCollection = new PropertyNameCollection(['id', 'name']); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn($propertyNameCollection)->shouldBeCalled(); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn($propertyNameCollection)->shouldBeCalled(); $propertyMetadata = (new ApiProperty())->withReadable(true)->withWritable(true); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn($propertyMetadata)->shouldBeCalled(); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'id', [])->willReturn((new ApiProperty())->withIdentifier(true))->shouldBeCalled(); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn($propertyMetadata)->shouldBeCalled(); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'id', Argument::type('array'))->willReturn((new ApiProperty())->withIdentifier(true))->shouldBeCalled(); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); $iriConverterProphecy->getResourceFromIri('fail', $context + ['fetch_data' => true])->willThrow(new InvalidArgumentException()); diff --git a/src/Symfony/Bundle/Resources/config/metadata/property.xml b/src/Symfony/Bundle/Resources/config/metadata/property.xml index 1a0e221399a..2bcbc35cb66 100644 --- a/src/Symfony/Bundle/Resources/config/metadata/property.xml +++ b/src/Symfony/Bundle/Resources/config/metadata/property.xml @@ -13,7 +13,7 @@ - + diff --git a/tests/Fixtures/TestBundle/Entity/Camp.php b/tests/Fixtures/TestBundle/Entity/Camp.php new file mode 100644 index 00000000000..c409d55ad00 --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/Camp.php @@ -0,0 +1,53 @@ + + * + * 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\Metadata\ApiProperty; +use ApiPlatform\Metadata\ApiResource; +use Doctrine\ORM\Mapping as ORM; + +#[ApiResource( + denormalizationContext: [ + 'allow_extra_attributes' => false, + ], +)] +#[ORM\Entity] +class Camp +{ + #[ORM\Id] + #[ORM\Column(type: 'integer')] + #[ORM\GeneratedValue] + #[ApiProperty(writable: false)] + private ?int $id = null; + + #[ORM\Column(type: 'string', length: 255)] + private ?string $name = null; + + public function getId(): ?int + { + return $this->id; + } + + public function getName(): ?string + { + return $this->name; + } + + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } +} diff --git a/tests/Fixtures/TestBundle/Entity/Issue6225/Bar6225.php b/tests/Fixtures/TestBundle/Entity/Issue6225/Bar6225.php index 6a5b06df56e..d2658ff41c7 100644 --- a/tests/Fixtures/TestBundle/Entity/Issue6225/Bar6225.php +++ b/tests/Fixtures/TestBundle/Entity/Issue6225/Bar6225.php @@ -30,7 +30,7 @@ public function __construct() #[ORM\Id] #[ORM\Column(type: 'symfony_uuid', unique: true)] - #[Groups(['Foo:Read'])] + #[Groups(['Foo:Read', 'Foo:Write'])] private Uuid $id; #[ORM\OneToOne(mappedBy: 'bar', cascade: ['persist', 'remove'])] diff --git a/tests/Functional/NestedPatchTest.php b/tests/Functional/NestedPatchTest.php index 52fc1c756c1..c39485e9ad5 100644 --- a/tests/Functional/NestedPatchTest.php +++ b/tests/Functional/NestedPatchTest.php @@ -14,6 +14,7 @@ namespace ApiPlatform\Tests\Functional; use ApiPlatform\Symfony\Bundle\Test\ApiTestCase; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Camp; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6225\Bar6225; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue6225\Foo6225; use ApiPlatform\Tests\RecreateSchemaTrait; @@ -28,7 +29,7 @@ class NestedPatchTest extends ApiTestCase public static function getResources(): array { - return [Foo6225::class, Bar6225::class]; + return [Foo6225::class, Bar6225::class, Camp::class]; } public function testIssue6225(): void @@ -75,4 +76,61 @@ public function testIssue6225(): void ], ], json_decode($patchResponse->getContent(), true)); } + + public function testIdNotWriteable(): void + { + if ($this->isMongoDB()) { + $this->markTestSkipped(); + } + + $this->recreateSchema(self::getResources()); + + $camp = new Camp(); + $camp->setName('Test Camp'); + $manager = static::getContainer()->get('doctrine')->getManager(); + $manager->persist($camp); + $manager->flush(); + + static::createClient()->request( + 'PATCH', + '/camps/'.$camp->getId(), + [ + 'json' => [ + 'id' => 39, + ], + 'headers' => ['Content-Type' => 'application/merge-patch+json'], + ] + ); + + $this->assertResponseStatusCodeSame(400); + $this->assertJsonContains([ + 'detail' => 'Extra attributes are not allowed ("id" is unknown).', + ]); + } + + public function testIdIsNotWritable(): void + { + if ($this->isMongoDB()) { + $this->markTestSkipped(); + } + + $this->recreateSchema(self::getResources()); + + static::createClient()->request( + 'POST', + '/camps', + [ + 'json' => [ + 'id' => 39, + 'name' => 'New Camp', + ], + 'headers' => ['Content-Type' => 'application/json'], + ] + ); + + $this->assertResponseStatusCodeSame(400); + $this->assertJsonContains([ + 'detail' => 'Update is not allowed for this operation.', + ]); + } } diff --git a/tests/Hal/Serializer/ItemNormalizerTest.php b/tests/Hal/Serializer/ItemNormalizerTest.php deleted file mode 100644 index b45e6b40337..00000000000 --- a/tests/Hal/Serializer/ItemNormalizerTest.php +++ /dev/null @@ -1,429 +0,0 @@ - - * - * 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\Hal\Serializer; - -use ApiPlatform\Hal\Serializer\ItemNormalizer; -use ApiPlatform\Metadata\ApiProperty; -use ApiPlatform\Metadata\IriConverterInterface; -use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; -use ApiPlatform\Metadata\Property\PropertyNameCollection; -use ApiPlatform\Metadata\ResourceClassResolverInterface; -use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue5452\ActivableInterface; -use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue5452\Author; -use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue5452\Book; -use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue5452\Library; -use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue5452\TimestampableInterface; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MaxDepthDummy; -use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy; -use PHPUnit\Framework\TestCase; -use Prophecy\Argument; -use Prophecy\PhpUnit\ProphecyTrait; -use Symfony\Component\PropertyInfo\Type; -use Symfony\Component\Serializer\Exception\LogicException; -use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; -use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; -use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; -use Symfony\Component\Serializer\NameConverter\NameConverterInterface; -use Symfony\Component\Serializer\Normalizer\NormalizerInterface; -use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; -use Symfony\Component\Serializer\Serializer; -use Symfony\Component\Serializer\SerializerInterface; - -/** - * @author Kévin Dunglas - */ -class ItemNormalizerTest extends TestCase -{ - use ProphecyTrait; - - public function testDoesNotSupportDenormalization(): void - { - $this->expectException(LogicException::class); - $this->expectExceptionMessage('jsonhal is a read-only format.'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $nameConverter = $this->prophesize(NameConverterInterface::class); - - $normalizer = new ItemNormalizer( - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - $iriConverterProphecy->reveal(), - $resourceClassResolverProphecy->reveal(), - null, - $nameConverter->reveal() - ); - - $this->assertFalse($normalizer->supportsDenormalization('foo', ItemNormalizer::FORMAT)); - $normalizer->denormalize(['foo'], 'Foo'); - } - - #[\PHPUnit\Framework\Attributes\Group('legacy')] - public function testSupportsNormalization(): void - { - $std = new \stdClass(); - $dummy = new Dummy(); - $dummy->setDescription('hello'); - - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $resourceClassResolverProphecy->isResourceClass(\stdClass::class)->willReturn(false); - - $nameConverter = $this->prophesize(NameConverterInterface::class); - - $normalizer = new ItemNormalizer( - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - $iriConverterProphecy->reveal(), - $resourceClassResolverProphecy->reveal(), - null, - $nameConverter->reveal() - ); - - $this->assertTrue($normalizer->supportsNormalization($dummy, $normalizer::FORMAT)); - $this->assertFalse($normalizer->supportsNormalization($dummy, 'xml')); - $this->assertFalse($normalizer->supportsNormalization($std, $normalizer::FORMAT)); - $this->assertEmpty($normalizer->getSupportedTypes('xml')); - $this->assertSame(['object' => true], $normalizer->getSupportedTypes($normalizer::FORMAT)); - } - - public function testNormalize(): void - { - $relatedDummy = new RelatedDummy(); - $dummy = new Dummy(); - $dummy->setName('hello'); - $dummy->setRelatedDummy($relatedDummy); - - $propertyNameCollection = new PropertyNameCollection(['name', 'relatedDummy']); - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn($propertyNameCollection); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn( - (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true) - ); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn( - (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class)])->withDescription('')->withReadable(true)->withWritable(false)->withWritableLink(false) - ); - - $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - $iriConverterProphecy->getIriFromResource($dummy, Argument::cetera())->willReturn('/dummies/1'); - $iriConverterProphecy->getIriFromResource($relatedDummy, Argument::cetera())->willReturn('/related-dummies/2'); - - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true); - $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - $resourceClassResolverProphecy->getResourceClass($dummy, null)->willReturn(Dummy::class); - $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class); - $resourceClassResolverProphecy->getResourceClass($dummy, Dummy::class)->willReturn(Dummy::class); - $resourceClassResolverProphecy->getResourceClass($relatedDummy, RelatedDummy::class)->willReturn(RelatedDummy::class); - - $serializerProphecy = $this->prophesize(SerializerInterface::class); - $serializerProphecy->willImplement(NormalizerInterface::class); - $serializerProphecy->normalize('hello', null, Argument::type('array'))->willReturn('hello'); - - $nameConverter = $this->prophesize(NameConverterInterface::class); - $nameConverter->normalize('name', Argument::any(), Argument::any(), Argument::any())->willReturn('name'); - $nameConverter->normalize('relatedDummy', Argument::any(), Argument::any(), Argument::any())->willReturn('related_dummy'); - - $normalizer = new ItemNormalizer( - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - $iriConverterProphecy->reveal(), - $resourceClassResolverProphecy->reveal(), - null, - $nameConverter->reveal() - ); - $normalizer->setSerializer($serializerProphecy->reveal()); - - $expected = [ - '_links' => [ - 'self' => [ - 'href' => '/dummies/1', - ], - 'related_dummy' => [ - 'href' => '/related-dummies/2', - ], - ], - 'name' => 'hello', - ]; - $this->assertEquals($expected, $normalizer->normalize($dummy)); - } - - public function testNormalizeWithUnionIntersectTypes(): void - { - $author = new Author(id: 2, name: 'Isaac Asimov'); - $library = new Library(id: 3, name: 'Le Bâteau Livre'); - $book = new Book(); - $book->author = $author; - $book->library = $library; - - $propertyNameCollection = new PropertyNameCollection(['author', 'library']); - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Book::class, [])->willReturn($propertyNameCollection); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Book::class, 'author', [])->willReturn( - (new ApiProperty())->withBuiltinTypes([ - new Type(Type::BUILTIN_TYPE_OBJECT, false, ActivableInterface::class), - new Type(Type::BUILTIN_TYPE_OBJECT, false, TimestampableInterface::class), - ])->withReadable(true) - ); - $propertyMetadataFactoryProphecy->create(Book::class, 'library', [])->willReturn( - (new ApiProperty())->withBuiltinTypes([ - new Type(Type::BUILTIN_TYPE_OBJECT, false, ActivableInterface::class), - new Type(Type::BUILTIN_TYPE_OBJECT, false, TimestampableInterface::class), - ])->withReadable(true) - ); - - $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - $iriConverterProphecy->getIriFromResource($book, Argument::cetera())->willReturn('/books/1'); - - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $resourceClassResolverProphecy->isResourceClass(Book::class)->willReturn(true); - $resourceClassResolverProphecy->isResourceClass(ActivableInterface::class)->willReturn(false); - $resourceClassResolverProphecy->isResourceClass(TimestampableInterface::class)->willReturn(false); - $resourceClassResolverProphecy->getResourceClass($book, null)->willReturn(Book::class); - $resourceClassResolverProphecy->getResourceClass(null, Book::class)->willReturn(Book::class); - - $serializerProphecy = $this->prophesize(SerializerInterface::class); - $serializerProphecy->willImplement(NormalizerInterface::class); - - $nameConverter = $this->prophesize(NameConverterInterface::class); - $nameConverter->normalize('author', Argument::any(), Argument::any(), Argument::any())->willReturn('author'); - $nameConverter->normalize('library', Argument::any(), Argument::any(), Argument::any())->willReturn('library'); - - $normalizer = new ItemNormalizer( - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - $iriConverterProphecy->reveal(), - $resourceClassResolverProphecy->reveal(), - null, - $nameConverter->reveal() - ); - $normalizer->setSerializer($serializerProphecy->reveal()); - - $expected = [ - '_links' => [ - 'self' => [ - 'href' => '/books/1', - ], - ], - 'author' => null, - 'library' => null, - ]; - $this->assertEquals($expected, $normalizer->normalize($book)); - } - - public function testNormalizeWithoutCache(): void - { - $relatedDummy = new RelatedDummy(); - $dummy = new Dummy(); - $dummy->setName('hello'); - $dummy->setRelatedDummy($relatedDummy); - - $propertyNameCollection = new PropertyNameCollection(['name', 'relatedDummy']); - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn($propertyNameCollection); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn( - (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true) - ); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn( - (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class)])->withDescription('')->withReadable(true)->withWritable(false)->withReadableLink(false) - ); - - $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - $iriConverterProphecy->getIriFromResource($dummy, Argument::cetera())->willReturn('/dummies/1'); - $iriConverterProphecy->getIriFromResource($relatedDummy, Argument::cetera())->willReturn('/related-dummies/2'); - - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $resourceClassResolverProphecy->getResourceClass($dummy, null)->willReturn(Dummy::class); - $resourceClassResolverProphecy->getResourceClass($dummy, Dummy::class)->willReturn(Dummy::class); - $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class); - $resourceClassResolverProphecy->getResourceClass($relatedDummy, RelatedDummy::class)->willReturn(RelatedDummy::class); - $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true); - $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true); - - $serializerProphecy = $this->prophesize(SerializerInterface::class); - $serializerProphecy->willImplement(NormalizerInterface::class); - $serializerProphecy->normalize('hello', null, Argument::type('array'))->willReturn('hello'); - - $nameConverter = $this->prophesize(NameConverterInterface::class); - $nameConverter->normalize('name', Argument::any(), Argument::any(), Argument::any())->willReturn('name'); - $nameConverter->normalize('relatedDummy', Argument::any(), Argument::any(), Argument::any())->willReturn('related_dummy'); - - $normalizer = new ItemNormalizer( - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - $iriConverterProphecy->reveal(), - $resourceClassResolverProphecy->reveal(), - null, - $nameConverter->reveal() - ); - $normalizer->setSerializer($serializerProphecy->reveal()); - - $expected = [ - '_links' => [ - 'self' => [ - 'href' => '/dummies/1', - ], - 'related_dummy' => [ - 'href' => '/related-dummies/2', - ], - ], - 'name' => 'hello', - ]; - $this->assertEquals($expected, $normalizer->normalize($dummy, null, ['not_serializable' => function (): void {}])); - } - - public function testMaxDepth(): void - { - $setId = function (MaxDepthDummy $dummy, int $id): void { - $prop = new \ReflectionProperty($dummy, 'id'); - $prop->setAccessible(true); - $prop->setValue($dummy, $id); - }; - - $level1 = new MaxDepthDummy(); - $setId($level1, 1); - $level1->name = 'level 1'; - - $level2 = new MaxDepthDummy(); - $setId($level2, 2); - $level2->name = 'level 2'; - $level1->child = $level2; - - $level3 = new MaxDepthDummy(); - $setId($level3, 3); - $level3->name = 'level 3'; - $level2->child = $level3; - - $propertyNameCollection = new PropertyNameCollection(['id', 'name', 'child']); - $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); - $propertyNameCollectionFactoryProphecy->create(MaxDepthDummy::class, [])->willReturn($propertyNameCollection); - - $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(MaxDepthDummy::class, 'id', [])->willReturn( - (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)])->withDescription('')->withReadable(true) - ); - $propertyMetadataFactoryProphecy->create(MaxDepthDummy::class, 'name', [])->willReturn( - (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true) - ); - $propertyMetadataFactoryProphecy->create(MaxDepthDummy::class, 'child', [])->willReturn( - (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, false, MaxDepthDummy::class)])->withDescription('')->withReadable(true)->withWritable(false)->withReadableLink(true) - ); - - $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - $iriConverterProphecy->getIriFromResource($level1, Argument::cetera())->willReturn('/max_depth_dummies/1'); - $iriConverterProphecy->getIriFromResource($level2, Argument::cetera())->willReturn('/max_depth_dummies/2'); - $iriConverterProphecy->getIriFromResource($level3, Argument::cetera())->willReturn('/max_depth_dummies/3'); - - $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); - $resourceClassResolverProphecy->getResourceClass($level1, null)->willReturn(MaxDepthDummy::class); - $resourceClassResolverProphecy->getResourceClass($level1, MaxDepthDummy::class)->willReturn(MaxDepthDummy::class); - $resourceClassResolverProphecy->getResourceClass($level2, MaxDepthDummy::class)->willReturn(MaxDepthDummy::class); - $resourceClassResolverProphecy->getResourceClass($level3, MaxDepthDummy::class)->willReturn(MaxDepthDummy::class); - $resourceClassResolverProphecy->getResourceClass(null, MaxDepthDummy::class)->willReturn(MaxDepthDummy::class); - $resourceClassResolverProphecy->isResourceClass(MaxDepthDummy::class)->willReturn(true); - - $normalizer = new ItemNormalizer( - $propertyNameCollectionFactoryProphecy->reveal(), - $propertyMetadataFactoryProphecy->reveal(), - $iriConverterProphecy->reveal(), - $resourceClassResolverProphecy->reveal(), - null, - null, - new ClassMetadataFactory(class_exists(AttributeLoader::class) ? new AttributeLoader() : new AnnotationLoader()) - ); - $serializer = new Serializer([$normalizer]); - $normalizer->setSerializer($serializer); - - $expected = [ - '_links' => [ - 'self' => [ - 'href' => '/max_depth_dummies/1', - ], - 'child' => [ - 'href' => '/max_depth_dummies/2', - ], - ], - '_embedded' => [ - 'child' => [ - '_links' => [ - 'self' => [ - 'href' => '/max_depth_dummies/2', - ], - 'child' => [ - 'href' => '/max_depth_dummies/3', - ], - ], - '_embedded' => [ - 'child' => [ - '_links' => [ - 'self' => [ - 'href' => '/max_depth_dummies/3', - ], - ], - 'id' => 3, - 'name' => 'level 3', - ], - ], - 'id' => 2, - 'name' => 'level 2', - ], - ], - 'id' => 1, - 'name' => 'level 1', - ]; - - $this->assertEquals($expected, $normalizer->normalize($level1, ItemNormalizer::FORMAT)); - $this->assertEquals($expected, $normalizer->normalize($level1, ItemNormalizer::FORMAT, [ObjectNormalizer::ENABLE_MAX_DEPTH => false])); - - $expected = [ - '_links' => [ - 'self' => [ - 'href' => '/max_depth_dummies/1', - ], - 'child' => [ - 'href' => '/max_depth_dummies/2', - ], - ], - '_embedded' => [ - 'child' => [ - '_links' => [ - 'self' => [ - 'href' => '/max_depth_dummies/2', - ], - ], - 'id' => 2, - 'name' => 'level 2', - ], - ], - 'id' => 1, - 'name' => 'level 1', - ]; - - $this->assertEquals($expected, $normalizer->normalize($level1, ItemNormalizer::FORMAT, [ObjectNormalizer::ENABLE_MAX_DEPTH => true])); - } -} diff --git a/tests/JsonLd/Serializer/ItemNormalizerTest.php b/tests/JsonLd/Serializer/ItemNormalizerTest.php index 9f062bac54d..d765b85a2ed 100644 --- a/tests/JsonLd/Serializer/ItemNormalizerTest.php +++ b/tests/JsonLd/Serializer/ItemNormalizerTest.php @@ -58,7 +58,7 @@ public function testNormalize(): void $propertyMetadata = (new ApiProperty())->withReadable(true); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::any())->willReturn($propertyMetadata); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn($propertyMetadata); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); $iriConverterProphecy->getIriFromResource($dummy, UrlGeneratorInterface::ABS_PATH, null, Argument::any())->willReturn('/dummies/1988');