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

api-platform / core / 17729190580

15 Sep 2025 09:57AM UTC coverage: 22.227% (-0.4%) from 22.578%
17729190580

push

github

web-flow
fix(metadata): compute isWritable during updates (#7383)

fixes #7382

4 of 532 new or added lines in 16 files covered. (0.75%)

167 existing lines in 22 files now uncovered.

11127 of 50061 relevant lines covered (22.23%)

23.49 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\Type;
50
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
51
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
52
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
53
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
54
use Symfony\Component\Serializer\SerializerInterface;
55

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

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

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

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

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

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

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

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

99
        $relatedDummies = new ArrayCollection([$relatedDummy]);
×
100

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

104
        $relatedDummyType = new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class);
×
105
        $relatedDummiesType = new Type(Type::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new Type(Type::BUILTIN_TYPE_INT), $relatedDummyType);
×
106

107
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
108
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true));
×
NEW
109
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'alias', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true));
×
NEW
110
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withDescription('')->withReadable(true)->withWritable(false)->withReadableLink(false));
×
NEW
111
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(true)->withWritable(false)->withReadableLink(false));
×
112

113
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
114
        $iriConverterProphecy->getIriFromResource($dummy, Argument::cetera())->willReturn('/dummies/1');
×
115
        $iriConverterProphecy->getIriFromResource($relatedDummy, Argument::cetera())->willReturn('/dummies/2');
×
116

117
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
118
        $propertyAccessorProphecy->getValue($dummy, 'name')->willReturn('foo');
×
119
        $propertyAccessorProphecy->getValue($dummy, 'relatedDummy')->willReturn($relatedDummy);
×
120
        $propertyAccessorProphecy->getValue($dummy, 'relatedDummies')->willReturn($relatedDummies);
×
121

122
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
123
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
124
        $resourceClassResolverProphecy->getResourceClass($dummy, null)->willReturn(Dummy::class);
×
125
        $resourceClassResolverProphecy->getResourceClass($relatedDummy, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
126
        $resourceClassResolverProphecy->getResourceClass($relatedDummies, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
127
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
128
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
129

130
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
131
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
132
        $serializerProphecy->normalize('foo', null, Argument::type('array'))->willReturn('foo');
×
133
        $serializerProphecy->normalize(['/dummies/2'], null, Argument::type('array'))->willReturn(['/dummies/2']);
×
134

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

138
        $expected = [
×
139
            'name' => 'foo',
×
140
            'relatedDummy' => '/dummies/2',
×
141
            'relatedDummies' => ['/dummies/2'],
×
142
        ];
×
143
        $this->assertSame($expected, $normalizer->normalize($dummy, null, [
×
144
            'resources' => [],
×
145
            'ignored_attributes' => ['alias'],
×
146
        ]));
×
147
    }
148

149
    public function testNormalizeWithSecuredProperty(): void
150
    {
151
        $dummy = new SecuredDummy();
×
152
        $dummy->setTitle('myPublicTitle');
×
153
        $dummy->setAdminOnlyProperty('secret');
×
154

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

158
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
159
        $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true));
×
NEW
160
        $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'adminOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withSecurity('is_granted(\'ROLE_ADMIN\')'));
×
161

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

165
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
166
        $propertyAccessorProphecy->getValue($dummy, 'title')->willReturn('myPublicTitle');
×
167
        $propertyAccessorProphecy->getValue($dummy, 'adminOnlyProperty')->willReturn('secret');
×
168

169
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
170
        $resourceClassResolverProphecy->getResourceClass($dummy, null)->willReturn(SecuredDummy::class);
×
171
        $resourceClassResolverProphecy->getResourceClass(null, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
172
        $resourceClassResolverProphecy->isResourceClass(SecuredDummy::class)->willReturn(true);
×
173

174
        $resourceAccessChecker = $this->prophesize(ResourceAccessCheckerInterface::class);
×
175
        $resourceAccessChecker->isGranted(
×
176
            SecuredDummy::class,
×
177
            'is_granted(\'ROLE_ADMIN\')',
×
178
            ['object' => $dummy, 'property' => 'adminOnlyProperty']
×
179
        )->willReturn(false);
×
180

181
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
182
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
183
        $serializerProphecy->normalize('myPublicTitle', null, Argument::type('array'))->willReturn('myPublicTitle');
×
184

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

188
        $expected = [
×
189
            'title' => 'myPublicTitle',
×
190
        ];
×
191
        $this->assertSame($expected, $normalizer->normalize($dummy, null, [
×
192
            'resources' => [],
×
193
        ]));
×
194
    }
195

196
    public function testNormalizePropertyAsIriWithUriTemplate(): void
