• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

api-platform / core / 19799380020

30 Nov 2025 01:09PM UTC coverage: 0.0%. Remained the same
19799380020

push

github

soyuka
Merge 4.2

0 of 306 new or added lines in 35 files covered. (0.0%)

22 existing lines in 15 files now uncovered.

0 of 57173 relevant lines covered (0.0%)

0.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/Serializer/Tests/AbstractItemNormalizerTest.php
1
<?php
2

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

12
declare(strict_types=1);
13

14
namespace ApiPlatform\Serializer\Tests;
15

16
use ApiPlatform\Metadata\ApiProperty;
17
use ApiPlatform\Metadata\ApiResource;
18
use ApiPlatform\Metadata\Exception\AccessDeniedException;
19
use ApiPlatform\Metadata\Exception\InvalidArgumentException;
20
use ApiPlatform\Metadata\Exception\ItemNotFoundException;
21
use ApiPlatform\Metadata\Get;
22
use ApiPlatform\Metadata\GetCollection;
23
use ApiPlatform\Metadata\IriConverterInterface;
24
use ApiPlatform\Metadata\Operations;
25
use ApiPlatform\Metadata\Patch;
26
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
27
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
28
use ApiPlatform\Metadata\Property\PropertyNameCollection;
29
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
30
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
31
use ApiPlatform\Metadata\ResourceAccessCheckerInterface;
32
use ApiPlatform\Metadata\ResourceClassResolverInterface;
33
use ApiPlatform\Metadata\UrlGeneratorInterface;
34
use ApiPlatform\Serializer\AbstractItemNormalizer;
35
use ApiPlatform\Serializer\Tests\Fixtures\ApiResource\DtoWithNullValue;
36
use ApiPlatform\Serializer\Tests\Fixtures\ApiResource\Dummy;
37
use ApiPlatform\Serializer\Tests\Fixtures\ApiResource\DummyTableInheritance;
38
use ApiPlatform\Serializer\Tests\Fixtures\ApiResource\DummyTableInheritanceChild;
39
use ApiPlatform\Serializer\Tests\Fixtures\ApiResource\DummyTableInheritanceRelated;
40
use ApiPlatform\Serializer\Tests\Fixtures\ApiResource\NonCloneableDummy;
41
use ApiPlatform\Serializer\Tests\Fixtures\ApiResource\PropertyCollectionIriOnly;
42
use ApiPlatform\Serializer\Tests\Fixtures\ApiResource\PropertyCollectionIriOnlyRelation;
43
use ApiPlatform\Serializer\Tests\Fixtures\ApiResource\RelatedDummy;
44
use ApiPlatform\Serializer\Tests\Fixtures\ApiResource\SecuredDummy;
45
use Doctrine\Common\Collections\ArrayCollection;
46
use PHPUnit\Framework\TestCase;
47
use Prophecy\Argument;
48
use Prophecy\PhpUnit\ProphecyTrait;
49
use Symfony\Component\PropertyAccess\PropertyAccessor;
50
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
51
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
52
use Symfony\Component\PropertyInfo\Type as LegacyType;
53
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
54
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
55
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
56
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
57
use Symfony\Component\Serializer\SerializerInterface;
58
use Symfony\Component\TypeInfo\Type;
59

60
/**
61
 * @author Amrouche Hamza <hamza.simperfit@gmail.com>
62
 * @author Kévin Dunglas <dunglas@gmail.com>
63
 */
64
class AbstractItemNormalizerTest extends TestCase
65
{
66
    use ProphecyTrait;
67

68
    #[\PHPUnit\Framework\Attributes\Group('legacy')]
69
    public function testSupportNormalizationAndSupportDenormalization(): void
70
    {
71
        $std = new \stdClass();
×
72
        $dummy = new Dummy();
×
73

74
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
75
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
76
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
77
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
78

79
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
80
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
81
        $resourceClassResolverProphecy->isResourceClass(\stdClass::class)->willReturn(false);
×
82

83
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
84

85
        $this->assertTrue($normalizer->supportsNormalization($dummy));
×
86
        $this->assertFalse($normalizer->supportsNormalization($std));
×
87
        $this->assertTrue($normalizer->supportsDenormalization($dummy, Dummy::class));
×
88
        $this->assertFalse($normalizer->supportsDenormalization($std, \stdClass::class));
×
89
        $this->assertFalse($normalizer->supportsNormalization([]));
×
90
        $this->assertSame(['object' => true], $normalizer->getSupportedTypes('any'));
×
91
    }
92

93
    public function testNormalize(): void
94
    {
95
        $relatedDummy = new RelatedDummy();
×
NEW
96
        $relatedDummy->setId(2);
×
97

98
        $dummy = new Dummy();
×
99
        $dummy->setName('foo');
×
100
        $dummy->setAlias('ignored');
×
101
        $dummy->setRelatedDummy($relatedDummy);
×
102
        $dummy->relatedDummies->add(new RelatedDummy());
×
103

104
        $relatedDummies = new ArrayCollection([$relatedDummy]);
×
105

106
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
107
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['name', 'alias', 'relatedDummy', 'relatedDummies']));
×
108

109
        // BC layer for api-platform/metadata < 4.1
110
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
111
            $relatedDummyType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class);
×
112
            $relatedDummiesType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $relatedDummyType);
×
113

114
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
115
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true));
×
116
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'alias', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true));
×
117
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withDescription('')->withReadable(true)->withWritable(false)->withReadableLink(false));
×
118
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(true)->withWritable(false)->withReadableLink(false));
×
119
        } else {
120
            $relatedDummyType = Type::object(RelatedDummy::class);
×
121
            $relatedDummiesType = Type::collection(Type::object(ArrayCollection::class), $relatedDummyType, Type::int());
×
122

123
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
124
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true));
×
125
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'alias', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true));
×
126
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType($relatedDummyType)->withDescription('')->withReadable(true)->withWritable(false)->withReadableLink(false));
×
127
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType($relatedDummiesType)->withReadable(true)->withWritable(false)->withReadableLink(false));
×
128
        }
129

130
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
131
        $iriConverterProphecy->getIriFromResource($dummy, Argument::cetera())->willReturn('/dummies/1');
×
132
        $iriConverterProphecy->getIriFromResource($relatedDummy, Argument::cetera())->willReturn('/dummies/2');
×
133

134
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
135
        $propertyAccessorProphecy->getValue($dummy, 'name')->willReturn('foo');
×
136
        $propertyAccessorProphecy->getValue($dummy, 'relatedDummy')->willReturn($relatedDummy);
×
137
        $propertyAccessorProphecy->getValue($dummy, 'relatedDummies')->willReturn($relatedDummies);
×
138

139
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
140
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
141
        $resourceClassResolverProphecy->getResourceClass($dummy, null)->willReturn(Dummy::class);
×
142
        $resourceClassResolverProphecy->getResourceClass($relatedDummy, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
143
        $resourceClassResolverProphecy->getResourceClass($relatedDummies, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
144
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
145
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
146

147
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
148
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
149
        $serializerProphecy->normalize('foo', null, Argument::type('array'))->willReturn('foo');
×
NEW
150
        $serializerProphecy->normalize('/dummies/2', null, Argument::type('array'))->willReturn('/dummies/2');
×
UNCOV
151
        $serializerProphecy->normalize(['/dummies/2'], null, Argument::type('array'))->willReturn(['/dummies/2']);
×
152

153
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
154
        $normalizer->setSerializer($serializerProphecy->reveal());
×
155

156
        $expected = [
×
157
            'name' => 'foo',
×
158
            'relatedDummy' => '/dummies/2',
×
159
            'relatedDummies' => ['/dummies/2'],
×
160
        ];
×
161
        $this->assertSame($expected, $normalizer->normalize($dummy, null, [
×
162
            'resources' => [],
×
163
            'ignored_attributes' => ['alias'],
×
164
        ]));
×
165
    }
166

167
    public function testNormalizeWithSecuredProperty(): void
168
    {
169
        $dummy = new SecuredDummy();
×
170
        $dummy->setTitle('myPublicTitle');
×
171
        $dummy->setAdminOnlyProperty('secret');
×
172

173
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
174
        $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['title', 'adminOnlyProperty']));
×
175

176
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
177

178
        // BC layer for api-platform/metadata < 4.1
179
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
180
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true));
×
181
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'adminOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withSecurity('is_granted(\'ROLE_ADMIN\')'));
×
182
        } else {
183
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true));
×
184
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'adminOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true)->withSecurity('is_granted(\'ROLE_ADMIN\')'));
×
185
        }
186

187
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
188
        $iriConverterProphecy->getIriFromResource($dummy, Argument::cetera())->willReturn('/secured_dummies/1');
×
189

190
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
191
        $propertyAccessorProphecy->getValue($dummy, 'title')->willReturn('myPublicTitle');
×
192
        $propertyAccessorProphecy->getValue($dummy, 'adminOnlyProperty')->willReturn('secret');
×
193

194
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
195
        $resourceClassResolverProphecy->getResourceClass($dummy, null)->willReturn(SecuredDummy::class);
×
196
        $resourceClassResolverProphecy->getResourceClass(null, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
197
        $resourceClassResolverProphecy->isResourceClass(SecuredDummy::class)->willReturn(true);
×
198

199
        $resourceAccessChecker = $this->prophesize(ResourceAccessCheckerInterface::class);
×
200
        $resourceAccessChecker->isGranted(
×
201
            SecuredDummy::class,
×
202
            'is_granted(\'ROLE_ADMIN\')',
×
203
            ['object' => $dummy, 'property' => 'adminOnlyProperty']
×
204
        )->willReturn(false);
×
205

206
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
207
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
208
        $serializerProphecy->normalize('myPublicTitle', null, Argument::type('array'))->willReturn('myPublicTitle');
×
209

210
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, $resourceAccessChecker->reveal()) extends AbstractItemNormalizer {};
×
211
        $normalizer->setSerializer($serializerProphecy->reveal());
