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

api-platform / core / 15775135891

20 Jun 2025 08:42AM UTC coverage: 22.065% (+0.2%) from 21.876%
15775135891

push

github

soyuka
Merge 4.1

13 of 103 new or added lines in 10 files covered. (12.62%)

868 existing lines in 35 files now uncovered.

11487 of 52060 relevant lines covered (22.06%)

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

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

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

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

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

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

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

91
    public function testNormalize(): void
92
    {
93
        $relatedDummy = new RelatedDummy();
×
94

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

101
        $relatedDummies = new ArrayCollection([$relatedDummy]);
×
102

103
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
104
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['name', 'alias', 'relatedDummy', 'relatedDummies']));
×
105

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

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

120
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
121
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true));
×
122
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'alias', [])->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true));
×
123
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn((new ApiProperty())->withNativeType($relatedDummyType)->withDescription('')->withReadable(true)->withWritable(false)->withReadableLink(false));
×
124
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn((new ApiProperty())->withNativeType($relatedDummiesType)->withReadable(true)->withWritable(false)->withReadableLink(false));
×
125
        }
126

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

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

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

144
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
145
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
146
        $serializerProphecy->normalize('foo', null, Argument::type('array'))->willReturn('foo');
×
147
        $serializerProphecy->normalize(['/dummies/2'], null, Argument::type('array'))->willReturn(['/dummies/2']);
×
148

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

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

163
    public function testNormalizeWithSecuredProperty(): void
164
    {
165
        $dummy = new SecuredDummy();
×
166
        $dummy->setTitle('myPublicTitle');
×
167
        $dummy->setAdminOnlyProperty('secret');
×
168

169
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
170
        $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, [])->willReturn(new PropertyNameCollection(['title', 'adminOnlyProperty']));
×
171

172
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
173

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

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

186
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
187
        $propertyAccessorProphecy->getValue($dummy, 'title')->willReturn('myPublicTitle');
×
188
        $propertyAccessorProphecy->getValue($dummy, 'adminOnlyProperty')->willReturn('secret');
×
189

190
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
191
        $resourceClassResolverProphecy->getResourceClass($dummy, null)->willReturn(SecuredDummy::class);
×
192
        $resourceClassResolverProphecy->getResourceClass(null, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
193
        $resourceClassResolverProphecy->isResourceClass(SecuredDummy::class)->willReturn(true);
×
194

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

202
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
203
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
204
        $serializerProphecy->normalize('myPublicTitle', null, Argument::type('array'))->willReturn('myPublicTitle');
×
205

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

209
        $expected = [
×
210
            'title' => 'myPublicTitle',
×
211
        ];
×
212
        $this->assertSame($expected, $normalizer->normalize($dummy, null, [
×
213
            'resources' => [],
×
214
        ]));
×
215
    }
216

217
    public function testNormalizePropertyAsIriWithUriTemplate(): void
218
    {
219
        $propertyCollectionIriOnlyRelation = new PropertyCollectionIriOnlyRelation();
×
220
        $propertyCollectionIriOnlyRelation->name = 'My Relation';
×
221

222
        $propertyCollectionIriOnly = new PropertyCollectionIriOnly();
×
223
        $propertyCollectionIriOnly->addPropertyCollectionIriOnlyRelation($propertyCollectionIriOnlyRelation);
×
224

225
        $collectionOperation = new GetCollection('/property-collection-relations');
×
226
        $getIterableOperation = new GetCollection('/parent/{parentId}/another-collection-operations');
×
227
        $getToOneOperation = new Get('/parent/{parentId}/another-collection-operations/{id}');
×
228

229
        $resourceRelationMetadataCollection = new ResourceMetadataCollection(PropertyCollectionIriOnlyRelation::class, [
×
230
            (new ApiResource())->withOperations(new Operations([$collectionOperation, $getIterableOperation, $getToOneOperation])),
×
231
        ]);
×
232

233
        $resourceMetadataCollectionFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
×
234
        $resourceMetadataCollectionFactoryProphecy->create(PropertyCollectionIriOnlyRelation::class)->willReturn($resourceRelationMetadataCollection);
×
235

236
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
237
        $propertyNameCollectionFactoryProphecy->create(PropertyCollectionIriOnly::class, ['normalization_groups' => null, 'denormalization_groups' => null, 'operation_name' => null])->willReturn(
×
238
            new PropertyNameCollection(['propertyCollectionIriOnlyRelation', 'iterableIri', 'toOneRelation'])
×
239
        );
×
240

241
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
242

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

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

277
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
278
        $propertyAccessorProphecy->getValue($propertyCollectionIriOnly, 'propertyCollectionIriOnlyRelation')->willReturn([$propertyCollectionIriOnlyRelation]);
×
279
        $propertyAccessorProphecy->getValue($propertyCollectionIriOnly, 'iterableIri')->willReturn($propertyCollectionIriOnlyRelation);
×
280
        $propertyAccessorProphecy->getValue($propertyCollectionIriOnly, 'toOneRelation')->willReturn($propertyCollectionIriOnlyRelation);
×
281

282
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
283

284
        $resourceClassResolverProphecy->isResourceClass(PropertyCollectionIriOnly::class)->willReturn(true);
×
285
        $resourceClassResolverProphecy->getResourceClass(null, PropertyCollectionIriOnly::class)->willReturn(PropertyCollectionIriOnly::class);
×
286

287
        $resourceClassResolverProphecy->isResourceClass(PropertyCollectionIriOnlyRelation::class)->willReturn(true);
×
288
        $resourceClassResolverProphecy->getResourceClass([$propertyCollectionIriOnlyRelation], PropertyCollectionIriOnlyRelation::class)->willReturn(PropertyCollectionIriOnlyRelation::class);
×
289

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

293
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
294
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
295
        $normalizer->setSerializer($serializerProphecy->reveal());
×
296

297
        $expected = [
×
298
            'propertyCollectionIriOnlyRelation' => '/property-collection-relations',
×
299
            'iterableIri' => '/parent/42/another-collection-operations',
×
300
            'toOneRelation' => '/parent/42/another-collection-operations/24',
×
301
        ];
×
302

303
        $this->assertSame($expected, $normalizer->normalize($propertyCollectionIriOnly, 'jsonld', [
×
304
            'resources' => [],
×
305
            'root_operation' => new GetCollection('/property_collection_iri_onlies'),
×
306
        ]));
×
307
    }
308

309
    public function testDenormalizeWithSecuredProperty(): void
310
    {
311
        $data = [
×
312
            'title' => 'foo',
×
313
            'adminOnlyProperty' => 'secret',
×
314
        ];
×
315

316
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
317
        $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, [])->willReturn(new PropertyNameCollection(['title', 'adminOnlyProperty']));
×
318

319
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
320

321
        // BC layer for api-platform/metadata < 4.1
322
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
323
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
324
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'adminOnlyProperty', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withSecurity('is_granted(\'ROLE_ADMIN\')'));
×
325
        } else {
326
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', [])->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
327
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'adminOnlyProperty', [])->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true)->withSecurity('is_granted(\'ROLE_ADMIN\')'));
×
328
        }
329

330
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
331

332
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
333