197
    {
198
        $propertyCollectionIriOnlyRelation = new PropertyCollectionIriOnlyRelation();
×
199
        $propertyCollectionIriOnlyRelation->name = 'My Relation';
×
200

201
        $propertyCollectionIriOnly = new PropertyCollectionIriOnly();
×
202
        $propertyCollectionIriOnly->addPropertyCollectionIriOnlyRelation($propertyCollectionIriOnlyRelation);
×
203

204
        $collectionOperation = new GetCollection('/property-collection-relations');
×
205
        $getIterableOperation = new GetCollection('/parent/{parentId}/another-collection-operations');
×
206
        $getToOneOperation = new Get('/parent/{parentId}/another-collection-operations/{id}');
×
207

208
        $resourceRelationMetadataCollection = new ResourceMetadataCollection(PropertyCollectionIriOnlyRelation::class, [
×
209
            (new ApiResource())->withOperations(new Operations([$collectionOperation, $getIterableOperation, $getToOneOperation])),
×
210
        ]);
×
211

212
        $resourceMetadataCollectionFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
×
213
        $resourceMetadataCollectionFactoryProphecy->create(PropertyCollectionIriOnlyRelation::class)->willReturn($resourceRelationMetadataCollection);
×
214

215
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
NEW
216
        $propertyNameCollectionFactoryProphecy->create(PropertyCollectionIriOnly::class, ['normalization_groups' => null, 'denormalization_groups' => null, 'operation_name' => null, 'api_allow_update' => false])->willReturn(
×
217
            new PropertyNameCollection(['propertyCollectionIriOnlyRelation', 'iterableIri', 'toOneRelation'])
×
218
        );
×
219

220
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
221
        $propertyMetadataFactoryProphecy->create(PropertyCollectionIriOnly::class, 'propertyCollectionIriOnlyRelation', ['normalization_groups' => null, 'denormalization_groups' => null, 'operation_name' => null, 'api_allow_update' => false])->willReturn(
×
222
            (new ApiProperty())->withReadable(true)->withUriTemplate('/property-collection-relations')->withBuiltinTypes([
×
223
                new Type('iterable', false, null, true, new Type('int', false, null, false), new Type('object', false, PropertyCollectionIriOnlyRelation::class, false)),
×
224
            ])
×
225
        );
×
226

NEW
227
        $propertyMetadataFactoryProphecy->create(PropertyCollectionIriOnly::class, 'iterableIri', ['normalization_groups' => null, 'denormalization_groups' => null, 'operation_name' => null, 'api_allow_update' => false])->willReturn(
×
228
            (new ApiProperty())->withReadable(true)->withUriTemplate('/parent/{parentId}/another-collection-operations')->withBuiltinTypes([
×
229
                new Type('iterable', false, null, true, new Type('int', false, null, false), new Type('object', false, PropertyCollectionIriOnlyRelation::class, false)),
×
230
            ])
×
231
        );
×
232

NEW
233
        $propertyMetadataFactoryProphecy->create(PropertyCollectionIriOnly::class, 'toOneRelation', ['normalization_groups' => null, 'denormalization_groups' => null, 'operation_name' => null, 'api_allow_update' => false])->willReturn(
×
234
            (new ApiProperty())->withReadable(true)->withUriTemplate('/parent/{parentId}/another-collection-operations/{id}')->withBuiltinTypes([
×
235
                new Type('object', false, PropertyCollectionIriOnlyRelation::class, false),
×
236
            ])
×
237
        );
×
238

239
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
240
        $iriConverterProphecy->getIriFromResource($propertyCollectionIriOnly, UrlGeneratorInterface::ABS_URL, null, Argument::any())->willReturn('/property-collection-relations', '/parent/42/another-collection-operations');
×
241
        $iriConverterProphecy->getIriFromResource($propertyCollectionIriOnly, UrlGeneratorInterface::ABS_PATH, Argument::type(GetCollection::class), Argument::any())->willReturn('/property-collection-relations', '/parent/42/another-collection-operations');
×
242
        $iriConverterProphecy->getIriFromResource($propertyCollectionIriOnly, UrlGeneratorInterface::ABS_PATH, Argument::type(Get::class), Argument::any())->willReturn('/parent/42/another-collection-operations/24');
×
243

244
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
245
        $propertyAccessorProphecy->getValue($propertyCollectionIriOnly, 'propertyCollectionIriOnlyRelation')->willReturn([$propertyCollectionIriOnlyRelation]);
×
246
        $propertyAccessorProphecy->getValue($propertyCollectionIriOnly, 'iterableIri')->willReturn($propertyCollectionIriOnlyRelation);
×
247
        $propertyAccessorProphecy->getValue($propertyCollectionIriOnly, 'toOneRelation')->willReturn($propertyCollectionIriOnlyRelation);
×
248

249
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
250

251
        $resourceClassResolverProphecy->isResourceClass(PropertyCollectionIriOnly::class)->willReturn(true);
×
252
        $resourceClassResolverProphecy->getResourceClass(null, PropertyCollectionIriOnly::class)->willReturn(PropertyCollectionIriOnly::class);
×
253

254
        $resourceClassResolverProphecy->isResourceClass(PropertyCollectionIriOnlyRelation::class)->willReturn(true);
×
255
        $resourceClassResolverProphecy->getResourceClass([$propertyCollectionIriOnlyRelation], PropertyCollectionIriOnlyRelation::class)->willReturn(PropertyCollectionIriOnlyRelation::class);
×
256

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

260
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
261
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
262
        $normalizer->setSerializer($serializerProphecy->reveal());
×
263

264
        $expected = [
×
265
            'propertyCollectionIriOnlyRelation' => '/property-collection-relations',
×
266
            'iterableIri' => '/parent/42/another-collection-operations',
×
267
            'toOneRelation' => '/parent/42/another-collection-operations/24',
×
268
        ];
×
269

270
        $this->assertSame($expected, $normalizer->normalize($propertyCollectionIriOnly, 'jsonld', [
×
271
            'resources' => [],
×
272
            'root_operation' => new GetCollection('/property_collection_iri_onlies'),
×
273
        ]));
×
274
    }
275

276
    public function testDenormalizeWithSecuredProperty(): void
277
    {
278
        $data = [
×
279
            'title' => 'foo',
×
280
            'adminOnlyProperty' => 'secret',
×
281
        ];
×
282

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

286
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
287
        $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
NEW
288
        $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'adminOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withSecurity('is_granted(\'ROLE_ADMIN\')'));
×
289

290
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
291

292
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
293

294
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
295
        $resourceClassResolverProphecy->getResourceClass(null, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
296
        $resourceClassResolverProphecy->isResourceClass(SecuredDummy::class)->willReturn(true);
×
297

298
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
299
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
300

301
        $resourceAccessChecker = $this->prophesize(ResourceAccessCheckerInterface::class);
×
302
        $resourceAccessChecker->isGranted(
×
303
            SecuredDummy::class,
×
304
            'is_granted(\'ROLE_ADMIN\')',
×
305
            ['object' => null, 'property' => 'adminOnlyProperty']
×
306
        )->willReturn(false);
×
307

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

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

313
        $this->assertInstanceOf(SecuredDummy::class, $actual);
×
314

315
        $propertyAccessorProphecy->setValue($actual, 'title', 'foo')->shouldHaveBeenCalled();
×
316
        $propertyAccessorProphecy->setValue($actual, 'adminOnlyProperty', 'secret')->shouldNotHaveBeenCalled();
×
317
    }
318

319
    public function testDenormalizeCreateWithDeniedPostDenormalizeSecuredProperty(): void
320
    {
321
        $data = [
×
322
            'title' => 'foo',
×
323
            'ownerOnlyProperty' => 'should not be set',
×
324
        ];
×
325

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

329
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
330
        $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
NEW
331
        $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withWritable(true)->withSecurityPostDenormalize('false')->withDefault(''));
×
332

333
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
334

335
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
336