×
212

213
        $expected = [
×
214
            'title' => 'myPublicTitle',
×
215
        ];
×
216
        $this->assertSame($expected, $normalizer->normalize($dummy, null, [
×
217
            'resources' => [],
×
218
        ]));
×
219
    }
220

221
    public function testNormalizePropertyAsIriWithUriTemplate(): void
222
    {
223
        $propertyCollectionIriOnlyRelation = new PropertyCollectionIriOnlyRelation();
×
224
        $propertyCollectionIriOnlyRelation->name = 'My Relation';
×
225

226
        $propertyCollectionIriOnly = new PropertyCollectionIriOnly();
×
227
        $propertyCollectionIriOnly->addPropertyCollectionIriOnlyRelation($propertyCollectionIriOnlyRelation);
×
228

229
        $collectionOperation = new GetCollection('/property-collection-relations');
×
230
        $getIterableOperation = new GetCollection('/parent/{parentId}/another-collection-operations');
×
231
        $getToOneOperation = new Get('/parent/{parentId}/another-collection-operations/{id}');
×
232

233
        $resourceRelationMetadataCollection = new ResourceMetadataCollection(PropertyCollectionIriOnlyRelation::class, [
×
234
            (new ApiResource())->withOperations(new Operations([$collectionOperation, $getIterableOperation, $getToOneOperation])),
×
235
        ]);
×
236

237
        $resourceMetadataCollectionFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
×
238
        $resourceMetadataCollectionFactoryProphecy->create(PropertyCollectionIriOnlyRelation::class)->willReturn($resourceRelationMetadataCollection);
×
239

240
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
241
        $propertyNameCollectionFactoryProphecy->create(PropertyCollectionIriOnly::class, Argument::type('array'))->willReturn(
×
242
            new PropertyNameCollection(['propertyCollectionIriOnlyRelation', 'iterableIri', 'toOneRelation'])
×
243
        );
×
244

245
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
246

247
        // BC layer for api-platform/metadata < 4.1
248
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
249
            $propertyMetadataFactoryProphecy->create(PropertyCollectionIriOnly::class, 'propertyCollectionIriOnlyRelation', Argument::type('array'))->willReturn(
×
250
                (new ApiProperty())->withReadable(true)->withUriTemplate('/property-collection-relations')->withBuiltinTypes([
×
251
                    new LegacyType('iterable', false, null, true, new LegacyType('int', false, null, false), new LegacyType('object', false, PropertyCollectionIriOnlyRelation::class, false)),
×
252
                ])
×
253
            );
×
254
            $propertyMetadataFactoryProphecy->create(PropertyCollectionIriOnly::class, 'iterableIri', Argument::type('array'))->willReturn(
×
255
                (new ApiProperty())->withReadable(true)->withUriTemplate('/parent/{parentId}/another-collection-operations')->withBuiltinTypes([
×
256
                    new LegacyType('iterable', false, null, true, new LegacyType('int', false, null, false), new LegacyType('object', false, PropertyCollectionIriOnlyRelation::class, false)),
×
257
                ])
×
258
            );
×
259
            $propertyMetadataFactoryProphecy->create(PropertyCollectionIriOnly::class, 'toOneRelation', Argument::type('array'))->willReturn(
×
260
                (new ApiProperty())->withReadable(true)->withUriTemplate('/parent/{parentId}/another-collection-operations/{id}')->withBuiltinTypes([
×
261
                    new LegacyType('object', false, PropertyCollectionIriOnlyRelation::class, false),
×
262
                ])
×
263
            );
×
264
        } else {
265
            $propertyMetadataFactoryProphecy->create(PropertyCollectionIriOnly::class, 'propertyCollectionIriOnlyRelation', Argument::type('array'))->willReturn(
×
266
                (new ApiProperty())->withReadable(true)->withUriTemplate('/property-collection-relations')->withNativeType(Type::list(Type::object(PropertyCollectionIriOnlyRelation::class)))
×
267
            );
×
268
            $propertyMetadataFactoryProphecy->create(PropertyCollectionIriOnly::class, 'iterableIri', Argument::type('array'))->willReturn(
×
269
                (new ApiProperty())->withReadable(true)->withUriTemplate('/parent/{parentId}/another-collection-operations')->withNativeType(Type::iterable(Type::object(PropertyCollectionIriOnlyRelation::class)))
×
270
            );
×
271
            $propertyMetadataFactoryProphecy->create(PropertyCollectionIriOnly::class, 'toOneRelation', Argument::type('array'))->willReturn(
×
272
                (new ApiProperty())->withReadable(true)->withUriTemplate('/parent/{parentId}/another-collection-operations/{id}')->withNativeType(Type::object(PropertyCollectionIriOnlyRelation::class))
×
273
            );
×
274
        }
275

276
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
277
        $iriConverterProphecy->getIriFromResource($propertyCollectionIriOnly, UrlGeneratorInterface::ABS_URL, null, Argument::any())->willReturn('/property-collection-relations', '/parent/42/another-collection-operations');
×
278
        $iriConverterProphecy->getIriFromResource($propertyCollectionIriOnly, UrlGeneratorInterface::ABS_PATH, Argument::type(GetCollection::class), Argument::any())->willReturn('/property-collection-relations', '/parent/42/another-collection-operations');
×
279
        $iriConverterProphecy->getIriFromResource($propertyCollectionIriOnly, UrlGeneratorInterface::ABS_PATH, Argument::type(Get::class), Argument::any())->willReturn('/parent/42/another-collection-operations/24');
×
280

281
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
282
        $propertyAccessorProphecy->getValue($propertyCollectionIriOnly, 'propertyCollectionIriOnlyRelation')->willReturn([$propertyCollectionIriOnlyRelation]);
×
283
        $propertyAccessorProphecy->getValue($propertyCollectionIriOnly, 'iterableIri')->willReturn($propertyCollectionIriOnlyRelation);
×
284
        $propertyAccessorProphecy->getValue($propertyCollectionIriOnly, 'toOneRelation')->willReturn($propertyCollectionIriOnlyRelation);
×
285

286
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
287

288
        $resourceClassResolverProphecy->isResourceClass(PropertyCollectionIriOnly::class)->willReturn(true);
×
289
        $resourceClassResolverProphecy->getResourceClass(null, PropertyCollectionIriOnly::class)->willReturn(PropertyCollectionIriOnly::class);
×
290

291
        $resourceClassResolverProphecy->isResourceClass(PropertyCollectionIriOnlyRelation::class)->willReturn(true);
×
292
        $resourceClassResolverProphecy->getResourceClass([$propertyCollectionIriOnlyRelation], PropertyCollectionIriOnlyRelation::class)->willReturn(PropertyCollectionIriOnlyRelation::class);
×
293

294
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), new PropertyAccessor(), // $propertyAccessorProphecy->reveal(),
×
295
            null, null, [], $resourceMetadataCollectionFactoryProphecy->reveal(), null, ) extends AbstractItemNormalizer {};
×
296

297
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
298
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
299
        // This is the fix: we must ensure normalize returns the IRIs as strings, not null
NEW
300
        $serializerProphecy->normalize('/property-collection-relations', Argument::cetera())->willReturn('/property-collection-relations');
×
NEW
301
        $serializerProphecy->normalize('/parent/42/another-collection-operations', Argument::cetera())->willReturn('/parent/42/another-collection-operations');
×
NEW
302
        $serializerProphecy->normalize('/parent/42/another-collection-operations/24', Argument::cetera())->willReturn('/parent/42/another-collection-operations/24');
×
303

UNCOV
304
        $normalizer->setSerializer($serializerProphecy->reveal());
×
305

306
        $expected = [
×
307
            'propertyCollectionIriOnlyRelation' => '/property-collection-relations',
×
308
            'iterableIri' => '/parent/42/another-collection-operations',
×
309
            'toOneRelation' => '/parent/42/another-collection-operations/24',
×
310
        ];
×
311

312
        $this->assertSame($expected, $normalizer->normalize($propertyCollectionIriOnly, 'jsonld', [
×
313
            'resources' => [],
×
314
            'root_operation' => new GetCollection('/property_collection_iri_onlies'),
×
315
        ]));
×
316
    }
317

318
    public function testDenormalizeWithSecuredPropertyAndThrowOnAccessDeniedExtraPropertyInAttributeMetaThrowsAccessDeniedExceptionWithSecurityMessage(): void
319
    {
320
        $data = [
×
321
            'title' => 'foo',
×
322
            'adminOnlyProperty' => 'secret',
×
323
        ];
×
324
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
325
        $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['title', 'adminOnlyProperty']));
×
326

327
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
328

329
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
330
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
331
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'adminOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withSecurityPostDenormalize('is_granted(\'ROLE_ADMIN\')')->withExtraProperties(['throw_on_access_denied' => true]));
×
332
        } else {
333
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
334
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'adminOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true)->withSecurityPostDenormalize('is_granted(\'ROLE_ADMIN\')')->withExtraProperties(['throw_on_access_denied' => true]));
×
335
        }
336

337
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
338

339
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
340

341
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
342
        $resourceClassResolverProphecy->getResourceClass(null, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
343
        $resourceClassResolverProphecy->isResourceClass(SecuredDummy::class)->willReturn(true);
×
344

345
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
346
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
347

348
        $resourceAccessChecker = $this->prophesize(ResourceAccessCheckerInterface::class);
×
349
        $resourceAccessChecker->isGranted(
×
350
            SecuredDummy::class,
×
351
            'is_granted(\'ROLE_ADMIN\')',
×
352
            Argument::that(function (array $context) {
×
353
                return \array_key_exists('property', $context)
×
354
                    && \array_key_exists('object', $context)
×
355
                    && \array_key_exists('previous_object', $context)
×
356
                    && 'adminOnlyProperty' === $context['property']
×
357
                    && null === $context['previous_object']
×
358
                    && $context['object'] instanceof SecuredDummy;
×
359
            })
×
360
        )->willReturn(false);
×
361

362
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, $resourceAccessChecker->reveal()) extends AbstractItemNormalizer {};
×
363
        $normalizer->setSerializer($serializerProphecy->reveal());