334
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
335
        $resourceClassResolverProphecy->getResourceClass(null, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
336
        $resourceClassResolverProphecy->isResourceClass(SecuredDummy::class)->willReturn(true);
×
337

338
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
339
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
340

341
        $resourceAccessChecker = $this->prophesize(ResourceAccessCheckerInterface::class);
×
342
        $resourceAccessChecker->isGranted(
×
343
            SecuredDummy::class,
×
344
            'is_granted(\'ROLE_ADMIN\')',
×
345
            ['object' => null, 'property' => 'adminOnlyProperty']
×
346
        )->willReturn(false);
×
347

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

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

353
        $this->assertInstanceOf(SecuredDummy::class, $actual);
×
354

355
        $propertyAccessorProphecy->setValue($actual, 'title', 'foo')->shouldHaveBeenCalled();
×
356
        $propertyAccessorProphecy->setValue($actual, 'adminOnlyProperty', 'secret')->shouldNotHaveBeenCalled();
×
357
    }
358

359
    public function testDenormalizeCreateWithDeniedPostDenormalizeSecuredProperty(): void
360
    {
361
        $data = [
×
362
            'title' => 'foo',
×
363
            'ownerOnlyProperty' => 'should not be set',
×
364
        ];
×
365

366
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
367
        $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, [])->willReturn(new PropertyNameCollection(['title', 'ownerOnlyProperty']));
×
368

369
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
370

371
        // BC layer for api-platform/metadata < 4.1
372
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
373
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
374
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withWritable(true)->withSecurityPostDenormalize('false')->withDefault(''));
×
375
        } else {
376
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', [])->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
377
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', [])->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true)->withWritable(true)->withSecurityPostDenormalize('false')->withDefault(''));
×
378
        }
379

380
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
381

382
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
383

384
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
385
        $resourceClassResolverProphecy->getResourceClass(null, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
386
        $resourceClassResolverProphecy->isResourceClass(SecuredDummy::class)->willReturn(true);
×
387

388
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
389
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
390

391
        $resourceAccessChecker = $this->prophesize(ResourceAccessCheckerInterface::class);
×
392
        $resourceAccessChecker->isGranted(
×
393
            SecuredDummy::class,
×
394
            'false',
×
395
            Argument::any()
×
396
        )->willReturn(false);
×
397

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

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

403
        $this->assertInstanceOf(SecuredDummy::class, $actual);
×
404

405
        $propertyAccessorProphecy->setValue($actual, 'title', 'foo')->shouldHaveBeenCalled();
×
406
        $propertyAccessorProphecy->setValue($actual, 'ownerOnlyProperty', 'should not be set')->shouldHaveBeenCalled();
×
407
        $propertyAccessorProphecy->setValue($actual, 'ownerOnlyProperty', '')->shouldHaveBeenCalled();
×
408
    }
409

410
    public function testDenormalizeUpdateWithSecuredProperty(): void
411
    {
412
        $dummy = new SecuredDummy();
×
413

414
        $data = [
×
415
            'title' => 'foo',
×
416
            'ownerOnlyProperty' => 'secret',
×
417
        ];
×
418

419
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
420
        $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, [])->willReturn(new PropertyNameCollection(['title', 'ownerOnlyProperty']));
×
421

422
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
423

424
        // BC layer for api-platform/metadata < 4.1
425
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
426
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
427
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withWritable(true)->withSecurity('true'));
×
428
        } else {
429
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', [])->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
430
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', [])->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true)->withWritable(true)->withSecurity('true'));
×
431
        }
432

433
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
434

435
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
436

437
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
438
        $resourceClassResolverProphecy->getResourceClass($dummy, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
439
        $resourceClassResolverProphecy->getResourceClass(null, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
440
        $resourceClassResolverProphecy->isResourceClass(SecuredDummy::class)->willReturn(true);
×
441

442
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
443
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
444

445
        $resourceAccessChecker = $this->prophesize(ResourceAccessCheckerInterface::class);
×
446
        $resourceAccessChecker->isGranted(
×
447
            SecuredDummy::class,
×
448
            'true',
×
449
            ['object' => null, 'property' => 'ownerOnlyProperty']
×
450
        )->willReturn(true);
×
451
        $resourceAccessChecker->isGranted(
×
452
            SecuredDummy::class,
×
453
            'true',
×
454
            ['object' => $dummy, 'property' => 'ownerOnlyProperty']
×
455
        )->willReturn(true);
×
456

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

460
        $context = [AbstractItemNormalizer::OBJECT_TO_POPULATE => $dummy];
×
461
        $actual = $normalizer->denormalize($data, SecuredDummy::class, null, $context);
×
462

463
        $this->assertInstanceOf(SecuredDummy::class, $actual);
×
464

465
        $propertyAccessorProphecy->setValue($actual, 'title', 'foo')->shouldHaveBeenCalled();
×
466
        $propertyAccessorProphecy->setValue($actual, 'ownerOnlyProperty', 'secret')->shouldHaveBeenCalled();
×
467
    }
468

469
    public function testDenormalizeUpdateWithDeniedSecuredProperty(): void
470
    {
471
        $dummy = new SecuredDummy();
×
472
        $dummy->setOwnerOnlyProperty('secret');
×
473

474
        $data = [
×
475
            'title' => 'foo',
×
476
            'ownerOnlyProperty' => 'should not be set',
×
477
        ];
×
478

479
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
480
        $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, [])->willReturn(new PropertyNameCollection(['title', 'ownerOnlyProperty']));
×
481

482
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
483

484
        // BC layer for api-platform/metadata < 4.1
485
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
486
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
487
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withWritable(true)->withSecurity('false'));
×
488
        } else {
489
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', [])->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
490
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', [])->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true)->withWritable(true)->withSecurity('false'));
×
491
        }
492

493
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
494

495
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
496

497
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
498
        $resourceClassResolverProphecy->getResourceClass(null, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
499
        $resourceClassResolverProphecy->getResourceClass($dummy, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
500
        $resourceClassResolverProphecy->isResourceClass(SecuredDummy::class)->willReturn(true);
×
501

502
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
503
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
504

505
        $resourceAccessChecker = $this->prophesize(ResourceAccessCheckerInterface::class);
×
506
        $resourceAccessChecker->isGranted(
×
507
            SecuredDummy::class,
×
508
            'false',
×
509
            ['object' => null, 'property' => 'ownerOnlyProperty']
×
510
        )->willReturn(false);
×
511
        $resourceAccessChecker->isGranted(
×
512
            SecuredDummy::class,
×
513
            'false',
×
514
            ['object' => $dummy, 'property' => 'ownerOnlyProperty']
×
515
        )->willReturn(false);
×
516

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

520
        $context = [AbstractItemNormalizer::OBJECT_TO_POPULATE => $dummy];
×
521
        $actual = $normalizer->denormalize($data, SecuredDummy::class, null, $context);
×
522

523
        $this->assertInstanceOf(SecuredDummy::class, $actual);
×
524

525
        $propertyAccessorProphecy->setValue($actual, 'title', 'foo')->shouldHaveBeenCalled();
×
526
        $propertyAccessorProphecy->setValue($actual, 'ownerOnlyProperty', 'should not be set')->shouldNotHaveBeenCalled();
×
527
    }
528

529
    public function testDenormalizeUpdateWithDeniedPostDenormalizeSecuredProperty(): void
530
    {
531
        $dummy = new SecuredDummy();
×
532
        $dummy->setOwnerOnlyProperty('secret');
×
533

534
        $data = [
×
535
            'title' => 'foo',
×
536
            'ownerOnlyProperty' => 'should not be set',
×
537
        ];
×
538

539
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
540
        $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, [])->willReturn(new PropertyNameCollection(['title', 'ownerOnlyProperty']));
×
541

542
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
543

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

553
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
554

555
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
556
        $propertyAccessorProphecy->getValue($dummy, 'ownerOnlyProperty')->willReturn('secret');
×
557

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

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

566
        $resourceAccessChecker = $this->prophesize(ResourceAccessCheckerInterface::class);
×
567
        $resourceAccessChecker->isGranted(
×
568
            SecuredDummy::class,
×
569
            'false',
×
570
            ['object' => $dummy, 'previous_object' => $dummy, 'property' => 'ownerOnlyProperty']
×
571
        )->willReturn(false);
×
572

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

576
        $context = [AbstractItemNormalizer::OBJECT_TO_POPULATE => $dummy];
×
577
        $actual = $normalizer->denormalize($data, SecuredDummy::class, null, $context);
×
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', 'secret')->shouldHaveBeenCalled();
×
584
    }