337
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
338
        $resourceClassResolverProphecy->getResourceClass(null, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
339
        $resourceClassResolverProphecy->isResourceClass(SecuredDummy::class)->willReturn(true);
×
340

341
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
342
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
343

344
        $resourceAccessChecker = $this->prophesize(ResourceAccessCheckerInterface::class);
×
345
        $resourceAccessChecker->isGranted(
×
346
            SecuredDummy::class,
×
347
            'false',
×
348
            Argument::any()
×
349
        )->willReturn(false);
×
350

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

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

356
        $this->assertInstanceOf(SecuredDummy::class, $actual);
×
357

358
        $propertyAccessorProphecy->setValue($actual, 'title', 'foo')->shouldHaveBeenCalled();
×
359
        $propertyAccessorProphecy->setValue($actual, 'ownerOnlyProperty', 'should not be set')->shouldHaveBeenCalled();
×
360
        $propertyAccessorProphecy->setValue($actual, 'ownerOnlyProperty', '')->shouldHaveBeenCalled();
×
361
    }
362

363
    public function testDenormalizeUpdateWithSecuredProperty(): void
364
    {
365
        $dummy = new SecuredDummy();
×
366

367
        $data = [
×
368
            'title' => 'foo',
×
369
            'ownerOnlyProperty' => 'secret',
×
370
        ];
×
371

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

375
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
376
        $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
NEW
377
        $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withWritable(true)->withSecurity('true'));
×
378

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

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

383
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
384
        $resourceClassResolverProphecy->getResourceClass($dummy, SecuredDummy::class)->willReturn(SecuredDummy::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
            'true',
×
395
            ['object' => null, 'property' => 'ownerOnlyProperty']
×
396
        )->willReturn(true);
×
397
        $resourceAccessChecker->isGranted(
×
398
            SecuredDummy::class,
×
399
            'true',
×
400
            ['object' => $dummy, 'property' => 'ownerOnlyProperty']
×
401
        )->willReturn(true);
×
402

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

406
        $context = [AbstractItemNormalizer::OBJECT_TO_POPULATE => $dummy];
×
407
        $actual = $normalizer->denormalize($data, SecuredDummy::class, null, $context);
×
408

409
        $this->assertInstanceOf(SecuredDummy::class, $actual);
×
410

411
        $propertyAccessorProphecy->setValue($actual, 'title', 'foo')->shouldHaveBeenCalled();
×
412
        $propertyAccessorProphecy->setValue($actual, 'ownerOnlyProperty', 'secret')->shouldHaveBeenCalled();
×
413
    }
414

415
    public function testDenormalizeUpdateWithDeniedSecuredProperty(): void
416
    {
417
        $dummy = new SecuredDummy();
×
418
        $dummy->setOwnerOnlyProperty('secret');
×
419

420
        $data = [
×
421
            'title' => 'foo',
×
422
            'ownerOnlyProperty' => 'should not be set',
×
423
        ];
×
424

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

428
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
429
        $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
NEW
430
        $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withWritable(true)->withSecurity('false'));
×
431

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

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

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

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

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

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

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

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

464
        $propertyAccessorProphecy->setValue($actual, 'title', 'foo')->shouldHaveBeenCalled();
×
465
        $propertyAccessorProphecy->setValue($actual, 'ownerOnlyProperty', 'should not be set')->shouldNotHaveBeenCalled();
×
466
    }
467

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

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

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

481
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
482
        $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
NEW
483
        $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withWritable(true)->withSecurityPostDenormalize('false'));
×
484

485
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
486

487
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
488
        $propertyAccessorProphecy->getValue($dummy, 'ownerOnlyProperty')->willReturn('secret');
×
489

490
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
491
        $resourceClassResolverProphecy->getResourceClass($dummy, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
492
        $resourceClassResolverProphecy->getResourceClass(null, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
493
        $resourceClassResolverProphecy->isResourceClass(SecuredDummy::class)->willReturn(true);
×
494

495
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
496
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
497

498
        $resourceAccessChecker = $this->prophesize(ResourceAccessCheckerInterface::class);
×
499
        $resourceAccessChecker->isGranted(
×
500
            SecuredDummy::class,
×
501
            'false',
×
502
            ['object' => $dummy, 'previous_object' => $dummy, 'property' => 'ownerOnlyProperty']
×
503
        )->willReturn(false);
×
504

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

508
        $context = [AbstractItemNormalizer::OBJECT_TO_POPULATE => $dummy];
×
509
        $actual = $normalizer->denormalize($data, SecuredDummy::class, null, $context);
×
510

511
        $this->assertInstanceOf(SecuredDummy::class, $actual);
×
512

513
        $propertyAccessorProphecy->setValue($actual, 'title', 'foo')->shouldHaveBeenCalled();
×
514
        $propertyAccessorProphecy->setValue($actual, 'ownerOnlyProperty', 'should not be set')->shouldHaveBeenCalled();
×
515
        $propertyAccessorProphecy->setValue($actual, 'ownerOnlyProperty', 'secret')->shouldHaveBeenCalled();
×
516
    }
517

518
    public function testNormalizeReadableLinks(): void
519
    {
520
        $relatedDummy = new RelatedDummy();
×
521

522
        $dummy = new Dummy();
×
523
        $dummy->setRelatedDummy($relatedDummy);
×
524
        $dummy->relatedDummies->add(new RelatedDummy());
×
525

526
        $relatedDummies = new ArrayCollection([$relatedDummy]);
×
527

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

531
        $relatedDummyType = new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class);
×
532
        $relatedDummiesType = new Type(Type::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new Type(Type::BUILTIN_TYPE_INT), $relatedDummyType);
×
533

534
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
535
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withReadable(true)->withWritable(false)->withReadableLink(true));
×
NEW
536
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(true)->withWritable(false)->withReadableLink(true));
×
537

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

541
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
542
        $propertyAccessorProphecy->getValue($dummy, 'relatedDummy')->willReturn($relatedDummy);
×
543
        $propertyAccessorProphecy->getValue($dummy, 'relatedDummies')->willReturn($relatedDummies);
×
544

545
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
546
        $resourceClassResolverProphecy->getResourceClass($dummy, null)->willReturn(Dummy::class);