×
364

365
        $this->expectException(AccessDeniedException::class);
×
366
        $this->expectExceptionMessage('Access denied');
×
367

368
        $normalizer->denormalize($data, SecuredDummy::class);
×
369
    }
370

371
    public function testDenormalizeWithSecuredPropertyAndThrowOnAccessDeniedExtraPropertyInOperationAndSecurityMessageInOperationThrowsAccessDeniedExceptionWithSecurityMessage(): void
372
    {
373
        $data = [
×
374
            'title' => 'foo',
×
375
            'adminOnlyProperty' => 'secret',
×
376
        ];
×
377
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
378
        $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['title', 'adminOnlyProperty']));
×
379

380
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
381

382
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
383
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
384
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'adminOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withSecurityPostDenormalize('is_granted(\'ROLE_ADMIN\')'));
×
385
        } else {
386
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
387
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'adminOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true)->withSecurityPostDenormalize('is_granted(\'ROLE_ADMIN\')'));
×
388
        }
389

390
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
391

392
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
393

394
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
395
        $resourceClassResolverProphecy->getResourceClass(null, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
396
        $resourceClassResolverProphecy->isResourceClass(SecuredDummy::class)->willReturn(true);
×
397

398
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
399
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
400

401
        $resourceAccessChecker = $this->prophesize(ResourceAccessCheckerInterface::class);
×
402
        $resourceAccessChecker->isGranted(
×
403
            SecuredDummy::class,
×
404
            'is_granted(\'ROLE_ADMIN\')',
×
405
            Argument::that(function (array $context) {
×
406
                return \array_key_exists('property', $context)
×
407
                    && \array_key_exists('object', $context)
×
408
                    && \array_key_exists('previous_object', $context)
×
409
                    && 'adminOnlyProperty' === $context['property']
×
410
                    && null === $context['previous_object']
×
411
                    && $context['object'] instanceof SecuredDummy;
×
412
            })
×
413
        )->willReturn(false);
×
414

415
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, $resourceAccessChecker->reveal()) extends AbstractItemNormalizer {};
×
416
        $normalizer->setSerializer($serializerProphecy->reveal());
×
417

418
        $this->expectException(AccessDeniedException::class);
×
419
        $this->expectExceptionMessage('Custom access denied message');
×
420

421
        $operation = new Patch(securityMessage: 'Custom access denied message', extraProperties: ['throw_on_access_denied' => true]);
×
422

423
        $normalizer->denormalize($data, SecuredDummy::class, 'json', [
×
424
            'operation' => $operation,
×
425
        ]);
×
426
    }
427

428
    public function testDenormalizeWithSecuredPropertyAndThrowOnAccessDeniedExtraPropertyInOperationThrowsAccessDeniedException(): void
429
    {
430
        $data = [
×
431
            'title' => 'foo',
×
432
            'adminOnlyProperty' => 'secret',
×
433
        ];
×
434
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
435
        $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['title', 'adminOnlyProperty']));
×
436

437
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
438

439
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
440
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
441
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'adminOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withSecurityPostDenormalize('is_granted(\'ROLE_ADMIN\')'));
×
442
        } else {
443
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
444
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'adminOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true)->withSecurityPostDenormalize('is_granted(\'ROLE_ADMIN\')'));
×
445
        }
446

447
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
448

449
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
450

451
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
452
        $resourceClassResolverProphecy->getResourceClass(null, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
453
        $resourceClassResolverProphecy->isResourceClass(SecuredDummy::class)->willReturn(true);
×
454

455
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
456
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
457

458
        $resourceAccessChecker = $this->prophesize(ResourceAccessCheckerInterface::class);
×
459
        $resourceAccessChecker->isGranted(
×
460
            SecuredDummy::class,
×
461
            'is_granted(\'ROLE_ADMIN\')',
×
462
            Argument::that(function (array $context) {
×
463
                return \array_key_exists('property', $context)
×
464
                    && \array_key_exists('object', $context)
×
465
                    && \array_key_exists('previous_object', $context)
×
466
                    && 'adminOnlyProperty' === $context['property']
×
467
                    && null === $context['previous_object']
×
468
                    && $context['object'] instanceof SecuredDummy;
×
469
            })
×
470
        )->willReturn(false);
×
471

472
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, $resourceAccessChecker->reveal()) extends AbstractItemNormalizer {};
×
473
        $normalizer->setSerializer($serializerProphecy->reveal());
×
474

475
        $this->expectException(AccessDeniedException::class);
×
476
        $this->expectExceptionMessage('Access denied');
×
477

478
        $operation = new Patch(extraProperties: ['throw_on_access_denied' => true]);
×
479

480
        $normalizer->denormalize($data, SecuredDummy::class, 'json', [
×
481
            'operation' => $operation,
×
482
        ]);
×
483
    }
484

485
    public function testDenormalizeWithSecuredProperty(): void
486
    {
487
        $data = [
×
488
            'title' => 'foo',
×
489
            'adminOnlyProperty' => 'secret',
×
490
        ];
×
491

492
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
493
        $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['title', 'adminOnlyProperty']));
×
494

495
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
496

497
        // BC layer for api-platform/metadata < 4.1
498
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
499
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
500
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'adminOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withSecurity('is_granted(\'ROLE_ADMIN\')'));
×
501
        } else {
502
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
503
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'adminOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true)->withSecurity('is_granted(\'ROLE_ADMIN\')'));
×
504
        }
505

506
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
507

508
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
509

510
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
511
        $resourceClassResolverProphecy->getResourceClass(null, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
512
        $resourceClassResolverProphecy->isResourceClass(SecuredDummy::class)->willReturn(true);
×
513

514
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
515
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
516

517
        $resourceAccessChecker = $this->prophesize(ResourceAccessCheckerInterface::class);
×
518
        $resourceAccessChecker->isGranted(
×
519
            SecuredDummy::class,
×
520
            'is_granted(\'ROLE_ADMIN\')',
×
521
            ['object' => null, 'property' => 'adminOnlyProperty']
×
522
        )->willReturn(false);
×
523

524
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, $resourceAccessChecker->reveal()) extends AbstractItemNormalizer {};
×
525
        $normalizer->setSerializer($serializerProphecy->reveal());
×
526

527
        $actual = $normalizer->denormalize($data, SecuredDummy::class);
×
528

529
        $this->assertInstanceOf(SecuredDummy::class, $actual);
×
530

531
        $propertyAccessorProphecy->setValue($actual, 'title', 'foo')->shouldHaveBeenCalled();
×
532
        $propertyAccessorProphecy->setValue($actual, 'adminOnlyProperty', 'secret')->shouldNotHaveBeenCalled();
×
533
    }
534

535
    public function testDenormalizeCreateWithDeniedPostDenormalizeSecuredProperty(): void
536
    {
537
        $data = [
×
538
            'title' => 'foo',
×
539
            'ownerOnlyProperty' => 'should not be set',
×
540
        ];
×
541

542
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
543
        $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['title', 'ownerOnlyProperty']));
×
544

545
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
546

547
        // BC layer for api-platform/metadata < 4.1
548
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
549
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
550
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withWritable(true)->withSecurityPostDenormalize('false')->withDefault(''));
×
551
        } else {
552
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
553
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true)->withWritable(true)->withSecurityPostDenormalize('false')->withDefault(''));
×
554
        }
555

556
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
557

558
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
559

560
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
561
        $resourceClassResolverProphecy->getResourceClass(null, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
562
        $resourceClassResolverProphecy->isResourceClass(SecuredDummy::class)->willReturn(true);
×
563

564
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
565
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
566

567
        $resourceAccessChecker = $this->prophesize(ResourceAccessCheckerInterface::class);
×
568
        $resourceAccessChecker->isGranted(
×
569
            SecuredDummy::class,
×
570
            'false',
×
571
            Argument::any()
×
572
        )->willReturn(false);
×
573

574
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, $resourceAccessChecker->reveal()) extends AbstractItemNormalizer {};
×
575
        $normalizer->setSerializer($serializerProphecy->reveal());
×
576

577
        $actual = $normalizer->denormalize($data, SecuredDummy::class);
×
578

579
        $this->assertInstanceOf(SecuredDummy::class, $actual);
×
580

581
        $propertyAccessorProphecy->setValue($actual, 'title', 'foo')->shouldHaveBeenCalled();
×
582
        $propertyAccessorProphecy->setValue($actual, 'ownerOnlyProperty', 'should not be set')->shouldHaveBeenCalled();
×
583
        $propertyAccessorProphecy->setValue($actual, 'ownerOnlyProperty', '')->shouldHaveBeenCalled();
×
584
    }
585

586
    public function testDenormalizeUpdateWithSecuredProperty(): void
587
    {
588
        $dummy = new SecuredDummy();
×
589

590
        $data = [
×
591
            'title' => 'foo',
×
592
            'ownerOnlyProperty' => 'secret',
×
593
        ];
×
594

595
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
596
        $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['title', 'ownerOnlyProperty']));
×
597

598
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
599

600
        // BC layer for api-platform/metadata < 4.1
601
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
602
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
603
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withWritable(true)->withSecurity('true'));
×
604
        } else {
605
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
606
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true)->withWritable(true)->withSecurity('true'));
×
607
        }
608

609
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
610

611
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
612