585

586
    public function testNormalizeReadableLinks(): void
587
    {
588
        $relatedDummy = new RelatedDummy();
×
589

590
        $dummy = new Dummy();
×
591
        $dummy->setRelatedDummy($relatedDummy);
×
592
        $dummy->relatedDummies->add(new RelatedDummy());
×
593

594
        $relatedDummies = new ArrayCollection([$relatedDummy]);
×
595

596
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
597
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['relatedDummy', 'relatedDummies']));
×
598

599
        // BC layer for api-platform/metadata < 4.1
600
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
601
            $relatedDummyType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class);
×
602
            $relatedDummiesType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $relatedDummyType);
×
603

604
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
605
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withReadable(true)->withWritable(false)->withReadableLink(true));
×
606
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(true)->withWritable(false)->withReadableLink(true));
×
607
        } else {
608
            $relatedDummyType = Type::object(RelatedDummy::class);
×
609
            $relatedDummiesType = Type::collection(Type::object(ArrayCollection::class), $relatedDummyType, Type::int());
×
610

611
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
612
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn((new ApiProperty())->withNativeType($relatedDummyType)->withReadable(true)->withWritable(false)->withReadableLink(true));
×
613
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn((new ApiProperty())->withNativeType($relatedDummiesType)->withReadable(true)->withWritable(false)->withReadableLink(true));
×
614
        }
615

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

619
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
620
        $propertyAccessorProphecy->getValue($dummy, 'relatedDummy')->willReturn($relatedDummy);
×
621
        $propertyAccessorProphecy->getValue($dummy, 'relatedDummies')->willReturn($relatedDummies);
×
622

623
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
624
        $resourceClassResolverProphecy->getResourceClass($dummy, null)->willReturn(Dummy::class);
×
625
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
626
        $resourceClassResolverProphecy->getResourceClass($relatedDummy, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
627
        $resourceClassResolverProphecy->getResourceClass($relatedDummies, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
628
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
629
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
630

631
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
632
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
633
        $relatedDummyChildContext = Argument::allOf(
×
634
            Argument::type('array'),
×
635
            Argument::withEntry('resource_class', RelatedDummy::class),
×
636
            Argument::not(Argument::withKey('iri')),
×
637
            Argument::not(Argument::withKey('force_resource_class'))
×
638
        );
×
639
        $serializerProphecy->normalize($relatedDummy, null, $relatedDummyChildContext)->willReturn(['foo' => 'hello']);
×
640
        $serializerProphecy->normalize(['foo' => 'hello'], null, Argument::type('array'))->willReturn(['foo' => 'hello']);
×
641
        $serializerProphecy->normalize([['foo' => 'hello']], null, Argument::type('array'))->willReturn([['foo' => 'hello']]);
×
642

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

646
        $expected = [
×
647
            'relatedDummy' => ['foo' => 'hello'],
×
648
            'relatedDummies' => [['foo' => 'hello']],
×
649
        ];
×
650
        $this->assertSame($expected, $normalizer->normalize($dummy, null, [
×
651
            'resources' => [],
×
652
            'force_resource_class' => Dummy::class,
×
653
        ]));
×
654
    }
655

656
    public function testNormalizePolymorphicRelations(): void
657
    {
658
        $concreteDummy = new DummyTableInheritanceChild();
×
659

660
        $dummy = new DummyTableInheritanceRelated();
×
661
        $dummy->addChild($concreteDummy);
×
662

663
        $abstractDummies = new ArrayCollection([$concreteDummy]);
×
664

665
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
666
        $propertyNameCollectionFactoryProphecy->create(DummyTableInheritanceRelated::class, [])->willReturn(new PropertyNameCollection(['children']));
×
667

668
        // BC layer for api-platform/metadata < 4.1
669
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
670
            $abstractDummyType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, DummyTableInheritance::class);
×
671
            $abstractDummiesType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $abstractDummyType);
×
672

673
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
674
            $propertyMetadataFactoryProphecy->create(DummyTableInheritanceRelated::class, 'children', [])->willReturn((new ApiProperty())->withBuiltinTypes([$abstractDummiesType])->withReadable(true)->withWritable(false)->withReadableLink(true));
×
675
        } else {
676
            $abstractDummyType = Type::object(DummyTableInheritance::class);
×
677
            $abstractDummiesType = Type::collection(Type::object(ArrayCollection::class), $abstractDummyType, Type::int());
×
678

679
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
680
            $propertyMetadataFactoryProphecy->create(DummyTableInheritanceRelated::class, 'children', [])->willReturn((new ApiProperty())->withNativeType($abstractDummiesType)->withReadable(true)->withWritable(false)->withReadableLink(true));
×
681
        }
682

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

686
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
687
        $propertyAccessorProphecy->getValue($dummy, 'children')->willReturn($abstractDummies);
×
688

689
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
690
        $resourceClassResolverProphecy->getResourceClass($dummy, null)->willReturn(DummyTableInheritanceRelated::class);
×
691
        $resourceClassResolverProphecy->getResourceClass(null, DummyTableInheritanceRelated::class)->willReturn(DummyTableInheritanceRelated::class);
×
692
        $resourceClassResolverProphecy->getResourceClass($concreteDummy, DummyTableInheritance::class)->willReturn(DummyTableInheritanceChild::class);
×
693
        $resourceClassResolverProphecy->getResourceClass($abstractDummies, DummyTableInheritance::class)->willReturn(DummyTableInheritance::class);
×
694
        $resourceClassResolverProphecy->isResourceClass(DummyTableInheritanceRelated::class)->willReturn(true);
×
695
        $resourceClassResolverProphecy->isResourceClass(DummyTableInheritance::class)->willReturn(true);
×
696

697
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
698
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
699
        $concreteDummyChildContext = Argument::allOf(
×
700
            Argument::type('array'),
×
701
            Argument::not(Argument::withKey('iri'))
×
702
        );
×
703
        $serializerProphecy->normalize($concreteDummy, null, $concreteDummyChildContext)->willReturn(['foo' => 'concrete']);
×
704
        $serializerProphecy->normalize([['foo' => 'concrete']], null, Argument::type('array'))->willReturn([['foo' => 'concrete']]);
×
705

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

709
        $expected = [
×
710
            'children' => [['foo' => 'concrete']],
×
711
        ];
×
712
        $this->assertSame($expected, $normalizer->normalize($dummy, null, [
×
713
            'resources' => [],
×
714
        ]));
×
715
    }
716

717
    public function testDenormalize(): void