×
547
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
548
        $resourceClassResolverProphecy->getResourceClass($relatedDummy, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
549
        $resourceClassResolverProphecy->getResourceClass($relatedDummies, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
550
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
551
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
552

553
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
554
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
555
        $relatedDummyChildContext = Argument::allOf(
×
556
            Argument::type('array'),
×
557
            Argument::withEntry('resource_class', RelatedDummy::class),
×
558
            Argument::not(Argument::withKey('iri')),
×
559
            Argument::not(Argument::withKey('force_resource_class'))
×
560
        );
×
561
        $serializerProphecy->normalize($relatedDummy, null, $relatedDummyChildContext)->willReturn(['foo' => 'hello']);
×
562
        $serializerProphecy->normalize(['foo' => 'hello'], null, Argument::type('array'))->willReturn(['foo' => 'hello']);
×
563
        $serializerProphecy->normalize([['foo' => 'hello']], null, Argument::type('array'))->willReturn([['foo' => 'hello']]);
×
564

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

568
        $expected = [
×
569
            'relatedDummy' => ['foo' => 'hello'],
×
570
            'relatedDummies' => [['foo' => 'hello']],
×
571
        ];
×
572
        $this->assertSame($expected, $normalizer->normalize($dummy, null, [
×
573
            'resources' => [],
×
574
            'force_resource_class' => Dummy::class,
×
575
        ]));
×
576
    }
577

578
    public function testNormalizePolymorphicRelations(): void
579
    {
580
        $concreteDummy = new DummyTableInheritanceChild();
×
581

582
        $dummy = new DummyTableInheritanceRelated();
×
583
        $dummy->addChild($concreteDummy);
×
584

585
        $abstractDummies = new ArrayCollection([$concreteDummy]);
×
586

587
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
NEW
588
        $propertyNameCollectionFactoryProphecy->create(DummyTableInheritanceRelated::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['children']));
×
589

590
        $abstractDummyType = new Type(Type::BUILTIN_TYPE_OBJECT, false, DummyTableInheritance::class);
×
591
        $abstractDummiesType = new Type(Type::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new Type(Type::BUILTIN_TYPE_INT), $abstractDummyType);
×
592

593
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
594
        $propertyMetadataFactoryProphecy->create(DummyTableInheritanceRelated::class, 'children', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$abstractDummiesType])->withReadable(true)->withWritable(false)->withReadableLink(true));
×
595

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

599
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
600
        $propertyAccessorProphecy->getValue($dummy, 'children')->willReturn($abstractDummies);
×
601

602
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
603
        $resourceClassResolverProphecy->getResourceClass($dummy, null)->willReturn(DummyTableInheritanceRelated::class);
×
604
        $resourceClassResolverProphecy->getResourceClass(null, DummyTableInheritanceRelated::class)->willReturn(DummyTableInheritanceRelated::class);
×
605
        $resourceClassResolverProphecy->getResourceClass($concreteDummy, DummyTableInheritance::class)->willReturn(DummyTableInheritanceChild::class);
×
606
        $resourceClassResolverProphecy->getResourceClass($abstractDummies, DummyTableInheritance::class)->willReturn(DummyTableInheritance::class);
×
607
        $resourceClassResolverProphecy->isResourceClass(DummyTableInheritanceRelated::class)->willReturn(true);
×
608
        $resourceClassResolverProphecy->isResourceClass(DummyTableInheritance::class)->willReturn(true);
×
609

610
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
611
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
612
        $concreteDummyChildContext = Argument::allOf(
×
613
            Argument::type('array'),
×
614
            Argument::not(Argument::withKey('iri'))
×
615
        );
×
616
        $serializerProphecy->normalize($concreteDummy, null, $concreteDummyChildContext)->willReturn(['foo' => 'concrete']);
×
617
        $serializerProphecy->normalize([['foo' => 'concrete']], null, Argument::type('array'))->willReturn([['foo' => 'concrete']]);
×
618

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

622
        $expected = [
×
623
            'children' => [['foo' => 'concrete']],
×
624
        ];
×
625
        $this->assertSame($expected, $normalizer->normalize($dummy, null, [
×
626
            'resources' => [],
×
627
        ]));
×
628
    }
629

630
    public function testDenormalize(): void
631
    {
632
        $data = [
×
633
            'name' => 'foo',
×
634
            'relatedDummy' => '/dummies/1',
×
635
            'relatedDummies' => ['/dummies/2'],
×
636
        ];
×
637

638
        $relatedDummy1 = new RelatedDummy();
×
639
        $relatedDummy2 = new RelatedDummy();
×
640

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

644
        $relatedDummyType = new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class);
×
645
        $relatedDummiesType = new Type(Type::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new Type(Type::BUILTIN_TYPE_INT), $relatedDummyType);
×
646

647
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
648
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
649

NEW
650
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
NEW
651
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
652

653
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
654
        $iriConverterProphecy->getResourceFromIri('/dummies/1', Argument::type('array'))->willReturn($relatedDummy1);
×
655
        $iriConverterProphecy->getResourceFromIri('/dummies/2', Argument::type('array'))->willReturn($relatedDummy2);
×
656

657
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
658

659
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
660
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
661
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
662
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
663
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
664

665
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
666
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
667

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

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

673
        $this->assertInstanceOf(Dummy::class, $actual);
×
674

675
        $propertyAccessorProphecy->setValue($actual, 'name', 'foo')->shouldHaveBeenCalled();
×
676
        $propertyAccessorProphecy->setValue($actual, 'relatedDummy', $relatedDummy1)->shouldHaveBeenCalled();
×
677
        $propertyAccessorProphecy->setValue($actual, 'relatedDummies', [$relatedDummy2])->shouldHaveBeenCalled();
×
678
    }
679

680
    public function testCanDenormalizeInputClassWithDifferentFieldsThanResourceClass(): void