613
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
614
        $resourceClassResolverProphecy->getResourceClass($dummy, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
615
        $resourceClassResolverProphecy->getResourceClass(null, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
616
        $resourceClassResolverProphecy->isResourceClass(SecuredDummy::class)->willReturn(true);
×
617

618
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
619
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
620

621
        $resourceAccessChecker = $this->prophesize(ResourceAccessCheckerInterface::class);
×
622
        $resourceAccessChecker->isGranted(
×
623
            SecuredDummy::class,
×
624
            'true',
×
625
            ['object' => null, 'property' => 'ownerOnlyProperty']
×
626
        )->willReturn(true);
×
627
        $resourceAccessChecker->isGranted(
×
628
            SecuredDummy::class,
×
629
            'true',
×
630
            ['object' => $dummy, 'property' => 'ownerOnlyProperty']
×
631
        )->willReturn(true);
×
632

633
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, $resourceAccessChecker->reveal()) extends AbstractItemNormalizer {};
×
634
        $normalizer->setSerializer($serializerProphecy->reveal());
×
635

636
        $context = [AbstractItemNormalizer::OBJECT_TO_POPULATE => $dummy];
×
637
        $actual = $normalizer->denormalize($data, SecuredDummy::class, null, $context);
×
638

639
        $this->assertInstanceOf(SecuredDummy::class, $actual);
×
640

641
        $propertyAccessorProphecy->setValue($actual, 'title', 'foo')->shouldHaveBeenCalled();
×
642
        $propertyAccessorProphecy->setValue($actual, 'ownerOnlyProperty', 'secret')->shouldHaveBeenCalled();
×
643
    }
644

645
    public function testDenormalizeUpdateWithDeniedSecuredProperty(): void
646
    {
647
        $dummy = new SecuredDummy();
×
648
        $dummy->setOwnerOnlyProperty('secret');
×
649

650
        $data = [
×
651
            'title' => 'foo',
×
652
            'ownerOnlyProperty' => 'should not be set',
×
653
        ];
×
654

655
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
656
        $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['title', 'ownerOnlyProperty']));
×
657

658
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
659

660
        // BC layer for api-platform/metadata < 4.1
661
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
662
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
663
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withWritable(true)->withSecurity('false'));
×
664
        } else {
665
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
666
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true)->withWritable(true)->withSecurity('false'));
×
667
        }
668

669
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
670

671
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
672

673
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
674
        $resourceClassResolverProphecy->getResourceClass(null, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
675
        $resourceClassResolverProphecy->getResourceClass($dummy, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
676
        $resourceClassResolverProphecy->isResourceClass(SecuredDummy::class)->willReturn(true);
×
677

678
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
679
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
680

681
        $resourceAccessChecker = $this->prophesize(ResourceAccessCheckerInterface::class);
×
682
        $resourceAccessChecker->isGranted(
×
683
            SecuredDummy::class,
×
684
            'false',
×
685
            ['object' => null, 'property' => 'ownerOnlyProperty']
×
686
        )->willReturn(false);
×
687
        $resourceAccessChecker->isGranted(
×
688
            SecuredDummy::class,
×
689
            'false',
×
690
            ['object' => $dummy, 'property' => 'ownerOnlyProperty']
×
691
        )->willReturn(false);
×
692

693
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, $resourceAccessChecker->reveal()) extends AbstractItemNormalizer {};
×
694
        $normalizer->setSerializer($serializerProphecy->reveal());
×
695

696
        $context = [AbstractItemNormalizer::OBJECT_TO_POPULATE => $dummy];
×
697
        $actual = $normalizer->denormalize($data, SecuredDummy::class, null, $context);
×
698

699
        $this->assertInstanceOf(SecuredDummy::class, $actual);
×
700

701
        $propertyAccessorProphecy->setValue($actual, 'title', 'foo')->shouldHaveBeenCalled();
×
702
        $propertyAccessorProphecy->setValue($actual, 'ownerOnlyProperty', 'should not be set')->shouldNotHaveBeenCalled();
×
703
    }
704

705
    public function testDenormalizeUpdateWithDeniedPostDenormalizeSecuredProperty(): void
706
    {
707
        $dummy = new SecuredDummy();
×
708
        $dummy->setOwnerOnlyProperty('secret');
×
709

710
        $data = [
×
711
            'title' => 'foo',
×
712
            'ownerOnlyProperty' => 'should not be set',
×
713
        ];
×
714

715
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
716
        $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['title', 'ownerOnlyProperty']));
×
717

718
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
719

720
        // BC layer for api-platform/metadata < 4.1
721
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
722
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
723
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withWritable(true)->withSecurityPostDenormalize('false'));
×
724
        } else {
725
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
726
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true)->withWritable(true)->withSecurityPostDenormalize('false'));
×
727
        }
728

729
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
730

731
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
732
        $propertyAccessorProphecy->getValue($dummy, 'ownerOnlyProperty')->willReturn('secret');
×
733

734
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
735
        $resourceClassResolverProphecy->getResourceClass($dummy, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
736
        $resourceClassResolverProphecy->getResourceClass(null, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
737
        $resourceClassResolverProphecy->isResourceClass(SecuredDummy::class)->willReturn(true);
×
738

739
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
740
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
741

742
        $resourceAccessChecker = $this->prophesize(ResourceAccessCheckerInterface::class);
×
743
        $resourceAccessChecker->isGranted(
×
744
            SecuredDummy::class,
×
745
            'false',
×
746
            ['object' => $dummy, 'previous_object' => $dummy, 'property' => 'ownerOnlyProperty']
×
747
        )->willReturn(false);
×
748

749
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, $resourceAccessChecker->reveal()) extends AbstractItemNormalizer {};
×
750
        $normalizer->setSerializer($serializerProphecy->reveal());
×
751

752
        $context = [AbstractItemNormalizer::OBJECT_TO_POPULATE => $dummy];
×
753
        $actual = $normalizer->denormalize($data, SecuredDummy::class, null, $context);
×
754

755
        $this->assertInstanceOf(SecuredDummy::class, $actual);
×
756

757
        $propertyAccessorProphecy->setValue($actual, 'title', 'foo')->shouldHaveBeenCalled();
×
758
        $propertyAccessorProphecy->setValue($actual, 'ownerOnlyProperty', 'should not be set')->shouldHaveBeenCalled();
×
759
        $propertyAccessorProphecy->setValue($actual, 'ownerOnlyProperty', 'secret')->shouldHaveBeenCalled();
×
760
    }
761

762
    public function testNormalizeReadableLinks(): void
763
    {
764
        $relatedDummy = new RelatedDummy();
×
765

766
        $dummy = new Dummy();
×
767
        $dummy->setRelatedDummy($relatedDummy);
×
768
        $dummy->relatedDummies->add(new RelatedDummy());
×
769

770
        $relatedDummies = new ArrayCollection([$relatedDummy]);
×
771

772
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
773
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['relatedDummy', 'relatedDummies']));
×
774

775
        // BC layer for api-platform/metadata < 4.1
776
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
777
            $relatedDummyType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class);
×
778
            $relatedDummiesType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $relatedDummyType);
×
779

780
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
781
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withReadable(true)->withWritable(false)->withReadableLink(true));
×
782
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(true)->withWritable(false)->withReadableLink(true));
×
783
        } else {
784
            $relatedDummyType = Type::object(RelatedDummy::class);
×
785
            $relatedDummiesType = Type::collection(Type::object(ArrayCollection::class), $relatedDummyType, Type::int());
×
786

787
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
788
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType($relatedDummyType)->withReadable(true)->withWritable(false)->withReadableLink(true));
×
789
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType($relatedDummiesType)->withReadable(true)->withWritable(false)->withReadableLink(true));
×
790
        }
791

792
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
793
        $iriConverterProphecy->getIriFromResource($dummy, Argument::cetera())->willReturn('/dummies/1');
×
794

795
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
796
        $propertyAccessorProphecy->getValue($dummy, 'relatedDummy')->willReturn($relatedDummy);
×
797
        $propertyAccessorProphecy->getValue($dummy, 'relatedDummies')->willReturn($relatedDummies);
×
798

799
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
800
        $resourceClassResolverProphecy->getResourceClass($dummy, null)->willReturn(Dummy::class);
×
801
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
802
        $resourceClassResolverProphecy->getResourceClass($relatedDummy, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
803
        $resourceClassResolverProphecy->getResourceClass($relatedDummies, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
804
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
805
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
806

807
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
808
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
809
        $relatedDummyChildContext = Argument::allOf(
×
810
            Argument::type('array'),
×
811
            Argument::withEntry('resource_class', RelatedDummy::class),
×
812
            Argument::not(Argument::withKey('iri')),
×
813
            Argument::not(Argument::withKey('force_resource_class'))
×
814
        );
×
815
        $serializerProphecy->normalize($relatedDummy, null, $relatedDummyChildContext)->willReturn(['foo' => 'hello']);
×
816
        $serializerProphecy->normalize(['foo' => 'hello'], null, Argument::type('array'))->willReturn(['foo' => 'hello']);
×
817
        $serializerProphecy->normalize([['foo' => 'hello']], null, Argument::type('array'))->willReturn([['foo' => 'hello']]);
×
818

819
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
820
        $normalizer->setSerializer($serializerProphecy->reveal());
×
821

822
        $expected = [
×
823
            'relatedDummy' => ['foo' => 'hello'],
×
824
            'relatedDummies' => [['foo' => 'hello']],
×
825
        ];
×
826
        $this->assertSame($expected, $normalizer->normalize($dummy, null, [
×
827
            'resources' => [],
×
828
            'force_resource_class' => Dummy::class,
×
829
        ]));
×
830
    }
831

832
    public function testNormalizePolymorphicRelations(): void
833
    {
834
        $concreteDummy = new DummyTableInheritanceChild();
×
835

836
        $dummy = new DummyTableInheritanceRelated();
×
837
        $dummy->addChild($concreteDummy);
×
838

839
        $abstractDummies = new ArrayCollection([$concreteDummy]);
×
840

841
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
842
        $propertyNameCollectionFactoryProphecy->create(DummyTableInheritanceRelated::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['children']));
×
843

844
        // BC layer for api-platform/metadata < 4.1
845
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
846
            $abstractDummyType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, DummyTableInheritance::class);