718
    {
719
        $data = [
×
720
            'name' => 'foo',
×
721
            'relatedDummy' => '/dummies/1',
×
722
            'relatedDummies' => ['/dummies/2'],
×
723
        ];
×
724

725
        $relatedDummy1 = new RelatedDummy();
×
726
        $relatedDummy2 = new RelatedDummy();
×
727

728
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
729
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['name', 'relatedDummy', 'relatedDummies']));
×
730

731
        // BC layer for api-platform/metadata < 4.1
732
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
733
            $relatedDummyType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class);
×
734
            $relatedDummiesType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $relatedDummyType);
×
735

736
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
737
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
738

739
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
740
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
741
        } else {
742
            $relatedDummyType = Type::object(RelatedDummy::class);
×
743
            $relatedDummiesType = Type::collection(Type::object(ArrayCollection::class), $relatedDummyType, Type::int());
×
744

745
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
746
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
747

748
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn((new ApiProperty())->withNativeType($relatedDummyType)->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
749
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn((new ApiProperty())->withNativeType($relatedDummiesType)->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
750
        }
751

752
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
753
        $iriConverterProphecy->getResourceFromIri('/dummies/1', Argument::type('array'))->willReturn($relatedDummy1);
×
754
        $iriConverterProphecy->getResourceFromIri('/dummies/2', Argument::type('array'))->willReturn($relatedDummy2);
×
755

756
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
757

758
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
759
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
760
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
761
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
762
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
763

764
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
765
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
766

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

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

772
        $this->assertInstanceOf(Dummy::class, $actual);
×
773

774
        $propertyAccessorProphecy->setValue($actual, 'name', 'foo')->shouldHaveBeenCalled();
×
775
        $propertyAccessorProphecy->setValue($actual, 'relatedDummy', $relatedDummy1)->shouldHaveBeenCalled();
×
776
        $propertyAccessorProphecy->setValue($actual, 'relatedDummies', [$relatedDummy2])->shouldHaveBeenCalled();
×
777
    }
778

779
    public function testCanDenormalizeInputClassWithDifferentFieldsThanResourceClass(): void
780
    {
781
        $this->markTestSkipped('TODO: check why this test has been commented');
×
782

783
        // $data = [
784
        //     'dummyName' => 'Dummy Name',
785
        // ];
786
        //
787
        // $context = [
788
        //     'resource_class' => DummyForAdditionalFields::class,
789
        //     'input' => ['class' => DummyForAdditionalFieldsInput::class],
790
        //     'output' => ['class' => DummyForAdditionalFields::class],
791
        // ];
792
        // $augmentedContext = $context + ['api_denormalize' => true];
793
        //
794
        // $preHydratedDummy = new DummyForAdditionalFieldsInput('Name Dummy');
795
        // $cleanedContext = array_diff_key($augmentedContext, [
796
        //     'input' => null,
797
        //     'resource_class' => null,
798
        // ]);
799
        // $cleanedContextWithObjectToPopulate = array_merge($cleanedContext, [
800
        //     AbstractObjectNormalizer::OBJECT_TO_POPULATE => $preHydratedDummy,
801
        //     AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE => true,
802
        // ]);
803
        //
804
        // $dummyInputDto = new DummyForAdditionalFieldsInput('Dummy Name');
805
        // $dummy = new DummyForAdditionalFields('Dummy Name', 'name-dummy');
806
        //
807
        // $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
808
        //
809
        // $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
810
        //
811
        // $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
812
        //
813
        // $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
814
        // $resourceClassResolverProphecy->getResourceClass(null, DummyForAdditionalFields::class)->willReturn(DummyForAdditionalFields::class);
815
        //
816
        // $inputDataTransformerProphecy = $this->prophesize(DataTransformerInitializerInterface::class);
817
        // $inputDataTransformerProphecy->willImplement(DataTransformerInitializerInterface::class);
818
        // $inputDataTransformerProphecy->initialize(DummyForAdditionalFieldsInput::class, $cleanedContext)->willReturn($preHydratedDummy);
819
        // $inputDataTransformerProphecy->supportsTransformation($data, DummyForAdditionalFields::class, $augmentedContext)->willReturn(true);
820
        // $inputDataTransformerProphecy->transform($dummyInputDto, DummyForAdditionalFields::class, $augmentedContext)->willReturn($dummy);
821
        //
822
        // $serializerProphecy = $this->prophesize(SerializerInterface::class);
823
        // $serializerProphecy->willImplement(DenormalizerInterface::class);
824
        // $serializerProphecy->denormalize($data, DummyForAdditionalFieldsInput::class, 'json', $cleanedContextWithObjectToPopulate)->willReturn($dummyInputDto);
825
        //
826
        // $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), null, null, null, [], null, null) extends AbstractItemNormalizer {
827
        // };
828
        // $normalizer->setSerializer($serializerProphecy->reveal());
829
        //
830
        // $actual = $normalizer->denormalize($data, DummyForAdditionalFields::class, 'json', $context);
831
        //
832
        // $this->assertInstanceOf(DummyForAdditionalFields::class, $actual);
833
        // $this->assertSame('Dummy Name', $actual->getName());
834
    }
835

836
    public function testDenormalizeWritableLinks(): void
837
    {
838
        $data = [
×
839
            'name' => 'foo',
×
840
            'relatedDummy' => ['foo' => 'bar'],
×
841
            'relatedDummies' => [['bar' => 'baz']],
×
842
            'relatedDummiesWithUnionTypes' => [0 => ['bar' => 'qux'], 1. => ['bar' => 'quux']],
×
843
        ];
×
844

845
        $relatedDummy1 = new RelatedDummy();
×
846
        $relatedDummy2 = new RelatedDummy();
×
847
        $relatedDummy3 = new RelatedDummy();
×
848
        $relatedDummy4 = new RelatedDummy();
×
849

850
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
851
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['name', 'relatedDummy', 'relatedDummies', 'relatedDummiesWithUnionTypes']));
×
852

853
        // BC layer for api-platform/metadata < 4.1
854
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
855
            $relatedDummyType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class);
×
856
            $relatedDummiesType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $relatedDummyType);
×
857
            $relatedDummiesWithUnionTypesIntType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $relatedDummyType);
×
858
            $relatedDummiesWithUnionTypesFloatType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT), $relatedDummyType);
×
859