681
    {
682
        $this->markTestSkipped('TODO: check why this test has been commented');
×
683

684
        // $data = [
685
        //     'dummyName' => 'Dummy Name',
686
        // ];
687
        //
688
        // $context = [
689
        //     'resource_class' => DummyForAdditionalFields::class,
690
        //     'input' => ['class' => DummyForAdditionalFieldsInput::class],
691
        //     'output' => ['class' => DummyForAdditionalFields::class],
692
        // ];
693
        // $augmentedContext = $context + ['api_denormalize' => true];
694
        //
695
        // $preHydratedDummy = new DummyForAdditionalFieldsInput('Name Dummy');
696
        // $cleanedContext = array_diff_key($augmentedContext, [
697
        //     'input' => null,
698
        //     'resource_class' => null,
699
        // ]);
700
        // $cleanedContextWithObjectToPopulate = array_merge($cleanedContext, [
701
        //     AbstractObjectNormalizer::OBJECT_TO_POPULATE => $preHydratedDummy,
702
        //     AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE => true,
703
        // ]);
704
        //
705
        // $dummyInputDto = new DummyForAdditionalFieldsInput('Dummy Name');
706
        // $dummy = new DummyForAdditionalFields('Dummy Name', 'name-dummy');
707
        //
708
        // $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
709
        //
710
        // $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
711
        //
712
        // $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
713
        //
714
        // $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
715
        // $resourceClassResolverProphecy->getResourceClass(null, DummyForAdditionalFields::class)->willReturn(DummyForAdditionalFields::class);
716
        //
717
        // $inputDataTransformerProphecy = $this->prophesize(DataTransformerInitializerInterface::class);
718
        // $inputDataTransformerProphecy->willImplement(DataTransformerInitializerInterface::class);
719
        // $inputDataTransformerProphecy->initialize(DummyForAdditionalFieldsInput::class, $cleanedContext)->willReturn($preHydratedDummy);
720
        // $inputDataTransformerProphecy->supportsTransformation($data, DummyForAdditionalFields::class, $augmentedContext)->willReturn(true);
721
        // $inputDataTransformerProphecy->transform($dummyInputDto, DummyForAdditionalFields::class, $augmentedContext)->willReturn($dummy);
722
        //
723
        // $serializerProphecy = $this->prophesize(SerializerInterface::class);
724
        // $serializerProphecy->willImplement(DenormalizerInterface::class);
725
        // $serializerProphecy->denormalize($data, DummyForAdditionalFieldsInput::class, 'json', $cleanedContextWithObjectToPopulate)->willReturn($dummyInputDto);
726
        //
727
        // $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), null, null, null, [], null, null) extends AbstractItemNormalizer {
728
        // };
729
        // $normalizer->setSerializer($serializerProphecy->reveal());
730
        //
731
        // $actual = $normalizer->denormalize($data, DummyForAdditionalFields::class, 'json', $context);
732
        //
733
        // $this->assertInstanceOf(DummyForAdditionalFields::class, $actual);
734
        // $this->assertSame('Dummy Name', $actual->getName());
735
    }
736

737
    public function testDenormalizeWritableLinks(): void
738
    {
739
        $data = [
×
740
            'name' => 'foo',
×
741
            'relatedDummy' => ['foo' => 'bar'],
×
742
            'relatedDummies' => [['bar' => 'baz']],
×
743
            'relatedDummiesWithUnionTypes' => [0 => ['bar' => 'qux'], 1. => ['bar' => 'quux']],
×
744
        ];
×
745

746
        $relatedDummy1 = new RelatedDummy();
×
747
        $relatedDummy2 = new RelatedDummy();
×
748
        $relatedDummy3 = new RelatedDummy();
×
749
        $relatedDummy4 = new RelatedDummy();
×
750

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

754
        $relatedDummyType = new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class);
×
755
        $relatedDummiesType = new Type(Type::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new Type(Type::BUILTIN_TYPE_INT), $relatedDummyType);
×
756
        $relatedDummiesWithUnionTypesIntType = new Type(Type::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new Type(Type::BUILTIN_TYPE_INT), $relatedDummyType);
×
757
        $relatedDummiesWithUnionTypesFloatType = new Type(Type::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new Type(Type::BUILTIN_TYPE_FLOAT), $relatedDummyType);
×
758

759
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
760
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
NEW
761
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
NEW
762
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
NEW
763
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummiesWithUnionTypes', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesWithUnionTypesIntType, $relatedDummiesWithUnionTypesFloatType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
764

765
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
766

767
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
768

769
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
770
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
771
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
772
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
773
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
774

775
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
776
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
777
        $serializerProphecy->denormalize(['foo' => 'bar'], RelatedDummy::class, null, Argument::type('array'))->willReturn($relatedDummy1);
×
778
        $serializerProphecy->denormalize(['bar' => 'baz'], RelatedDummy::class, null, Argument::type('array'))->willReturn($relatedDummy2);
×
779
        $serializerProphecy->denormalize(['bar' => 'qux'], RelatedDummy::class, null, Argument::type('array'))->willReturn($relatedDummy3);
×
780
        $serializerProphecy->denormalize(['bar' => 'quux'], RelatedDummy::class, null, Argument::type('array'))->willReturn($relatedDummy4);
×
781

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

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

787
        $this->assertInstanceOf(Dummy::class, $actual);
×
788

789
        $propertyAccessorProphecy->setValue($actual, 'name', 'foo')->shouldHaveBeenCalled();
×
790
        $propertyAccessorProphecy->setValue($actual, 'relatedDummy', $relatedDummy1)->shouldHaveBeenCalled();
×
791
        $propertyAccessorProphecy->setValue($actual, 'relatedDummies', [$relatedDummy2])->shouldHaveBeenCalled();
×
792
        $propertyAccessorProphecy->setValue($actual, 'relatedDummiesWithUnionTypes', [0 => $relatedDummy3, 1. => $relatedDummy4])->shouldHaveBeenCalled();
×
793
    }
794

795
    public function testBadRelationType(): void
796
    {
797
        $this->expectException(NotNormalizableValueException::class);
×
798
        $this->expectExceptionMessage('The type of the "relatedDummy" attribute must be "array" (nested document) or "string" (IRI), "integer" given.');
×
799

800
        $data = [
×
801
            'relatedDummy' => 22,
×
802
        ];
×
803

804
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
NEW
805
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['relatedDummy']));
×
806

807
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
808
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn(
×
809
            (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class)])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)
×
810
        );
×
811

812
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
813

814
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
815
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
816
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
817
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
818
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
819

820
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
821

822
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
823
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
824

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

828
        $normalizer->denormalize($data, Dummy::class);
×
829
    }
830

831
    public function testBadRelationTypeWithExceptionToValidationErrors(): void
832
    {
833
        $data = [
×
834
            'relatedDummy' => 22,
×
835
        ];
×
836

837
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
NEW
838
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['relatedDummy']));
×
839

840
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
841
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn(
×
842
            (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class)])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)
×
843
        );
×
844

845
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
846

847
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
848
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
849
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
850
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
851
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
852

853
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
854

855
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
856
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
857

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

861
        // 'not_normalizable_value_exceptions' is set by Serializer thanks to DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS
862
        $actual = $normalizer->denormalize($data, Dummy::class, null, ['not_normalizable_value_exceptions' => []]);
×
863
        $this->assertNull($actual->relatedDummy);
×
864
    }
865