×
847
            $abstractDummiesType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $abstractDummyType);
×
848

849
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
850
            $propertyMetadataFactoryProphecy->create(DummyTableInheritanceRelated::class, 'children', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$abstractDummiesType])->withReadable(true)->withWritable(false)->withReadableLink(true));
×
851
        } else {
852
            $abstractDummyType = Type::object(DummyTableInheritance::class);
×
853
            $abstractDummiesType = Type::collection(Type::object(ArrayCollection::class), $abstractDummyType, Type::int());
×
854

855
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
856
            $propertyMetadataFactoryProphecy->create(DummyTableInheritanceRelated::class, 'children', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType($abstractDummiesType)->withReadable(true)->withWritable(false)->withReadableLink(true));
×
857
        }
858

859
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
860
        $iriConverterProphecy->getIriFromResource($dummy, Argument::cetera())->willReturn('/dummies/1');
×
861

862
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
863
        $propertyAccessorProphecy->getValue($dummy, 'children')->willReturn($abstractDummies);
×
864

865
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
866
        $resourceClassResolverProphecy->getResourceClass($dummy, null)->willReturn(DummyTableInheritanceRelated::class);
×
867
        $resourceClassResolverProphecy->getResourceClass(null, DummyTableInheritanceRelated::class)->willReturn(DummyTableInheritanceRelated::class);
×
868
        $resourceClassResolverProphecy->getResourceClass($concreteDummy, DummyTableInheritance::class)->willReturn(DummyTableInheritanceChild::class);
×
869
        $resourceClassResolverProphecy->getResourceClass($abstractDummies, DummyTableInheritance::class)->willReturn(DummyTableInheritance::class);
×
870
        $resourceClassResolverProphecy->isResourceClass(DummyTableInheritanceRelated::class)->willReturn(true);
×
871
        $resourceClassResolverProphecy->isResourceClass(DummyTableInheritance::class)->willReturn(true);
×
872

873
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
874
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
875
        $concreteDummyChildContext = Argument::allOf(
×
876
            Argument::type('array'),
×
877
            Argument::not(Argument::withKey('iri'))
×
878
        );
×
879
        $serializerProphecy->normalize($concreteDummy, null, $concreteDummyChildContext)->willReturn(['foo' => 'concrete']);
×
880
        $serializerProphecy->normalize([['foo' => 'concrete']], null, Argument::type('array'))->willReturn([['foo' => 'concrete']]);
×
881

882
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
883
        $normalizer->setSerializer($serializerProphecy->reveal());
×
884

885
        $expected = [
×
886
            'children' => [['foo' => 'concrete']],
×
887
        ];
×
888
        $this->assertSame($expected, $normalizer->normalize($dummy, null, [
×
889
            'resources' => [],
×
890
        ]));
×
891
    }
892

893
    public function testDenormalize(): void
894
    {
895
        $data = [
×
896
            'name' => 'foo',
×
897
            'relatedDummy' => '/dummies/1',
×
898
            'relatedDummies' => ['/dummies/2'],
×
899
        ];
×
900

901
        $relatedDummy1 = new RelatedDummy();
×
902
        $relatedDummy2 = new RelatedDummy();
×
903

904
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
905
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['name', 'relatedDummy', 'relatedDummies']));
×
906

907
        // BC layer for api-platform/metadata < 4.1
908
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
909
            $relatedDummyType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class);
×
910
            $relatedDummiesType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $relatedDummyType);
×
911

912
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
913
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
914

915
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
916
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
917
        } else {
918
            $relatedDummyType = Type::object(RelatedDummy::class);
×
919
            $relatedDummiesType = Type::collection(Type::object(ArrayCollection::class), $relatedDummyType, Type::int());
×
920

921
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
922
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
923

924
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType($relatedDummyType)->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
925
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType($relatedDummiesType)->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
926
        }
927

928
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
929
        $iriConverterProphecy->getResourceFromIri('/dummies/1', Argument::type('array'))->willReturn($relatedDummy1);
×
930
        $iriConverterProphecy->getResourceFromIri('/dummies/2', Argument::type('array'))->willReturn($relatedDummy2);
×
931

932
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
933

934
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
935
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
936
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
937
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
938
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
939

940
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
941
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
942

943
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
944
        $normalizer->setSerializer($serializerProphecy->reveal());
×
945

946
        $actual = $normalizer->denormalize($data, Dummy::class);
×
947

948
        $this->assertInstanceOf(Dummy::class, $actual);
×
949

950
        $propertyAccessorProphecy->setValue($actual, 'name', 'foo')->shouldHaveBeenCalled();
×
951
        $propertyAccessorProphecy->setValue($actual, 'relatedDummy', $relatedDummy1)->shouldHaveBeenCalled();
×
952
        $propertyAccessorProphecy->setValue($actual, 'relatedDummies', [$relatedDummy2])->shouldHaveBeenCalled();
×
953
    }
954

955
    public function testCanDenormalizeInputClassWithDifferentFieldsThanResourceClass(): void
956
    {
957
        $this->markTestSkipped('TODO: check why this test has been commented');
×
958

959
        // $data = [
960
        //     'dummyName' => 'Dummy Name',
961
        // ];
962
        //
963
        // $context = [
964
        //     'resource_class' => DummyForAdditionalFields::class,
965
        //     'input' => ['class' => DummyForAdditionalFieldsInput::class],
966
        //     'output' => ['class' => DummyForAdditionalFields::class],
967
        // ];
968
        // $augmentedContext = $context + ['api_denormalize' => true];
969
        //
970
        // $preHydratedDummy = new DummyForAdditionalFieldsInput('Name Dummy');
971
        // $cleanedContext = array_diff_key($augmentedContext, [
972
        //     'input' => null,
973
        //     'resource_class' => null,
974
        // ]);
975
        // $cleanedContextWithObjectToPopulate = array_merge($cleanedContext, [
976
        //     AbstractObjectNormalizer::OBJECT_TO_POPULATE => $preHydratedDummy,
977
        //     AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE => true,
978
        // ]);
979
        //
980
        // $dummyInputDto = new DummyForAdditionalFieldsInput('Dummy Name');
981
        // $dummy = new DummyForAdditionalFields('Dummy Name', 'name-dummy');
982
        //
983
        // $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
984
        //
985
        // $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
986
        //
987
        // $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
988
        //
989
        // $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
990
        // $resourceClassResolverProphecy->getResourceClass(null, DummyForAdditionalFields::class)->willReturn(DummyForAdditionalFields::class);
991
        //
992
        // $inputDataTransformerProphecy = $this->prophesize(DataTransformerInitializerInterface::class);
993
        // $inputDataTransformerProphecy->willImplement(DataTransformerInitializerInterface::class);
994
        // $inputDataTransformerProphecy->initialize(DummyForAdditionalFieldsInput::class, $cleanedContext)->willReturn($preHydratedDummy);
995
        // $inputDataTransformerProphecy->supportsTransformation($data, DummyForAdditionalFields::class, $augmentedContext)->willReturn(true);
996
        // $inputDataTransformerProphecy->transform($dummyInputDto, DummyForAdditionalFields::class, $augmentedContext)->willReturn($dummy);
997
        //
998
        // $serializerProphecy = $this->prophesize(SerializerInterface::class);
999
        // $serializerProphecy->willImplement(DenormalizerInterface::class);
1000
        // $serializerProphecy->denormalize($data, DummyForAdditionalFieldsInput::class, 'json', $cleanedContextWithObjectToPopulate)->willReturn($dummyInputDto);
1001
        //
1002
        // $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), null, null, null, [], null, null) extends AbstractItemNormalizer {
1003
        // };
1004
        // $normalizer->setSerializer($serializerProphecy->reveal());
1005
        //
1006
        // $actual = $normalizer->denormalize($data, DummyForAdditionalFields::class, 'json', $context);
1007
        //
1008
        // $this->assertInstanceOf(DummyForAdditionalFields::class, $actual);
1009
        // $this->assertSame('Dummy Name', $actual->getName());
1010
    }
1011

1012
    public function testDenormalizeWritableLinks(): void
1013
    {
1014
        $data = [
×
1015
            'name' => 'foo',
×
1016
            'relatedDummy' => ['foo' => 'bar'],
×
1017
            'relatedDummies' => [['bar' => 'baz']],
×
1018
            'relatedDummiesWithUnionTypes' => [0 => ['bar' => 'qux'], 1 => ['bar' => 'quux']],
×
1019
        ];
×
1020

1021
        $relatedDummy1 = new RelatedDummy();
×
1022
        $relatedDummy2 = new RelatedDummy();
×
1023
        $relatedDummy3 = new RelatedDummy();
×
1024
        $relatedDummy4 = new RelatedDummy();
×
1025

1026
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1027
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['name', 'relatedDummy', 'relatedDummies', 'relatedDummiesWithUnionTypes']));
×
1028

1029
        // BC layer for api-platform/metadata < 4.1
1030
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1031
            $relatedDummyType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class);
×
1032
            $relatedDummiesType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $relatedDummyType);
×
1033
            $relatedDummiesWithUnionTypesIntType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $relatedDummyType);
×
1034
            $relatedDummiesWithUnionTypesFloatType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT), $relatedDummyType);
×
1035