860
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
861
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
862
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
863
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
864
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummiesWithUnionTypes', [])->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesWithUnionTypesIntType, $relatedDummiesWithUnionTypesFloatType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
865
        } else {
866
            $relatedDummyType = Type::object(RelatedDummy::class);
×
867
            $relatedDummiesType = Type::collection(Type::object(ArrayCollection::class), $relatedDummyType, Type::int());
×
868
            $relatedDummiesWithUnionTypesIntType = Type::collection(Type::object(ArrayCollection::class), $relatedDummyType, Type::int());
×
869
            $relatedDummiesWithUnionTypesFloatType = Type::collection(Type::object(ArrayCollection::class), $relatedDummyType, Type::float());
×
870

871
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
872
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
873
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn((new ApiProperty())->withNativeType($relatedDummyType)->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
874
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn((new ApiProperty())->withNativeType($relatedDummiesType)->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
875
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummiesWithUnionTypes', [])->willReturn((new ApiProperty())->withNativeType(Type::union($relatedDummiesWithUnionTypesIntType, $relatedDummiesWithUnionTypesFloatType))->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
876
        }
877

878
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
879

880
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
881

882
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
883
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
884
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
885
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
886
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
887
        $resourceClassResolverProphecy->isResourceClass(ArrayCollection::class)->willReturn(false);
×
888

889
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
890
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
891
        $serializerProphecy->denormalize(['foo' => 'bar'], RelatedDummy::class, null, Argument::type('array'))->willReturn($relatedDummy1);
×
892
        $serializerProphecy->denormalize(['bar' => 'baz'], RelatedDummy::class, null, Argument::type('array'))->willReturn($relatedDummy2);
×
893
        $serializerProphecy->denormalize(['bar' => 'qux'], RelatedDummy::class, null, Argument::type('array'))->willReturn($relatedDummy3);
×
894
        $serializerProphecy->denormalize(['bar' => 'quux'], RelatedDummy::class, null, Argument::type('array'))->willReturn($relatedDummy4);
×
895

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

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

901
        $this->assertInstanceOf(Dummy::class, $actual);
×
902

903
        $propertyAccessorProphecy->setValue($actual, 'name', 'foo')->shouldHaveBeenCalled();
×
904
        $propertyAccessorProphecy->setValue($actual, 'relatedDummy', $relatedDummy1)->shouldHaveBeenCalled();
×
905
        $propertyAccessorProphecy->setValue($actual, 'relatedDummies', [$relatedDummy2])->shouldHaveBeenCalled();
×
906
        $propertyAccessorProphecy->setValue($actual, 'relatedDummiesWithUnionTypes', [0 => $relatedDummy3, 1. => $relatedDummy4])->shouldHaveBeenCalled();
×
907
    }
908

909
    public function testBadRelationType(): void
910
    {
911
        $this->expectException(NotNormalizableValueException::class);
×
912
        $this->expectExceptionMessage('The type of the "relatedDummy" attribute must be "array" (nested document) or "string" (IRI), "integer" given.');
×
913

914
        $data = [
×
915
            'relatedDummy' => 22,
×
916
        ];
×
917

918
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
919
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['relatedDummy']));
×
920

921
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
922

923
        // BC layer for api-platform/metadata < 4.1
924
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
925
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn(
×
926
                (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class)])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)
×
927
            );
×
928
        } else {
929
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn(
×
930
                (new ApiProperty())->withNativeType(Type::object(RelatedDummy::class))->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)
×
931
            );
×
932
        }
933

934
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
935

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

942
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
943

944
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
945
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
946

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

950
        $normalizer->denormalize($data, Dummy::class);
×
951
    }
952

953
    public function testBadRelationTypeWithExceptionToValidationErrors(): void
954
    {
955
        $data = [
×
956
            'relatedDummy' => 22,
×
957
        ];
×
958

959
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
960
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['relatedDummy']));
×
961

962
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
963

964
        // BC layer for api-platform/metadata < 4.1
965
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
966
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn(
×
967
                (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class)])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)
×
968
            );
×
969
        } else {
970
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn(
×
971
                (new ApiProperty())->withNativeType(Type::object(RelatedDummy::class))->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)
×
972
            );
×
973
        }
974

975
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
976

977
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
978
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
979
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
980
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
981
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
982

983
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
984

985
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
986
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
987

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

991
        // 'not_normalizable_value_exceptions' is set by Serializer thanks to DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS
992
        $actual = $normalizer->denormalize($data, Dummy::class, null, ['not_normalizable_value_exceptions' => []]);
×
993
        $this->assertNull($actual->relatedDummy);
×
994
    }
995

996
    public function testDeserializationPathForNotDenormalizableRelations(): void
997
    {
998
        $data = [
×
999
            'relatedDummies' => ['wrong'],
×
1000
        ];
×
1001

1002
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1003
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['relatedDummies']));
×
1004

1005
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1006

1007
        // BC layer for api-platform/metadata < 4.1
1008
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1009
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn(
×
1010
                (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)
×
1011
            );
×
1012
        } else {
1013
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn(
×
1014
                (new ApiProperty())->withNativeType(Type::collection(Type::object(ArrayCollection::class), Type::object(RelatedDummy::class)))->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true)
×
1015
            );
×
1016
        }
1017

1018
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1019
        $iriConverterProphecy->getResourceFromIri(Argument::cetera())->willThrow(new InvalidArgumentException('Invalid IRI'));
×
1020

1021
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1022
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1023
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
1024
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1025
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
1026

1027
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1028

1029
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1030
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1031

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

1035
        $errors = [];
×
UNCOV
1036
        $actual = $normalizer->denormalize($data, Dummy::class, null, ['not_normalizable_value_exceptions' => &$errors]);
×
1037
        $this->assertEmpty($actual->relatedDummies);
×
1038
        $this->assertCount(1, $errors); // @phpstan-ignore-line method.impossibleType (false positive)
×
1039
        $this->assertInstanceOf(NotNormalizableValueException::class, $errors[0]);
×
1040
        $this->assertSame('relatedDummies[0]', $errors[0]->getPath());
×
1041
        $this->assertSame('Invalid IRI "wrong".', $errors[0]->getMessage());
×
1042
    }
1043

1044
    public function testDeserializationPathForNotDenormalizableResource(): void
1045
    {
UNCOV
1046
        $this->expectException(NotNormalizableValueException::class);
×
UNCOV
1047
        $this->expectExceptionMessage('Invalid IRI "wrong IRI".');
×
1048

1049
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1050

1051
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1052

1053
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
UNCOV
1054
        $iriConverterProphecy->getResourceFromIri(Argument::cetera())->willThrow(new InvalidArgumentException('Invalid IRI'));
×
1055

1056
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
UNCOV
1057
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1058
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1059

1060
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1061

1062
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
UNCOV
1063
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1064

1065
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
UNCOV
1066
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1067

1068
        $normalizer->denormalize('wrong IRI', Dummy::class, null, ['not_normalizable_value_exceptions' => []]);
×
1069
    }
1070

1071
    public function testDeserializationPathForNotFoundResource(): void
1072
    {
1073
        $this->expectException(NotNormalizableValueException::class);
×
1074
        $this->expectExceptionMessage('Some item not found exception.');
×
1075

1076
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1077

1078
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1079

UNCOV
1080
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1081
        $iriConverterProphecy->getResourceFromIri(Argument::cetera())->willThrow(new ItemNotFoundException('Some item not found exception.'));
×
1082

UNCOV
1083
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
UNCOV
1084
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
UNCOV
1085
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1086

1087
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1088

1089
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
UNCOV
1090
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1091

UNCOV
1092
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
1093
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1094

UNCOV
1095
        $normalizer->denormalize('/some-iri', Dummy::class, null, ['not_normalizable_value_exceptions' => []]);
×
1096
    }
1097

1098
    public function testInnerDocumentNotAllowed(): void
1099
    {
1100
        $this->expectException(UnexpectedValueException::class);
×
UNCOV
1101
        $this->expectExceptionMessage('Nested documents for attribute "relatedDummy" are not allowed. Use IRIs instead.');
×
1102

1103
        $data = [
×
UNCOV
1104
            'relatedDummy' => [
×
1105
                'foo' => 'bar',
×
1106
            ],
×
1107
        ];
×
1108

1109
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1110
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['relatedDummy']));
×
1111

1112
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1113

1114
        // BC layer for api-platform/metadata < 4.1
1115
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1116
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn(
×
1117
                (new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class)])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)
×
UNCOV
1118
            );
×
1119
        } else {
UNCOV
1120
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn(
×
UNCOV
1121
                (new ApiProperty())->withNativeType(Type::object(RelatedDummy::class))->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)
×
UNCOV
1122
            );
×
1123
        }