866
    public function testDeserializationPathForNotDenormalizableRelations(): void
867
    {
868
        $data = [
×
869
            'relatedDummies' => ['wrong'],
×
870
        ];
×
871

872
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
NEW
873
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['relatedDummies']));
×
874

875
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
876
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn(
×
877
            (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, false, null, true, null, new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class))])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true)
×
878
        );
×
879

880
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
881
        $iriConverterProphecy->getResourceFromIri(Argument::cetera())->willThrow(new InvalidArgumentException('Invalid IRI'));
×
882

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

889
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
890

891
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
892
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
893

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

897
        $errors = [];
×
898
        $actual = $normalizer->denormalize($data, Dummy::class, null, ['not_normalizable_value_exceptions' => &$errors]);
×
899
        $this->assertEmpty($actual->relatedDummies);
×
900
        $this->assertCount(1, $errors); // @phpstan-ignore-line method.impossibleType (false positive)
×
901
        $this->assertInstanceOf(NotNormalizableValueException::class, $errors[0]);
×
902
        $this->assertSame('relatedDummies[0]', $errors[0]->getPath());
×
903
        $this->assertSame('Invalid IRI "wrong".', $errors[0]->getMessage());
×
904
    }
905

906
    public function testDeserializationPathForNotDenormalizableResource(): void
907
    {
908
        $this->expectException(NotNormalizableValueException::class);
×
909
        $this->expectExceptionMessage('Invalid IRI "wrong IRI".');
×
910

911
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
NEW
912
        $propertyNameCollectionFactoryProphecy->create(Argument::cetera())->willReturn(new PropertyNameCollection());
×
913

914
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
915

916
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
917
        $iriConverterProphecy->getResourceFromIri(Argument::cetera())->willThrow(new InvalidArgumentException('Invalid IRI'));
×
918

919
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
920
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
921
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
922

923
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
924

925
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
926
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
927

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

931
        $normalizer->denormalize('wrong IRI', Dummy::class, null, ['not_normalizable_value_exceptions' => []]);
×
932
    }
933

934
    public function testDeserializationPathForNotFoundResource(): void
935
    {
936
        $this->expectException(NotNormalizableValueException::class);
×
937
        $this->expectExceptionMessage('Some item not found exception.');
×
938

939
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
940

941
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
942

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

946
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
947
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
948
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
949

950
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
951

952
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
953
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
954

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

958
        $normalizer->denormalize('/some-iri', Dummy::class, null, ['not_normalizable_value_exceptions' => []]);
×
959
    }
960

961
    public function testInnerDocumentNotAllowed(): void
962
    {
963
        $this->expectException(UnexpectedValueException::class);
×
964
        $this->expectExceptionMessage('Nested documents for attribute "relatedDummy" are not allowed. Use IRIs instead.');
×
965

966
        $data = [
×
967
            'relatedDummy' => [
×
968
                'foo' => 'bar',
×
969
            ],
×
970
        ];
×
971

972
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
NEW
973
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['relatedDummy']));
×
974

975
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
976
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn(
×
977
            (new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class)])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)
×
978
        );
×
979

980
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
981

982
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
983
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
984
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
985
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
986
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
987

988
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
989

990
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
991
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
992

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

996
        $normalizer->denormalize($data, Dummy::class);
×
997
    }
998

999
    public function testBadType(): void
1000
    {
1001
        $this->expectException(UnexpectedValueException::class);
×
1002
        $this->expectExceptionMessage('The type of the "foo" attribute must be "float", "integer" given.');
×
1003

1004
        $data = [
×
1005
            'foo' => 42,
×
1006
        ];
×
1007

1008
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
NEW
1009
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['foo']));
×
1010

1011
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
1012
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1013

1014
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1015

1016
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1017
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1018
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1019

1020
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1021

1022
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1023
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1024

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

1028
        $normalizer->denormalize($data, Dummy::class);
×
1029
    }
1030

1031
    public function testTypeChecksCanBeDisabled(): void
1032
    {
1033
        $data = [
×
1034
            'foo' => 42,
×
1035
        ];
×
1036

1037
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
NEW
1038
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['foo']));
×
1039

1040
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
1041
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1042

1043
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1044

1045
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1046
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1047
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1048

1049
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1050

1051
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1052
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1053

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

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

1059
        $this->assertInstanceOf(Dummy::class, $actual);
×
1060

1061
        $propertyAccessorProphecy->setValue($actual, 'foo', 42)->shouldHaveBeenCalled();
×
1062
    }
1063

1064
    public function testJsonAllowIntAsFloat(): void
1065
    {
1066
        $data = [
×
1067
            'foo' => 42,
×
1068
        ];
×
1069

1070
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
NEW
1071
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['foo']));
×
1072

1073
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
1074
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1075

1076
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1077

1078
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1079
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1080
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1081

1082
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1083

1084
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1085
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1086

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

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

1092
        $this->assertInstanceOf(Dummy::class, $actual);
×
1093

1094
        $propertyAccessorProphecy->setValue($actual, 'foo', 42)->shouldHaveBeenCalled();
×
1095
    }
1096

1097
    public function testDenormalizeBadKeyType(): void
1098
    {
1099
        $this->expectException(NotNormalizableValueException::class);
×
1100
        $this->expectExceptionMessage('The type of the key "a" must be "int", "string" given.');
×
1101

1102
        $data = [
×
1103
            'name' => 'foo',
×
1104
            'relatedDummy' => [
×
1105
                'foo' => 'bar',
×
1106
            ],
×
1107
            'relatedDummies' => [
×
1108
                'a' => [
×
1109
                    'bar' => 'baz',
×
1110
                ],
×
1111
            ],
×
1112
            'relatedDummiesWithUnionTypes' => [
×
1113
                'a' => [
×
1114
                    'bar' => 'baz',
×
1115
                ],
×
1116
            ],
×
1117
        ];
×
1118

1119
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
NEW
1120
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['relatedDummies']));
×
1121

1122
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
1123
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
NEW
1124
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1125
        $type = new Type(
×
1126
            Type::BUILTIN_TYPE_OBJECT,
×
1127
            false,
×
1128
            ArrayCollection::class,
×
1129
            true,
×
1130
            new Type(Type::BUILTIN_TYPE_INT),
×
1131
            new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class)
×
1132
        );
×
NEW
1133
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$type])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
1134

1135
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1136

1137
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1138
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1139
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
1140
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1141
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
1142