1036
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1037
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
1038
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
1039
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
1040
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummiesWithUnionTypes', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesWithUnionTypesIntType, $relatedDummiesWithUnionTypesFloatType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
1041
        } else {
1042
            $relatedDummyType = Type::object(RelatedDummy::class);
×
1043
            $relatedDummiesType = Type::collection(Type::object(ArrayCollection::class), $relatedDummyType, Type::int());
×
1044
            $relatedDummiesWithUnionTypesIntType = Type::collection(Type::object(ArrayCollection::class), $relatedDummyType, Type::int());
×
1045
            $relatedDummiesWithUnionTypesFloatType = Type::collection(Type::object(ArrayCollection::class), $relatedDummyType, Type::float());
×
1046

1047
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1048
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
1049
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType($relatedDummyType)->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
1050
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType($relatedDummiesType)->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
1051
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummiesWithUnionTypes', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::union($relatedDummiesWithUnionTypesIntType, $relatedDummiesWithUnionTypesFloatType))->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
1052
        }
1053

1054
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1055

1056
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1057

1058
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1059
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1060
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
1061
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1062
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
1063
        $resourceClassResolverProphecy->isResourceClass(ArrayCollection::class)->willReturn(false);
×
1064

1065
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1066
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1067
        $serializerProphecy->denormalize(['foo' => 'bar'], RelatedDummy::class, null, Argument::type('array'))->willReturn($relatedDummy1);
×
1068
        $serializerProphecy->denormalize(['bar' => 'baz'], RelatedDummy::class, null, Argument::type('array'))->willReturn($relatedDummy2);
×
1069
        $serializerProphecy->denormalize(['bar' => 'qux'], RelatedDummy::class, null, Argument::type('array'))->willReturn($relatedDummy3);
×
1070
        $serializerProphecy->denormalize(['bar' => 'quux'], RelatedDummy::class, null, Argument::type('array'))->willReturn($relatedDummy4);
×
1071

1072
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
1073
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1074

1075
        $actual = $normalizer->denormalize($data, Dummy::class);
×
1076

1077
        $this->assertInstanceOf(Dummy::class, $actual);
×
1078

1079
        $propertyAccessorProphecy->setValue($actual, 'name', 'foo')->shouldHaveBeenCalled();
×
1080
        $propertyAccessorProphecy->setValue($actual, 'relatedDummy', $relatedDummy1)->shouldHaveBeenCalled();
×
1081
        $propertyAccessorProphecy->setValue($actual, 'relatedDummies', [$relatedDummy2])->shouldHaveBeenCalled();
×
1082
        $propertyAccessorProphecy->setValue($actual, 'relatedDummiesWithUnionTypes', [0 => $relatedDummy3, 1 => $relatedDummy4])->shouldHaveBeenCalled();
×
1083
    }
1084

1085
    public function testDenormalizeRelationNotFoundReturnsNull(): void
1086
    {
1087
        $data = [
×
1088
            'relatedDummy' => '/dummies/not_found',
×
1089
        ];
×
1090

1091
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1092
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['relatedDummy']));
×
1093

1094
        // BC layer for api-platform/metadata < 4.1
1095
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1096
            $relatedDummyType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class);
×
1097

1098
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1099
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1100
        } else {
1101
            $relatedDummyType = Type::object(RelatedDummy::class);
×
1102

1103
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1104
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType($relatedDummyType)->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1105
        }
1106

1107
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1108
        $iriConverterProphecy->getResourceFromIri('/dummies/not_found', Argument::type('array'))->willThrow(new ItemNotFoundException());
×
1109

1110
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1111

1112
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1113
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1114
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
1115
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
1116
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1117

1118
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1119
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1120

1121
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
1122
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1123

1124
        $actual = $normalizer->denormalize($data, Dummy::class, null, [
×
1125
            'denormalize_throw_on_relation_not_found' => false,
×
1126
            'not_normalizable_value_exceptions' => [],
×
1127
        ]);
×
1128

1129
        $this->assertInstanceOf(Dummy::class, $actual);
×
1130
        $propertyAccessorProphecy->setValue($actual, 'relatedDummy', null)->shouldHaveBeenCalled();
×
1131
    }
1132

1133
    public function testBadRelationType(): void
1134
    {
1135
        $this->expectException(NotNormalizableValueException::class);
×
1136
        $this->expectExceptionMessage('The type of the "relatedDummy" attribute must be "array" (nested document) or "string" (IRI), "integer" given.');
×
1137

1138
        $data = [
×
1139
            'relatedDummy' => 22,
×
1140
        ];
×
1141

1142
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1143
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['relatedDummy']));
×
1144

1145
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1146

1147
        // BC layer for api-platform/metadata < 4.1
1148
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1149
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn(
×
1150
                (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class)])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)
×
1151
            );
×
1152
        } else {
1153
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn(
×
1154
                (new ApiProperty())->withNativeType(Type::object(RelatedDummy::class))->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)
×
1155
            );
×
1156
        }
1157

1158
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1159

1160
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1161
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1162
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
1163
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1164
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
1165

1166
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1167

1168
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1169
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1170

1171
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
1172
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1173

1174
        $normalizer->denormalize($data, Dummy::class);
×
1175
    }
1176

1177
    public function testBadRelationTypeWithExceptionToValidationErrors(): void
1178
    {
1179
        $data = [
×
1180
            'relatedDummy' => 22,
×
1181
        ];
×
1182

1183
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1184
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['relatedDummy']));
×
1185

1186
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1187

1188
        // BC layer for api-platform/metadata < 4.1
1189
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1190
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn(
×
1191
                (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class)])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)
×
1192
            );
×
1193
        } else {
1194
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn(
×
1195
                (new ApiProperty())->withNativeType(Type::object(RelatedDummy::class))->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)
×
1196
            );
×
1197
        }
1198

1199
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1200

1201
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1202
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1203
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
1204
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1205
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
1206

1207
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1208

1209
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1210
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1211

1212
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
1213
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1214

1215
        // 'not_normalizable_value_exceptions' is set by Serializer thanks to DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS
1216
        $actual = $normalizer->denormalize($data, Dummy::class, null, ['not_normalizable_value_exceptions' => []]);
×
1217
        $this->assertNull($actual->relatedDummy);
×
1218
    }
1219

1220
    public function testDeserializationPathForNotDenormalizableRelations(): void
1221
    {
1222
        $data = [
×
1223
            'relatedDummies' => ['wrong'],
×
1224
        ];
×
1225

1226
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1227
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['relatedDummies']));
×
1228

1229
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1230

1231
        // BC layer for api-platform/metadata < 4.1
1232
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1233
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn(
×
1234
                (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, null, new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class))])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true)
×
1235
            );
×
1236
        } else {
1237
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn(
×
1238
                (new ApiProperty())->withNativeType(Type::collection(Type::object(ArrayCollection::class), Type::object(RelatedDummy::class)))->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true)
×
1239
            );
×
1240
        }
1241

1242
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1243
        $iriConverterProphecy->getResourceFromIri(Argument::cetera())->willThrow(new InvalidArgumentException('Invalid IRI'));
×
1244

1245
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1246
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1247
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
1248
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1249
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
1250

1251
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1252

1253
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1254
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1255

1256
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
1257
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1258

1259
        $errors = [];
×
1260
        $actual = $normalizer->denormalize($data, Dummy::class, null, ['not_normalizable_value_exceptions' => &$errors]);
×
1261
        $this->assertEmpty($actual->relatedDummies);
×
1262
        $this->assertCount(1, $errors);
×
1263
        $this->assertInstanceOf(NotNormalizableValueException::class, $errors[0]);
×
1264
        $this->assertSame('relatedDummies[0]', $errors[0]->getPath());
×
1265
        $this->assertSame('Invalid IRI "wrong".', $errors[0]->getMessage());
×
1266
    }
1267

1268
    public function testDeserializationPathForNotDenormalizableResource(): void
1269
    {
1270
        $this->expectException(NotNormalizableValueException::class);
×
1271
        $this->expectExceptionMessage('Invalid IRI "wrong IRI".');
×
1272

1273
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1274

1275
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1276

1277
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1278
        $iriConverterProphecy->getResourceFromIri(Argument::cetera())->willThrow(new InvalidArgumentException('Invalid IRI'));
×
1279

1280
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1281
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1282
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1283

1284
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1285

1286
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1287
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1288

1289
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
1290
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1291

1292
        $normalizer->denormalize('wrong IRI', Dummy::class, null, ['not_normalizable_value_exceptions' => []]);
×
1293
    }
1294

1295
    public function testDeserializationPathForNotFoundResource(): void
1296
    {
1297
        $this->expectException(NotNormalizableValueException::class);
×
1298
        $this->expectExceptionMessage('Some item not found exception.');
×
1299

1300
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1301

1302
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1303

1304
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1305
        $iriConverterProphecy->getResourceFromIri(Argument::cetera())->willThrow(new ItemNotFoundException('Some item not found exception.'));
×
1306

1307
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1308
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1309
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1310

1311
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1312

1313
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1314
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1315

1316
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
1317
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1318

1319
        $normalizer->denormalize('/some-iri', Dummy::class, null, ['not_normalizable_value_exceptions' => []]);
×
1320
    }
1321

1322
    public function testInnerDocumentNotAllowed(): void
1323
    {
1324
        $this->expectException(UnexpectedValueException::class);
×
1325
        $this->expectExceptionMessage('Nested documents for attribute "relatedDummy" are not allowed. Use IRIs instead.');
×
1326

1327
        $data = [
×
1328
            'relatedDummy' => [
×
1329
                'foo' => 'bar',
×
1330
            ],
×
1331
        ];
×
1332

1333
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1334
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['relatedDummy']));
×
1335

1336
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1337

1338
        // BC layer for api-platform/metadata < 4.1
1339
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1340
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn(
×
1341
                (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class)])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)
×
1342
            );
×
1343
        } else {
1344
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn(
×
1345
                (new ApiProperty())->withNativeType(Type::object(RelatedDummy::class))->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)
×
1346
            );
×
1347
        }
1348

1349
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1350

1351
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1352
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1353
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
1354
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
1355
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1356

1357
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1358

1359
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1360
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1361

1362
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
1363
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1364

1365
        $normalizer->denormalize($data, Dummy::class);
×
1366
    }
1367

1368
    public function testBadType(): void