1124

1125
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1126

1127
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1128
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1129
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
1130
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
1131
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1132

1133
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1134

UNCOV
1135
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1136
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1137

UNCOV
1138
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
1139
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1140

1141
        $normalizer->denormalize($data, Dummy::class);
×
1142
    }
1143

1144
    public function testBadType(): void
1145
    {
1146
        $this->expectException(UnexpectedValueException::class);
×
UNCOV
1147
        $this->expectExceptionMessage('The type of the "foo" attribute must be "float", "integer" given.');
×
1148

1149
        $data = [
×
UNCOV
1150
            'foo' => 42,
×
1151
        ];
×
1152

1153
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1154
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['foo']));
×
1155

UNCOV
1156
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1157

1158
        // BC layer for api-platform/metadata < 4.1
1159
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1160
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1161
        } else {
1162
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', [])->willReturn((new ApiProperty())->withNativeType(Type::float())->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1163
        }
1164

1165
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1166

UNCOV
1167
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
UNCOV
1168
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
UNCOV
1169
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1170

1171
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1172

1173
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1174
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1175

UNCOV
1176
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
1177
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1178

UNCOV
1179
        $normalizer->denormalize($data, Dummy::class);
×
1180
    }
1181

1182
    public function testTypeChecksCanBeDisabled(): void
1183
    {
1184
        $data = [
×
UNCOV
1185
            'foo' => 42,
×
1186
        ];
×
1187

UNCOV
1188
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1189
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['foo']));
×
1190

1191
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1192

1193
        // BC layer for api-platform/metadata < 4.1
UNCOV
1194
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1195
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1196
        } else {
1197
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', [])->willReturn((new ApiProperty())->withNativeType(Type::float())->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1198
        }
1199

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

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

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

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

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

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

UNCOV
1216
        $this->assertInstanceOf(Dummy::class, $actual);
×
1217

1218
        $propertyAccessorProphecy->setValue($actual, 'foo', 42)->shouldHaveBeenCalled();
×
1219
    }
1220

1221
    public function testJsonAllowIntAsFloat(): void
1222
    {
UNCOV
1223
        $data = [
×
1224
            'foo' => 42,
×
UNCOV
1225
        ];
×
1226

1227
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1228
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['foo']));
×
1229

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

1232
        // BC layer for api-platform/metadata < 4.1
1233
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
UNCOV
1234
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1235
        } else {
1236
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', [])->willReturn((new ApiProperty())->withNativeType(Type::float())->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1237
        }
1238

UNCOV
1239
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1240

UNCOV
1241
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1242
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
UNCOV
1243
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1244

UNCOV
1245
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1246

1247
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1248
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1249

UNCOV
1250
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
1251
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1252

UNCOV
1253
        $actual = $normalizer->denormalize($data, Dummy::class, 'jsonfoo');
×
1254

UNCOV
1255
        $this->assertInstanceOf(Dummy::class, $actual);
×
1256

1257
        $propertyAccessorProphecy->setValue($actual, 'foo', 42)->shouldHaveBeenCalled();
×
1258
    }
1259

1260
    public function testDenormalizeBadKeyType(): void
1261
    {
UNCOV
1262
        $this->expectException(NotNormalizableValueException::class);
×
1263
        $this->expectExceptionMessage('The type of the key "a" must be "int", "string" given.');
×
1264

1265
        $data = [
×
1266
            'name' => 'foo',
×
1267
            'relatedDummy' => [
×
UNCOV
1268
                'foo' => 'bar',
×
1269
            ],
×
UNCOV
1270
            'relatedDummies' => [
×
1271
                'a' => [
×
1272
                    'bar' => 'baz',
×
UNCOV
1273
                ],
×
1274
            ],
×
1275
            'relatedDummiesWithUnionTypes' => [
×
UNCOV
1276
                'a' => [
×
1277
                    'bar' => 'baz',
×
UNCOV
1278
                ],
×
1279
            ],
×
UNCOV
1280
        ];
×
1281

UNCOV
1282
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
UNCOV
1283
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['relatedDummies']));
×
1284

UNCOV
1285
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1286

1287
        // BC layer for api-platform/metadata < 4.1
UNCOV
1288
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1289
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
1290
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1291

1292
            $type = new LegacyType(
×
1293
                LegacyType::BUILTIN_TYPE_OBJECT,
×
1294
                false,
×
1295
                ArrayCollection::class,
×
1296
                true,
×
1297
                new LegacyType(LegacyType::BUILTIN_TYPE_INT),
×
1298
                new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class)
×
1299
            );
×
1300
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn((new ApiProperty())->withBuiltinTypes([$type])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
1301
        } else {
1302
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
1303
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn((new ApiProperty())->withNativeType(Type::object(RelatedDummy::class))->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1304

UNCOV
1305
            $type = Type::collection(Type::object(ArrayCollection::class), Type::object(RelatedDummy::class), Type::int());
×
1306
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn((new ApiProperty())->withNativeType($type)->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
1307
        }
1308

1309
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1310

UNCOV
1311
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1312
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1313
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
1314
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
UNCOV
1315
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
1316

1317
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1318

1319
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1320
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1321

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

UNCOV
1325
        $normalizer->denormalize($data, Dummy::class);
×
1326
    }
1327

1328
    public function testNullable(): void
1329
    {
1330
        $data = [
×
UNCOV
1331
            'name' => null,
×
UNCOV
1332
        ];
×
1333

UNCOV
1334
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1335
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['name']));
×
1336

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

1339
        // BC layer for api-platform/metadata < 4.1
UNCOV
1340
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1341
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING, true)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1342
        } else {
1343
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn((new ApiProperty())->withNativeType(Type::nullable(Type::string()))->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)); // @phpstan-ignore-line
×
1344
        }
1345

1346
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1347

UNCOV
1348
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1349
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
UNCOV
1350
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1351

UNCOV
1352
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1353

1354
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1355
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1356

UNCOV
1357
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
1358
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1359

UNCOV
1360
        $actual = $normalizer->denormalize($data, Dummy::class);
×
1361

UNCOV
1362
        $this->assertInstanceOf(Dummy::class, $actual);
×
1363

1364
        $propertyAccessorProphecy->setValue($actual, 'name', null)->shouldHaveBeenCalled();
×
1365
    }
1366

1367
    public function testDenormalizeBasicTypePropertiesFromXml(): void