1143
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1144

1145
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1146
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1147

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

1151
        $normalizer->denormalize($data, Dummy::class);
×
1152
    }
1153

1154
    public function testNullable(): void
1155
    {
1156
        $data = [
×
1157
            'name' => null,
×
1158
        ];
×
1159

1160
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
NEW
1161
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['name']));
×
1162

1163
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
1164
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING, true)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1165

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

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

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

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

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

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

1182
        $this->assertInstanceOf(Dummy::class, $actual);
×
1183

1184
        $propertyAccessorProphecy->setValue($actual, 'name', null)->shouldHaveBeenCalled();
×
1185
    }
1186

1187
    public function testDenormalizeBasicTypePropertiesFromXml(): void
1188
    {
1189
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
NEW
1190
        $propertyNameCollectionFactoryProphecy->create(ObjectWithBasicProperties::class, Argument::type('array'))->willReturn(new PropertyNameCollection([
×
1191
            'boolTrue1',
×
1192
            'boolFalse1',
×
1193
            'boolTrue2',
×
1194
            'boolFalse2',
×
1195
            'int1',
×
1196
            'int2',
×
1197
            'float1',
×
1198
            'float2',
×
1199
            'float3',
×
1200
            'floatNaN',
×
1201
            'floatInf',
×
1202
            'floatNegInf',
×
1203
        ]));
×
1204

1205
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
1206
        $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolTrue1', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withDescription('')->withReadable(false)->withWritable(true));
×
NEW
1207
        $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolFalse1', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withDescription('')->withReadable(false)->withWritable(true));
×
NEW
1208
        $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolTrue2', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withDescription('')->withReadable(false)->withWritable(true));
×
NEW
1209
        $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'boolFalse2', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_BOOL)])->withDescription('')->withReadable(false)->withWritable(true));
×
NEW
1210
        $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'int1', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)])->withDescription('')->withReadable(false)->withWritable(true));
×
NEW
1211
        $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'int2', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)])->withDescription('')->withReadable(false)->withWritable(true));
×
NEW
1212
        $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'float1', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true));
×
NEW
1213
        $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'float2', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true));
×
NEW
1214
        $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'float3', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true));
×
NEW
1215
        $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'floatNaN', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true));
×
NEW
1216
        $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'floatInf', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true));
×
NEW
1217
        $propertyMetadataFactoryProphecy->create(ObjectWithBasicProperties::class, 'floatNegInf', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true));
×
1218

1219
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1220

1221
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1222
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'boolTrue1', true)->shouldBeCalled();
×
1223
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'boolFalse1', false)->shouldBeCalled();
×
1224
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'boolTrue2', true)->shouldBeCalled();
×
1225
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'boolFalse2', false)->shouldBeCalled();
×
1226
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'int1', 4711)->shouldBeCalled();
×
1227
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'int2', -4711)->shouldBeCalled();
×
1228
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'float1', Argument::approximate(123.456, 0))->shouldBeCalled();
×
1229
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'float2', Argument::approximate(-1.2344e56, 1))->shouldBeCalled();
×
1230
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'float3', Argument::approximate(45E-6, 1))->shouldBeCalled();
×
1231
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'floatNaN', Argument::that(static fn (float $arg) => is_nan($arg)))->shouldBeCalled();
×
1232
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'floatInf', \INF)->shouldBeCalled();
×
1233
        $propertyAccessorProphecy->setValue(Argument::type(ObjectWithBasicProperties::class), 'floatNegInf', -\INF)->shouldBeCalled();
×
1234

1235
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1236
        $resourceClassResolverProphecy->getResourceClass(null, ObjectWithBasicProperties::class)->willReturn(ObjectWithBasicProperties::class);
×
1237
        $resourceClassResolverProphecy->isResourceClass(ObjectWithBasicProperties::class)->willReturn(true);
×
1238

1239
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1240
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
1241

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

1245
        $objectWithBasicProperties = $normalizer->denormalize(
×
1246
            [
×
1247
                'boolTrue1' => 'true',
×
1248
                'boolFalse1' => 'false',
×
1249
                'boolTrue2' => '1',
×
1250
                'boolFalse2' => '0',
×
1251
                'int1' => '4711',
×
1252
                'int2' => '-4711',
×
1253
                'float1' => '123.456',
×
1254
                'float2' => '-1.2344e56',
×
1255
                'float3' => '45E-6',
×
1256
                'floatNaN' => 'NaN',
×
1257
                'floatInf' => 'INF',
×
1258
                'floatNegInf' => '-INF',
×
1259
            ],
×
1260
            ObjectWithBasicProperties::class,
×
1261
            'xml'
×
1262
        );
×
1263

1264
        $this->assertInstanceOf(ObjectWithBasicProperties::class, $objectWithBasicProperties);
×
1265
    }
1266

1267
    public function testDenormalizeCollectionDecodedFromXmlWithOneChild(): void
1268
    {
1269
        $data = [
×
1270
            'relatedDummies' => [
×
1271
                'name' => 'foo',
×
1272
            ],
×
1273
        ];
×
1274

1275
        $relatedDummy = new RelatedDummy();
×
1276
        $relatedDummy->setName('foo');
×
1277

1278
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
NEW
1279
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['relatedDummies']));
×
1280

1281
        $relatedDummyType = new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class);
×
1282
        $relatedDummiesType = new Type(Type::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new Type(Type::BUILTIN_TYPE_INT), $relatedDummyType);
×
1283

1284
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
1285
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
1286

1287
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1288

1289
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1290
        $propertyAccessorProphecy->setValue(Argument::type(Dummy::class), 'relatedDummies', Argument::type('array'))->shouldBeCalled();
×
1291

1292
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1293
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1294
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
1295
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
1296
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1297

1298
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1299
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1300
        $serializerProphecy->denormalize(['name' => 'foo'], RelatedDummy::class, 'xml', Argument::type('array'))->willReturn($relatedDummy);
×
1301

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

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

1308
    public function testDenormalizePopulatingNonCloneableObject(): void
1309
    {
1310
        $dummy = new NonCloneableDummy();
×
1311
        $dummy->setName('foo');
×
1312

1313
        $data = [
×
1314
            'name' => 'bar',
×
1315
        ];
×
1316

1317
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
NEW
1318
        $propertyNameCollectionFactoryProphecy->create(NonCloneableDummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['name']));
×
1319