1369
    {
1370
        $this->expectException(UnexpectedValueException::class);
×
1371
        $this->expectExceptionMessage('The type of the "foo" attribute must be "float", "integer" given.');
×
1372

1373
        $data = [
×
1374
            'foo' => 42,
×
1375
        ];
×
1376

1377
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1378
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['foo']));
×
1379

1380
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1381

1382
        // BC layer for api-platform/metadata < 4.1
1383
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1384
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1385
        } else {
1386
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::float())->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1387
        }
1388

1389
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1390

1391
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1392
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1393
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1394

1395
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1396

1397
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1398
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1399

1400
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
1401
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1402

1403
        $normalizer->denormalize($data, Dummy::class);
×
1404
    }
1405

1406
    public function testTypeChecksCanBeDisabled(): void
1407
    {
1408
        $data = [
×
1409
            'foo' => 42,
×
1410
        ];
×
1411

1412
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1413
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['foo']));
×
1414

1415
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1416

1417
        // BC layer for api-platform/metadata < 4.1
1418
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1419
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1420
        } else {
1421
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::float())->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1422
        }
1423

1424
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1425

1426
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1427
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1428
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1429

1430
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1431

1432
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1433
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1434

1435
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
1436
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1437

1438
        $actual = $normalizer->denormalize($data, Dummy::class, null, ['disable_type_enforcement' => true]);
×
1439

1440
        $this->assertInstanceOf(Dummy::class, $actual);
×
1441

1442
        $propertyAccessorProphecy->setValue($actual, 'foo', 42)->shouldHaveBeenCalled();
×
1443
    }
1444

1445
    public function testJsonAllowIntAsFloat(): void
1446
    {
1447
        $data = [
×
1448
            'foo' => 42,
×
1449
        ];
×
1450

1451
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1452
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['foo']));
×
1453

1454
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1455

1456
        // BC layer for api-platform/metadata < 4.1
1457
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1458
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1459
        } else {
1460
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::float())->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1461
        }
1462

1463
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1464

1465
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1466
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1467
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1468

1469
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1470

1471
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1472
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1473

1474
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
1475
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1476

1477
        $actual = $normalizer->denormalize($data, Dummy::class, 'jsonfoo');
×
1478

1479
        $this->assertInstanceOf(Dummy::class, $actual);
×
1480

1481
        $propertyAccessorProphecy->setValue($actual, 'foo', 42)->shouldHaveBeenCalled();
×
1482
    }
1483

1484
    public function testDenormalizeBadKeyType(): void
1485
    {
1486
        $this->expectException(NotNormalizableValueException::class);
×
1487
        $this->expectExceptionMessage('The type of the key "a" must be "int", "string" given.');
×
1488

1489
        $data = [
×
1490
            'name' => 'foo',
×
1491
            'relatedDummy' => [
×
1492
                'foo' => 'bar',
×
1493
            ],
×
1494
            'relatedDummies' => [
×
1495
                'a' => [
×
1496
                    'bar' => 'baz',
×
1497
                ],
×
1498
            ],
×
1499
            'relatedDummiesWithUnionTypes' => [
×
1500
                'a' => [
×
1501
                    'bar' => 'baz',
×
1502
                ],
×
1503
            ],
×
1504
        ];
×
1505

1506
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1507
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['relatedDummies']));
×
1508

1509
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1510

1511
        // BC layer for api-platform/metadata < 4.1
1512
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1513
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
1514
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1515

1516
            $type = new LegacyType(
×
1517
                LegacyType::BUILTIN_TYPE_OBJECT,
×
1518
                false,
×
1519
                ArrayCollection::class,
×
1520
                true,
×
1521
                new LegacyType(LegacyType::BUILTIN_TYPE_INT),
×
1522
                new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class)
×
1523
            );
×
1524
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$type])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
1525
        } else {
1526
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
1527
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::object(RelatedDummy::class))->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1528

1529
            $type = Type::collection(Type::object(ArrayCollection::class), Type::object(RelatedDummy::class), Type::int());
×
1530
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType($type)->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
1531
        }
1532

1533
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1534

1535
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1536
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1537
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
1538
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1539
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
1540

1541
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1542

1543
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1544
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1545

1546
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
1547
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1548

1549
        $normalizer->denormalize($data, Dummy::class);
×
1550
    }
1551

1552
    public function testNullable(): void
1553
    {
1554
        $data = [
×
1555
            'name' => null,
×
1556
        ];
×
1557

1558
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1559
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['name']));
×
1560

1561
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1562

1563
        // BC layer for api-platform/metadata < 4.1
1564
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1565
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING, true)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1566
        } else {
1567
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::nullable(Type::string()))->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1568
        }
1569

1570
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1571

1572
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1573
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1574
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1575

1576
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1577

1578
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1579
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1580

1581
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
1582
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1583

1584
        $actual = $normalizer->denormalize($data, Dummy::class);
×
1585

1586
        $this->assertInstanceOf(Dummy::class, $actual);
×
1587

1588
        $propertyAccessorProphecy->setValue($actual, 'name', null)->shouldHaveBeenCalled();
×
1589
    }
1590

1591
    public function testDenormalizeBasicTypePropertiesFromXml(): void
1592
    {
1593
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1594
        $propertyNameCollectionFactoryProphecy->create(ObjectWithBasicProperties::class, Argument::type('array'))->willReturn(new PropertyNameCollection([
×
1595
            'boolTrue1',
×
1596
            'boolFalse1',
×
1597
            'boolTrue2',
×
1598
            'boolFalse2',
×
1599
            'int1',
×
1600
            'int2',
×
1601
            'float1',
×
1602
            'float2',
×
1603
            'float3',
×
1604
            'floatNaN',
×
1605
            'floatInf',
×
1606
            'floatNegInf',
×
1607
        ]));
×
1608

1609
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1610

1611
        // BC layer for api-platform/metadata < 4.1
1612
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1613
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolTrue1', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_BOOL)])->withDescription('')->withReadable(false)->withWritable(true));
×
1614
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolFalse1', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_BOOL)])->withDescription('')->withReadable(false)->withWritable(true));
×
1615
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolTrue2', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_BOOL)])->withDescription('')->withReadable(false)->withWritable(true));
×
1616
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolFalse2', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_BOOL)])->withDescription('')->withReadable(false)->withWritable(true));
×
1617
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'int1', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)])->withDescription('')->withReadable(false)->withWritable(true));
×
1618
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'int2', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)])->withDescription('')->withReadable(false)->withWritable(true));
×
1619
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'float1', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true));
×
1620
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'float2', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true));
×
1621
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'float3', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true));
×
1622
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'floatNaN', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true));
×
1623
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'floatInf', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true));
×
1624
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'floatNegInf', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true));
×
1625
        } else {
1626
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolTrue1', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::bool())->withDescription('')->withReadable(false)->withWritable(true));
×
1627
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolFalse1', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::bool())->withDescription('')->withReadable(false)->withWritable(true));
×
1628
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolTrue2', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::bool())->withDescription('')->withReadable(false)->withWritable(true));
×
1629
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolFalse2', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::bool())->withDescription('')->withReadable(false)->withWritable(true));
×
1630
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'int1', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::int())->withDescription('')->withReadable(false)->withWritable(true));
×
1631
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'int2', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::int())->withDescription('')->withReadable(false)->withWritable(true));
×
1632
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'float1', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::float())->withDescription('')->withReadable(false)->withWritable(true));
×
1633
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'float2', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::float())->withDescription('')->withReadable(false)->withWritable(true));
×
1634
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'float3', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::float())->withDescription('')->withReadable(false)->withWritable(true));
×
1635
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'floatNaN', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::float())->withDescription('')->withReadable(false)->withWritable(true));
×
1636
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'floatInf', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::float())->withDescription('')->withReadable(false)->withWritable(true));
×
1637
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'floatNegInf', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::float())->withDescription('')->withReadable(false)->withWritable(true));
×
1638
        }
1639

1640
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1641

1642
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1643
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'boolTrue1', true)->shouldBeCalled();
×
1644
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'boolFalse1', false)->shouldBeCalled();
×
1645
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'boolTrue2', true)->shouldBeCalled();
×
1646
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'boolFalse2', false)->shouldBeCalled();
×
1647
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'int1', 4711)->shouldBeCalled();
×
1648
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'int2', -4711)->shouldBeCalled();
×
1649
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'float1', Argument::approximate(123.456, 0))->shouldBeCalled();
×
1650
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'float2', Argument::approximate(-1.2344e56, 1))->shouldBeCalled();
×
1651
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'float3', Argument::approximate(45E-6, 1))->shouldBeCalled();
×
1652
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'floatNaN', Argument::that(static fn (float $arg) => is_nan($arg)))->shouldBeCalled();
×
1653
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'floatInf', \INF)->shouldBeCalled();
×
1654
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'floatNegInf', -\INF)->shouldBeCalled();
×
1655

1656
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1657
        $resourceClassResolverProphecy->getResourceClass(null, ObjectWithBasicProperties::class)->willReturn(ObjectWithBasicProperties::class);
×
1658
        $resourceClassResolverProphecy->isResourceClass(ObjectWithBasicProperties::class)->willReturn(true);
×
1659

1660
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1661
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
1662

1663
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
1664
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1665

1666
        $objectWithBasicProperties = $normalizer->denormalize(
×
1667
            [
×
1668
                'boolTrue1' => 'true',
×
1669
                'boolFalse1' => 'false',
×
1670
                'boolTrue2' => '1',
×
1671
                'boolFalse2' => '0',
×
1672
                'int1' => '4711',
×
1673
                'int2' => '-4711',
×
1674
                'float1' => '123.456',
×
1675
                'float2' => '-1.2344e56',
×
1676
                'float3' => '45E-6',
×
1677
                'floatNaN' => 'NaN',
×
1678
                'floatInf' => 'INF',
×
1679
                'floatNegInf' => '-INF',
×
1680
            ],
×
1681
            ObjectWithBasicProperties::class,
×
1682
            'xml'
×
1683
        );