1368
    {
UNCOV
1369
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1370
        $propertyNameCollectionFactoryProphecy->create(ObjectWithBasicProperties::class, [])->willReturn(new PropertyNameCollection([
×
UNCOV
1371
            'boolTrue1',
×
1372
            'boolFalse1',
×
1373
            'boolTrue2',
×
1374
            'boolFalse2',
×
UNCOV
1375
            'int1',
×
1376
            'int2',
×
UNCOV
1377
            'float1',
×
1378
            'float2',
×
1379
            'float3',
×
UNCOV
1380
            'floatNaN',
×
1381
            'floatInf',
×
1382
            'floatNegInf',
×
UNCOV
1383
        ]));
×
1384

UNCOV
1385
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1386

1387
        // BC layer for api-platform/metadata < 4.1
1388
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
UNCOV
1389
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolTrue1', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_BOOL)])->withDescription('')->withReadable(false)->withWritable(true));
×
UNCOV
1390
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolFalse1', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_BOOL)])->withDescription('')->withReadable(false)->withWritable(true));
×
UNCOV
1391
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolTrue2', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_BOOL)])->withDescription('')->withReadable(false)->withWritable(true));
×
UNCOV
1392
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolFalse2', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_BOOL)])->withDescription('')->withReadable(false)->withWritable(true));
×
1393
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'int1', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)])->withDescription('')->withReadable(false)->withWritable(true));
×
1394
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'int2', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)])->withDescription('')->withReadable(false)->withWritable(true));
×
1395
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'float1', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true));
×
1396
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'float2', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true));
×
1397
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'float3', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true));
×
1398
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'floatNaN', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true));
×
1399
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'floatInf', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true));
×
1400
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'floatNegInf', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true));
×
1401
        } else {
1402
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolTrue1', [])->willReturn((new ApiProperty())->withNativeType(Type::bool())->withDescription('')->withReadable(false)->withWritable(true));
×
1403
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolFalse1', [])->willReturn((new ApiProperty())->withNativeType(Type::bool())->withDescription('')->withReadable(false)->withWritable(true));
×
1404
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolTrue2', [])->willReturn((new ApiProperty())->withNativeType(Type::bool())->withDescription('')->withReadable(false)->withWritable(true));
×
1405
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolFalse2', [])->willReturn((new ApiProperty())->withNativeType(Type::bool())->withDescription('')->withReadable(false)->withWritable(true));
×
1406
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'int1', [])->willReturn((new ApiProperty())->withNativeType(Type::int())->withDescription('')->withReadable(false)->withWritable(true));
×
1407
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'int2', [])->willReturn((new ApiProperty())->withNativeType(Type::int())->withDescription('')->withReadable(false)->withWritable(true));
×
UNCOV
1408
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'float1', [])->willReturn((new ApiProperty())->withNativeType(Type::float())->withDescription('')->withReadable(false)->withWritable(true));
×
1409
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'float2', [])->willReturn((new ApiProperty())->withNativeType(Type::float())->withDescription('')->withReadable(false)->withWritable(true));
×
UNCOV
1410
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'float3', [])->willReturn((new ApiProperty())->withNativeType(Type::float())->withDescription('')->withReadable(false)->withWritable(true));
×
UNCOV
1411
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'floatNaN', [])->willReturn((new ApiProperty())->withNativeType(Type::float())->withDescription('')->withReadable(false)->withWritable(true));
×
1412
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'floatInf', [])->willReturn((new ApiProperty())->withNativeType(Type::float())->withDescription('')->withReadable(false)->withWritable(true));
×
1413
            $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'floatNegInf', [])->willReturn((new ApiProperty())->withNativeType(Type::float())->withDescription('')->withReadable(false)->withWritable(true));
×
1414
        }
1415

1416
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1417

1418
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1419
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'boolTrue1', true)->shouldBeCalled();
×
1420
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'boolFalse1', false)->shouldBeCalled();
×
1421
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'boolTrue2', true)->shouldBeCalled();
×
1422
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'boolFalse2', false)->shouldBeCalled();
×
1423
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'int1', 4711)->shouldBeCalled();
×
1424
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'int2', -4711)->shouldBeCalled();
×
UNCOV
1425
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'float1', Argument::approximate(123.456, 0))->shouldBeCalled();
×
1426
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'float2', Argument::approximate(-1.2344e56, 1))->shouldBeCalled();
×
1427
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'float3', Argument::approximate(45E-6, 1))->shouldBeCalled();
×
1428
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'floatNaN', Argument::that(static fn (float $arg) => is_nan($arg)))->shouldBeCalled();
×
1429
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'floatInf', \INF)->shouldBeCalled();
×
1430
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'floatNegInf', -\INF)->shouldBeCalled();
×
1431

1432
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1433
        $resourceClassResolverProphecy->getResourceClass(null, ObjectWithBasicProperties::class)->willReturn(ObjectWithBasicProperties::class);
×
1434
        $resourceClassResolverProphecy->isResourceClass(ObjectWithBasicProperties::class)->willReturn(true);
×
1435

1436
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1437
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
1438

UNCOV
1439
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
1440
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1441

1442
        $objectWithBasicProperties = $normalizer->denormalize(
×
1443
            [
×
1444
                'boolTrue1' => 'true',
×
1445
                'boolFalse1' => 'false',
×
1446
                'boolTrue2' => '1',
×
1447
                'boolFalse2' => '0',
×
1448
                'int1' => '4711',
×
1449
                'int2' => '-4711',
×
1450
                'float1' => '123.456',
×
1451
                'float2' => '-1.2344e56',
×
1452
                'float3' => '45E-6',
×
1453
                'floatNaN' => 'NaN',
×
1454
                'floatInf' => 'INF',
×
UNCOV
1455
                'floatNegInf' => '-INF',
×
1456
            ],
×
1457
            ObjectWithBasicProperties::class,
×
1458
            'xml'
×
UNCOV
1459
        );
×
1460

1461
        $this->assertInstanceOf(ObjectWithBasicProperties::class, $objectWithBasicProperties);
×
1462
    }
1463

1464
    public function testDenormalizeCollectionDecodedFromXmlWithOneChild(): void
1465
    {
1466
        $data = [
×
1467
            'relatedDummies' => [
×
1468
                'name' => 'foo',
×
1469
            ],
×
1470
        ];
×
1471

1472
        $relatedDummy = new RelatedDummy();
×
1473
        $relatedDummy->setName('foo');
×
1474

1475
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1476
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['relatedDummies']));
×
1477

1478
        // BC layer for api-platform/metadata < 4.1
1479
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1480
            $relatedDummyType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class);
×
1481
            $relatedDummiesType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $relatedDummyType);
×
1482

1483
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
UNCOV
1484
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
1485
        } else {
UNCOV
1486
            $relatedDummyType = Type::object(RelatedDummy::class);
×
UNCOV
1487
            $relatedDummiesType = Type::collection(Type::object(ArrayCollection::class), $relatedDummyType, Type::int());
×
1488

UNCOV
1489
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1490
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn((new ApiProperty())->withNativeType($relatedDummiesType)->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
1491
        }
1492

1493
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1494

UNCOV
1495
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1496
        $propertyAccessorProphecy->setValue(Argument::type(Dummy::class), 'relatedDummies', Argument::type('array'))->shouldBeCalled();
×
1497

UNCOV
1498
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1499
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1500
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
UNCOV
1501
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
UNCOV
1502
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1503

1504
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1505
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
UNCOV
1506
        $serializerProphecy->denormalize(['name' => 'foo'], RelatedDummy::class, 'xml', Argument::type('array'))->willReturn($relatedDummy);
×
1507

1508
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
UNCOV
1509
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1510

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

1514
    public function testDenormalizePopulatingNonCloneableObject(): void
1515
    {
UNCOV
1516
        $dummy = new NonCloneableDummy();
×
1517
        $dummy->setName('foo');
×
1518

1519
        $data = [
×
1520
            'name' => 'bar',
×
UNCOV
1521
        ];
×
1522

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

1526
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1527

1528
        // BC layer for api-platform/metadata < 4.1
1529
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1530
            $propertyMetadataFactoryProphecy->create(NonCloneableDummy::class, 'name', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
1531
        } else {
1532
            $propertyMetadataFactoryProphecy->create(NonCloneableDummy::class, 'name', [])->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
1533
        }
1534

1535
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
UNCOV
1536
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
UNCOV
1537
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
UNCOV
1538
        $resourceClassResolverProphecy->getResourceClass(null, NonCloneableDummy::class)->willReturn(NonCloneableDummy::class);
×
UNCOV
1539
        $resourceClassResolverProphecy->getResourceClass($dummy, NonCloneableDummy::class)->willReturn(NonCloneableDummy::class);
×
1540
        $resourceClassResolverProphecy->isResourceClass(NonCloneableDummy::class)->willReturn(true);
×
1541

UNCOV
1542
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1543
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
1544

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

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

UNCOV
1552
        $this->assertInstanceOf(NonCloneableDummy::class, $actual);
×
1553
        $this->assertSame($dummy, $actual);
×
1554
        $propertyAccessorProphecy->setValue($actual, 'name', 'bar')->shouldHaveBeenCalled();
×
1555
    }
1556

1557
    public function testDenormalizeObjectWithNullDisabledTypeEnforcement(): void
1558
    {
1559
        $data = [
×
1560
            'dummy' => null,
×
1561
        ];
×
1562

1563
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1564
        $propertyNameCollectionFactoryProphecy->create(DtoWithNullValue::class, [])->willReturn(new PropertyNameCollection(['dummy']));
×
1565

1566
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1567

1568
        // BC layer for api-platform/metadata < 4.1
1569
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1570
            $propertyMetadataFactoryProphecy->create(DtoWithNullValue::class, 'dummy', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, nullable: true)])->withDescription('')->withReadable(true)->withWritable(true));
×
1571
        } else {
UNCOV
1572
            $propertyMetadataFactoryProphecy->create(DtoWithNullValue::class, 'dummy', [])->willReturn((new ApiProperty())->withNativeType(Type::nullable(Type::object()))->withDescription('')->withReadable(true)->withWritable(true)); // @phpstan-ignore-line
×
1573
        }
1574

UNCOV
1575
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1576
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1577
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1578
        $resourceClassResolverProphecy->getResourceClass(null, DtoWithNullValue::class)->willReturn(DtoWithNullValue::class);
×
UNCOV
1579
        $resourceClassResolverProphecy->isResourceClass(DtoWithNullValue::class)->willReturn(true);
×
1580

UNCOV
1581
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
UNCOV
1582
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
1583

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

1587
        $context = [AbstractItemNormalizer::DISABLE_TYPE_ENFORCEMENT => true];
×
1588
        $actual = $normalizer->denormalize($data, DtoWithNullValue::class, null, $context);
×
1589

1590
        $this->assertInstanceOf(DtoWithNullValue::class, $actual);
×
UNCOV
1591
        $this->assertEquals(new DtoWithNullValue(), $actual);
×
1592
    }
1593

1594
    public function testCacheKey(): void
1595
    {
1596
        $relatedDummy = new RelatedDummy();
×
1597

UNCOV
1598
        $dummy = new Dummy();
×
1599
        $dummy->setName('foo');
×
1600
        $dummy->setAlias('ignored');
×
1601
        $dummy->setRelatedDummy($relatedDummy);
×
1602
        $dummy->relatedDummies->add(new RelatedDummy());
×
1603

UNCOV
1604
        $relatedDummies = new ArrayCollection([$relatedDummy]);
×
1605

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

1609
        // BC layer for api-platform/metadata < 4.1
UNCOV
1610
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1611
            $relatedDummyType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class);
×
1612
            $relatedDummiesType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $relatedDummyType);
×
1613

1614
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1615
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true));
×
UNCOV
1616
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'alias', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true));
×
UNCOV
1617
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withDescription('')->withReadable(true)->withWritable(false)->withReadableLink(false));
×
UNCOV
1618
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(true)->withWritable(false)->withReadableLink(false));
×
1619
        } else {
1620
            $relatedDummyType = Type::object(RelatedDummy::class);
×
UNCOV
1621
            $relatedDummiesType = Type::collection(Type::object(ArrayCollection::class), $relatedDummyType, Type::int());
×
1622

1623
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1624
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true));
×
1625
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'alias', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true));
×
1626
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType($relatedDummyType)->withDescription('')->withReadable(true)->withWritable(false)->withReadableLink(false));
×
UNCOV
1627
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType($relatedDummiesType)->withReadable(true)->withWritable(false)->withReadableLink(false));
×
1628
        }
1629

1630
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1631
        $iriConverterProphecy->getIriFromResource($dummy, Argument::cetera())->willReturn('/dummies/1');
×
UNCOV
1632
        $iriConverterProphecy->getIriFromResource($relatedDummy, Argument::cetera())->willReturn('/dummies/2');
×
1633

1634
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1635
        $propertyAccessorProphecy->getValue($dummy, 'name')->willReturn('foo');
×
1636
        $propertyAccessorProphecy->getValue($dummy, 'relatedDummy')->willReturn($relatedDummy);
×
UNCOV
1637
        $propertyAccessorProphecy->getValue($dummy, 'relatedDummies')->willReturn($relatedDummies);
×
1638

1639
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1640
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1641
        $resourceClassResolverProphecy->getResourceClass($dummy, null)->willReturn(Dummy::class);
×
1642
        $resourceClassResolverProphecy->getResourceClass($relatedDummy, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
UNCOV
1643
        $resourceClassResolverProphecy->getResourceClass($relatedDummies, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
1644
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1645
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
1646

1647
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1648
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
1649
        $serializerProphecy->normalize('foo', null, Argument::type('array'))->willReturn('foo');
×
1650
        $serializerProphecy->normalize(['/dummies/2'], null, Argument::type('array'))->willReturn(['/dummies/2']);
×
1651

UNCOV
1652
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
UNCOV
1653
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1654

1655
        $expected = [
×
1656
            'name' => 'foo',
×
UNCOV
1657
            'relatedDummy' => '/dummies/2',
×
1658
            'relatedDummies' => ['/dummies/2'],
×
1659
        ];
×
1660
        $this->assertSame($expected, $normalizer->normalize($dummy, null, [
×
1661
            'resources' => [],
×
UNCOV
1662
            'groups' => ['group'],
×
1663
            'ignored_attributes' => ['alias'],
×
1664
            'operation_name' => 'operation_name',
×
1665
            'root_operation_name' => 'root_operation_name',
×
1666
        ]));
×
1667

1668
        $operationCacheKey = (new \ReflectionClass($normalizer))->getProperty('localFactoryOptionsCache')->getValue($normalizer);
×
1669
        $this->assertEquals(array_keys($operationCacheKey), [\sprintf('%s%s%s%s', Dummy::class, 'operation_name', 'root_operation_name', 'n')]);
×
UNCOV
1670
        $this->assertEquals(current($operationCacheKey), ['serializer_groups' => ['group']]);
×
1671
    }
1672
}
1673

1674
class ObjectWithBasicProperties
1675
{
1676
    /** @var bool */
1677
    public $boolTrue1;
1678

1679
    /** @var bool */
1680
    public $boolFalse1;
1681

1682
    /** @var bool */
1683
    public $boolTrue2;
1684

1685
    /** @var bool */
1686
    public $boolFalse2;
1687

1688
    /** @var int */
1689
    public $int1;
1690

1691
    /** @var int */
1692
    public $int2;
1693

1694
    /** @var float */
1695
    public $float1;
1696

1697
    /** @var float */
1698
    public $float2;
1699

1700
    /** @var float */
1701
    public $float3;
1702

1703
    /** @var float */
1704
    public $floatNaN;
1705

1706
    /** @var float */
1707
    public $floatInf;
1708

1709
    /** @var float */
1710
    public $floatNegInf;
1711
}
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