1320
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
1321
        $propertyMetadataFactoryProphecy->create(NonCloneableDummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
1322

1323
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1324
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1325
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1326
        $resourceClassResolverProphecy->getResourceClass(null, NonCloneableDummy::class)->willReturn(NonCloneableDummy::class);
×
1327
        $resourceClassResolverProphecy->getResourceClass($dummy, NonCloneableDummy::class)->willReturn(NonCloneableDummy::class);
×
1328
        $resourceClassResolverProphecy->isResourceClass(NonCloneableDummy::class)->willReturn(true);
×
1329

1330
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1331
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
1332

1333
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
1334
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1335
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1336

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

1340
        $this->assertInstanceOf(NonCloneableDummy::class, $actual);
×
1341
        $this->assertSame($dummy, $actual);
×
1342
        $propertyAccessorProphecy->setValue($actual, 'name', 'bar')->shouldHaveBeenCalled();
×
1343
    }
1344

1345
    public function testDenormalizeObjectWithNullDisabledTypeEnforcement(): void
1346
    {
1347
        $data = [
×
1348
            'dummy' => null,
×
1349
        ];
×
1350

1351
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
NEW
1352
        $propertyNameCollectionFactoryProphecy->create(DtoWithNullValue::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['dummy']));
×
1353

1354
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
1355
        $propertyMetadataFactoryProphecy->create(DtoWithNullValue::class, 'dummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, nullable: true)])->withDescription('')->withReadable(true)->withWritable(true));
×
1356

1357
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1358
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1359
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1360
        $resourceClassResolverProphecy->getResourceClass(null, DtoWithNullValue::class)->willReturn(DtoWithNullValue::class);
×
1361
        $resourceClassResolverProphecy->isResourceClass(DtoWithNullValue::class)->willReturn(true);
×
1362

1363
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1364
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
1365

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

1369
        $context = [AbstractItemNormalizer::DISABLE_TYPE_ENFORCEMENT => true];
×
1370
        $actual = $normalizer->denormalize($data, DtoWithNullValue::class, null, $context);
×
1371

1372
        $this->assertInstanceOf(DtoWithNullValue::class, $actual);
×
1373
        $this->assertEquals(new DtoWithNullValue(), $actual);
×
1374
    }
1375

1376
    public function testCacheKey(): void
1377
    {
1378
        $relatedDummy = new RelatedDummy();
×
1379

1380
        $dummy = new Dummy();
×
1381
        $dummy->setName('foo');
×
1382
        $dummy->setAlias('ignored');
×
1383
        $dummy->setRelatedDummy($relatedDummy);
×
1384
        $dummy->relatedDummies->add(new RelatedDummy());
×
1385

1386
        $relatedDummies = new ArrayCollection([$relatedDummy]);
×
1387

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

1391
        $relatedDummyType = new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class);
×
1392
        $relatedDummiesType = new Type(Type::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new Type(Type::BUILTIN_TYPE_INT), $relatedDummyType);
×
1393

1394
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1395
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true));
×
1396
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'alias', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true));
×
1397
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withDescription('')->withReadable(true)->withWritable(false)->withReadableLink(false));
×
1398
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(true)->withWritable(false)->withReadableLink(false));
×
1399

1400
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1401
        $iriConverterProphecy->getIriFromResource($dummy, Argument::cetera())->willReturn('/dummies/1');
×
1402
        $iriConverterProphecy->getIriFromResource($relatedDummy, Argument::cetera())->willReturn('/dummies/2');
×
1403

1404
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1405
        $propertyAccessorProphecy->getValue($dummy, 'name')->willReturn('foo');
×
1406
        $propertyAccessorProphecy->getValue($dummy, 'relatedDummy')->willReturn($relatedDummy);
×
1407
        $propertyAccessorProphecy->getValue($dummy, 'relatedDummies')->willReturn($relatedDummies);
×
1408

1409
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1410
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1411
        $resourceClassResolverProphecy->getResourceClass($dummy, null)->willReturn(Dummy::class);
×
1412
        $resourceClassResolverProphecy->getResourceClass($relatedDummy, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
1413
        $resourceClassResolverProphecy->getResourceClass($relatedDummies, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
1414
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1415
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
1416

1417
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1418
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
1419
        $serializerProphecy->normalize('foo', null, Argument::type('array'))->willReturn('foo');
×
1420
        $serializerProphecy->normalize(['/dummies/2'], null, Argument::type('array'))->willReturn(['/dummies/2']);
×
1421

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

1425
        $expected = [
×
1426
            'name' => 'foo',
×
1427
            'relatedDummy' => '/dummies/2',
×
1428
            'relatedDummies' => ['/dummies/2'],
×
1429
        ];
×
1430
        $this->assertSame($expected, $normalizer->normalize($dummy, null, [
×
1431
            'resources' => [],
×
1432
            'groups' => ['group'],
×
1433
            'ignored_attributes' => ['alias'],
×
1434
            'operation_name' => 'operation_name',
×
1435
            'root_operation_name' => 'root_operation_name',
×
1436
        ]));
×
1437

1438
        $operationCacheKey = (new \ReflectionClass($normalizer))->getProperty('localFactoryOptionsCache')->getValue($normalizer);
×
1439
        $this->assertEquals(array_keys($operationCacheKey), [\sprintf('%s%s%s%s', Dummy::class, 'operation_name', 'root_operation_name', 'n')]);
×
NEW
1440
        $this->assertEquals(current($operationCacheKey), ['serializer_groups' => ['group'], 'api_allow_update' => false]);
×
1441
    }
1442
}
1443

1444
class ObjectWithBasicProperties
1445
{
1446
    /** @var bool */
1447
    public $boolTrue1;
1448

1449
    /** @var bool */
1450
    public $boolFalse1;
1451

1452
    /** @var bool */
1453
    public $boolTrue2;
1454

1455
    /** @var bool */
1456
    public $boolFalse2;
1457

1458
    /** @var int */
1459
    public $int1;
1460

1461
    /** @var int */
1462
    public $int2;
1463

1464
    /** @var float */
1465
    public $float1;
1466

1467
    /** @var float */
1468
    public $float2;
1469

1470
    /** @var float */
1471
    public $float3;
1472

1473
    /** @var float */
1474
    public $floatNaN;
1475

1476
    /** @var float */
1477
    public $floatInf;
1478

1479
    /** @var float */
1480
    public $floatNegInf;
1481
}
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

© 2026 Coveralls, Inc