×
1684

1685
        $this->assertInstanceOf(ObjectWithBasicProperties::class, $objectWithBasicProperties);
×
1686
    }
1687

1688
    public function testDenormalizeCollectionDecodedFromXmlWithOneChild(): void
1689
    {
1690
        $data = [
×
1691
            'relatedDummies' => [
×
1692
                'name' => 'foo',
×
1693
            ],
×
1694
        ];
×
1695

1696
        $relatedDummy = new RelatedDummy();
×
1697
        $relatedDummy->setName('foo');
×
1698

1699
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1700
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['relatedDummies']));
×
1701

1702
        // BC layer for api-platform/metadata < 4.1
1703
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1704
            $relatedDummyType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class);
×
1705
            $relatedDummiesType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $relatedDummyType);
×
1706

1707
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1708
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
1709
        } else {
1710
            $relatedDummyType = Type::object(RelatedDummy::class);
×
1711
            $relatedDummiesType = Type::collection(Type::object(ArrayCollection::class), $relatedDummyType, Type::int());
×
1712

1713
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1714
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType($relatedDummiesType)->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
1715
        }
1716

1717
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1718

1719
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1720
        $propertyAccessorProphecy->setValue(Argument::type(Dummy::class), 'relatedDummies', Argument::type('array'))->shouldBeCalled();
×
1721

1722
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1723
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1724
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
1725
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
1726
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1727

1728
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1729
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1730
        $serializerProphecy->denormalize(['name' => 'foo'], RelatedDummy::class, 'xml', Argument::type('array'))->willReturn($relatedDummy);
×
1731

1732
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
1733
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1734

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

1738
    public function testDenormalizePopulatingNonCloneableObject(): void
1739
    {
1740
        $dummy = new NonCloneableDummy();
×
1741
        $dummy->setName('foo');
×
1742

1743
        $data = [
×
1744
            'name' => 'bar',
×
1745
        ];
×
1746

1747
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1748
        $propertyNameCollectionFactoryProphecy->create(NonCloneableDummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['name']));
×
1749

1750
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1751

1752
        // BC layer for api-platform/metadata < 4.1
1753
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1754
            $propertyMetadataFactoryProphecy->create(NonCloneableDummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
1755
        } else {
1756
            $propertyMetadataFactoryProphecy->create(NonCloneableDummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
1757
        }
1758

1759
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1760
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1761
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1762
        $resourceClassResolverProphecy->getResourceClass(null, NonCloneableDummy::class)->willReturn(NonCloneableDummy::class);
×
1763
        $resourceClassResolverProphecy->getResourceClass($dummy, NonCloneableDummy::class)->willReturn(NonCloneableDummy::class);
×
1764
        $resourceClassResolverProphecy->isResourceClass(NonCloneableDummy::class)->willReturn(true);
×
1765

1766
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1767
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
1768

1769
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
1770
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1771
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1772

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

1776
        $this->assertInstanceOf(NonCloneableDummy::class, $actual);
×
1777
        $this->assertSame($dummy, $actual);
×
1778
        $propertyAccessorProphecy->setValue($actual, 'name', 'bar')->shouldHaveBeenCalled();
×
1779
    }
1780

1781
    public function testDenormalizeObjectWithNullDisabledTypeEnforcement(): void
1782
    {
1783
        $data = [
×
1784
            'dummy' => null,
×
1785
        ];
×
1786

1787
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1788
        $propertyNameCollectionFactoryProphecy->create(DtoWithNullValue::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['dummy']));
×
1789

1790
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1791

1792
        // BC layer for api-platform/metadata < 4.1
1793
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1794
            $propertyMetadataFactoryProphecy->create(DtoWithNullValue::class, 'dummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, nullable: true)])->withDescription('')->withReadable(true)->withWritable(true));
×
1795
        } else {
1796
            $propertyMetadataFactoryProphecy->create(DtoWithNullValue::class, 'dummy', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::nullable(Type::object()))->withDescription('')->withReadable(true)->withWritable(true));
×
1797
        }
1798

1799
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1800
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1801
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1802
        $resourceClassResolverProphecy->getResourceClass(null, DtoWithNullValue::class)->willReturn(DtoWithNullValue::class);
×
1803
        $resourceClassResolverProphecy->isResourceClass(DtoWithNullValue::class)->willReturn(true);
×
1804

1805
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1806
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
1807

1808
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
1809
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1810

1811
        $context = [AbstractItemNormalizer::DISABLE_TYPE_ENFORCEMENT => true];
×
1812
        $actual = $normalizer->denormalize($data, DtoWithNullValue::class, null, $context);
×
1813

1814
        $this->assertInstanceOf(DtoWithNullValue::class, $actual);
×
1815
        $this->assertEquals(new DtoWithNullValue(), $actual);
×
1816
    }
1817

1818
    public function testCacheKey(): void
1819
    {
1820
        $relatedDummy = new RelatedDummy();
×
1821

1822
        $dummy = new Dummy();
×
1823
        $dummy->setName('foo');
×
1824
        $dummy->setAlias('ignored');
×
1825
        $dummy->setRelatedDummy($relatedDummy);
×
1826
        $dummy->relatedDummies->add(new RelatedDummy());
×
1827

1828
        $relatedDummies = new ArrayCollection([$relatedDummy]);
×
1829

1830
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1831
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['name', 'alias', 'relatedDummy', 'relatedDummies']));
×
1832

1833
        // BC layer for api-platform/metadata < 4.1
1834
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1835
            $relatedDummyType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class);
×
1836
            $relatedDummiesType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $relatedDummyType);
×
1837

1838
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1839
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true));
×
1840
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'alias', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true));
×
1841
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withDescription('')->withReadable(true)->withWritable(false)->withReadableLink(false));
×
1842
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(true)->withWritable(false)->withReadableLink(false));
×
1843
        } else {
1844
            $relatedDummyType = Type::object(RelatedDummy::class);
×
1845
            $relatedDummiesType = Type::collection(Type::object(ArrayCollection::class), $relatedDummyType, Type::int());
×
1846

1847
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1848
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true));
×
1849
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'alias', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true));
×
1850
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType($relatedDummyType)->withDescription('')->withReadable(true)->withWritable(false)->withReadableLink(false));
×
1851
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType($relatedDummiesType)->withReadable(true)->withWritable(false)->withReadableLink(false));
×
1852
        }
1853

1854
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1855
        $iriConverterProphecy->getIriFromResource($dummy, Argument::cetera())->willReturn('/dummies/1');
×
1856
        $iriConverterProphecy->getIriFromResource($relatedDummy, Argument::cetera())->willReturn('/dummies/2');
×
1857

1858
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1859
        $propertyAccessorProphecy->getValue($dummy, 'name')->willReturn('foo');
×
1860
        $propertyAccessorProphecy->getValue($dummy, 'relatedDummy')->willReturn($relatedDummy);
×
1861
        $propertyAccessorProphecy->getValue($dummy, 'relatedDummies')->willReturn($relatedDummies);
×
1862

1863
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1864
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1865
        $resourceClassResolverProphecy->getResourceClass($dummy, null)->willReturn(Dummy::class);
×
1866
        $resourceClassResolverProphecy->getResourceClass($relatedDummy, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
1867
        $resourceClassResolverProphecy->getResourceClass($relatedDummies, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
1868
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1869
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
1870

1871
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1872
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
1873
        $serializerProphecy->normalize('foo', null, Argument::type('array'))->willReturn('foo');
×
NEW
1874
        $serializerProphecy->normalize('/dummies/2', null, Argument::type('array'))->willReturn('/dummies/2');
×
UNCOV
1875
        $serializerProphecy->normalize(['/dummies/2'], null, Argument::type('array'))->willReturn(['/dummies/2']);
×
1876

1877
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
1878
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1879

1880
        $expected = [
×
1881
            'name' => 'foo',
×
1882
            'relatedDummy' => '/dummies/2',
×
1883
            'relatedDummies' => ['/dummies/2'],
×
1884
        ];
×
1885
        $this->assertSame($expected, $normalizer->normalize($dummy, null, [
×
1886
            'resources' => [],
×
1887
            'groups' => ['group'],
×
1888
            'ignored_attributes' => ['alias'],
×
1889
            'operation_name' => 'operation_name',
×
1890
            'root_operation_name' => 'root_operation_name',
×
1891
        ]));
×
1892

1893
        $operationCacheKey = (new \ReflectionClass($normalizer))->getProperty('localFactoryOptionsCache')->getValue($normalizer);
×
1894
        $this->assertEquals(array_keys($operationCacheKey), [\sprintf('%s%s%s%s', Dummy::class, 'operation_name', 'root_operation_name', 'n')]);
×
1895
        $this->assertEquals(current($operationCacheKey), ['serializer_groups' => ['group'], 'api_allow_update' => false]);
×
1896
    }
1897
}
1898

1899
class ObjectWithBasicProperties
1900
{
1901
    /** @var bool */
1902
    public $boolTrue1;
1903

1904
    /** @var bool */
1905
    public $boolFalse1;
1906

1907
    /** @var bool */
1908
    public $boolTrue2;
1909

1910
    /** @var bool */
1911
    public $boolFalse2;
1912

1913
    /** @var int */
1914
    public $int1;
1915

1916
    /** @var int */
1917
    public $int2;
1918

1919
    /** @var float */
1920
    public $float1;
1921

1922
    /** @var float */
1923
    public $float2;
1924

1925
    /** @var float */
1926
    public $float3;
1927

1928
    /** @var float */
1929
    public $floatNaN;
1930

1931
    /** @var float */
1932
    public $floatInf;
1933

1934
    /** @var float */
1935
    public $floatNegInf;
1936
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc