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

api-platform / core / 18974957045

31 Oct 2025 02:04PM UTC coverage: 24.541% (-0.06%) from 24.603%
18974957045

push

github

web-flow
Merge 4.1 (#7499)

Co-authored-by: Nicolas LAURENT <aegypius@users.noreply.github.com>
Co-authored-by: Javier Sampedro <jsampedro77@gmail.com>
fix(laravel): serializer attributes on Eloquent methods (#7416)
fixes #7289
fixes #7338
fix(validator): custom message was not translated (#7424)
fixes #7336
fix(serializer): resilient denormalizeRelation capability (#7474)
fix(doctrine): properly set properties according to interface (#7487)
fix(graphql): stateOptions to get filter class (#7485)

11 of 232 new or added lines in 20 files covered. (4.74%)

28 existing lines in 6 files now uncovered.

14064 of 57309 relevant lines covered (24.54%)

26.35 hits per line

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

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

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

12
declare(strict_types=1);
13

14
namespace ApiPlatform\Serializer\Tests;
15

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

174
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
175

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

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

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

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

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

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

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

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

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

224
        $propertyCollectionIriOnly = new PropertyCollectionIriOnly();
×
225
        $propertyCollectionIriOnly->addPropertyCollectionIriOnlyRelation($propertyCollectionIriOnlyRelation);
×
226

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

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

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

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

243
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
244

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

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

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

284
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
285

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

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

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

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

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

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

311
    public function testDenormalizeWithSecuredPropertyAndThrowOnAccessDeniedExtraPropertyInAttributeMetaThrowsAccessDeniedExceptionWithSecurityMessage(): void
312
    {
313
        $data = [
×
314
            'title' => 'foo',
×
315
            'adminOnlyProperty' => 'secret',
×
316
        ];
×
317
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
318
        $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['title', 'adminOnlyProperty']));
×
319

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

322
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
323
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
324
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'adminOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withSecurityPostDenormalize('is_granted(\'ROLE_ADMIN\')')->withExtraProperties(['throw_on_access_denied' => true]));
×
325
        } else {
326
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
327
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'adminOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true)->withSecurityPostDenormalize('is_granted(\'ROLE_ADMIN\')')->withExtraProperties(['throw_on_access_denied' => true]));
×
328
        }
329

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

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

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

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

341
        $resourceAccessChecker = $this->prophesize(ResourceAccessCheckerInterface::class);
×
342
        $resourceAccessChecker->isGranted(
×
343
            SecuredDummy::class,
×
344
            'is_granted(\'ROLE_ADMIN\')',
×
345
            Argument::that(function (array $context) {
×
346
                return \array_key_exists('property', $context)
×
347
                    && \array_key_exists('object', $context)
×
348
                    && \array_key_exists('previous_object', $context)
×
349
                    && 'adminOnlyProperty' === $context['property']
×
350
                    && null === $context['previous_object']
×
351
                    && $context['object'] instanceof SecuredDummy;
×
352
            })
×
353
        )->willReturn(false);
×
354

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

358
        $this->expectException(AccessDeniedException::class);
×
359
        $this->expectExceptionMessage('Access denied');
×
360

361
        $normalizer->denormalize($data, SecuredDummy::class);
×
362
    }
363

364
    public function testDenormalizeWithSecuredPropertyAndThrowOnAccessDeniedExtraPropertyInOperationAndSecurityMessageInOperationThrowsAccessDeniedExceptionWithSecurityMessage(): void
365
    {
366
        $data = [
×
367
            'title' => 'foo',
×
368
            'adminOnlyProperty' => 'secret',
×
369
        ];
×
370
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
371
        $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['title', 'adminOnlyProperty']));
×
372

373
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
374

375
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
376
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
377
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'adminOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withSecurityPostDenormalize('is_granted(\'ROLE_ADMIN\')'));
×
378
        } else {
379
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
380
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'adminOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true)->withSecurityPostDenormalize('is_granted(\'ROLE_ADMIN\')'));
×
381
        }
382

383
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
384

385
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
386

387
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
388
        $resourceClassResolverProphecy->getResourceClass(null, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
389
        $resourceClassResolverProphecy->isResourceClass(SecuredDummy::class)->willReturn(true);
×
390

391
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
392
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
393

394
        $resourceAccessChecker = $this->prophesize(ResourceAccessCheckerInterface::class);
×
395
        $resourceAccessChecker->isGranted(
×
396
            SecuredDummy::class,
×
397
            'is_granted(\'ROLE_ADMIN\')',
×
398
            Argument::that(function (array $context) {
×
399
                return \array_key_exists('property', $context)
×
400
                    && \array_key_exists('object', $context)
×
401
                    && \array_key_exists('previous_object', $context)
×
402
                    && 'adminOnlyProperty' === $context['property']
×
403
                    && null === $context['previous_object']
×
404
                    && $context['object'] instanceof SecuredDummy;
×
405
            })
×
406
        )->willReturn(false);
×
407

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

411
        $this->expectException(AccessDeniedException::class);
×
412
        $this->expectExceptionMessage('Custom access denied message');
×
413

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

416
        $normalizer->denormalize($data, SecuredDummy::class, 'json', [
×
417
            'operation' => $operation,
×
418
        ]);
×
419
    }
420

421
    public function testDenormalizeWithSecuredPropertyAndThrowOnAccessDeniedExtraPropertyInOperationThrowsAccessDeniedException(): void
422
    {
423
        $data = [
×
424
            'title' => 'foo',
×
425
            'adminOnlyProperty' => 'secret',
×
426
        ];
×
427
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
428
        $propertyNameCollectionFactoryProphecy->create(SecuredDummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['title', 'adminOnlyProperty']));
×
429

430
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
431

432
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
433
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
434
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'adminOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withSecurityPostDenormalize('is_granted(\'ROLE_ADMIN\')'));
×
435
        } else {
436
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
437
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'adminOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true)->withSecurityPostDenormalize('is_granted(\'ROLE_ADMIN\')'));
×
438
        }
439

440
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
441

442
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
443

444
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
445
        $resourceClassResolverProphecy->getResourceClass(null, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
446
        $resourceClassResolverProphecy->isResourceClass(SecuredDummy::class)->willReturn(true);
×
447

448
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
449
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
450

451
        $resourceAccessChecker = $this->prophesize(ResourceAccessCheckerInterface::class);
×
452
        $resourceAccessChecker->isGranted(
×
453
            SecuredDummy::class,
×
454
            'is_granted(\'ROLE_ADMIN\')',
×
455
            Argument::that(function (array $context) {
×
456
                return \array_key_exists('property', $context)
×
457
                    && \array_key_exists('object', $context)
×
458
                    && \array_key_exists('previous_object', $context)
×
459
                    && 'adminOnlyProperty' === $context['property']
×
460
                    && null === $context['previous_object']
×
461
                    && $context['object'] instanceof SecuredDummy;
×
462
            })
×
463
        )->willReturn(false);
×
464

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

468
        $this->expectException(AccessDeniedException::class);
×
469
        $this->expectExceptionMessage('Access denied');
×
470

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

473
        $normalizer->denormalize($data, SecuredDummy::class, 'json', [
×
474
            'operation' => $operation,
×
475
        ]);
×
476
    }
477

478
    public function testDenormalizeWithSecuredProperty(): void
479
    {
480
        $data = [
×
481
            'title' => 'foo',
×
482
            'adminOnlyProperty' => 'secret',
×
483
        ];
×
484

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

488
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
489

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

499
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
500

501
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
502

503
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
504
        $resourceClassResolverProphecy->getResourceClass(null, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
505
        $resourceClassResolverProphecy->isResourceClass(SecuredDummy::class)->willReturn(true);
×
506

507
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
508
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
509

510
        $resourceAccessChecker = $this->prophesize(ResourceAccessCheckerInterface::class);
×
511
        $resourceAccessChecker->isGranted(
×
512
            SecuredDummy::class,
×
513
            'is_granted(\'ROLE_ADMIN\')',
×
514
            ['object' => null, 'property' => 'adminOnlyProperty']
×
515
        )->willReturn(false);
×
516

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

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

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

524
        $propertyAccessorProphecy->setValue($actual, 'title', 'foo')->shouldHaveBeenCalled();
×
525
        $propertyAccessorProphecy->setValue($actual, 'adminOnlyProperty', 'secret')->shouldNotHaveBeenCalled();
×
526
    }
527

528
    public function testDenormalizeCreateWithDeniedPostDenormalizeSecuredProperty(): void
529
    {
530
        $data = [
×
531
            'title' => 'foo',
×
532
            'ownerOnlyProperty' => 'should not be set',
×
533
        ];
×
534

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

538
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
539

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

549
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
550

551
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
552

553
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
554
        $resourceClassResolverProphecy->getResourceClass(null, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
555
        $resourceClassResolverProphecy->isResourceClass(SecuredDummy::class)->willReturn(true);
×
556

557
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
558
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
559

560
        $resourceAccessChecker = $this->prophesize(ResourceAccessCheckerInterface::class);
×
561
        $resourceAccessChecker->isGranted(
×
562
            SecuredDummy::class,
×
563
            'false',
×
564
            Argument::any()
×
565
        )->willReturn(false);
×
566

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

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

572
        $this->assertInstanceOf(SecuredDummy::class, $actual);
×
573

574
        $propertyAccessorProphecy->setValue($actual, 'title', 'foo')->shouldHaveBeenCalled();
×
575
        $propertyAccessorProphecy->setValue($actual, 'ownerOnlyProperty', 'should not be set')->shouldHaveBeenCalled();
×
576
        $propertyAccessorProphecy->setValue($actual, 'ownerOnlyProperty', '')->shouldHaveBeenCalled();
×
577
    }
578

579
    public function testDenormalizeUpdateWithSecuredProperty(): void
580
    {
581
        $dummy = new SecuredDummy();
×
582

583
        $data = [
×
584
            'title' => 'foo',
×
585
            'ownerOnlyProperty' => 'secret',
×
586
        ];
×
587

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

591
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
592

593
        // BC layer for api-platform/metadata < 4.1
594
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
595
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
596
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withWritable(true)->withSecurity('true'));
×
597
        } else {
598
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
599
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true)->withWritable(true)->withSecurity('true'));
×
600
        }
601

602
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
603

604
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
605

606
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
607
        $resourceClassResolverProphecy->getResourceClass($dummy, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
608
        $resourceClassResolverProphecy->getResourceClass(null, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
609
        $resourceClassResolverProphecy->isResourceClass(SecuredDummy::class)->willReturn(true);
×
610

611
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
612
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
613

614
        $resourceAccessChecker = $this->prophesize(ResourceAccessCheckerInterface::class);
×
615
        $resourceAccessChecker->isGranted(
×
616
            SecuredDummy::class,
×
617
            'true',
×
618
            ['object' => null, 'property' => 'ownerOnlyProperty']
×
619
        )->willReturn(true);
×
620
        $resourceAccessChecker->isGranted(
×
621
            SecuredDummy::class,
×
622
            'true',
×
623
            ['object' => $dummy, 'property' => 'ownerOnlyProperty']
×
624
        )->willReturn(true);
×
625

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

629
        $context = [AbstractItemNormalizer::OBJECT_TO_POPULATE => $dummy];
×
630
        $actual = $normalizer->denormalize($data, SecuredDummy::class, null, $context);
×
631

632
        $this->assertInstanceOf(SecuredDummy::class, $actual);
×
633

634
        $propertyAccessorProphecy->setValue($actual, 'title', 'foo')->shouldHaveBeenCalled();
×
635
        $propertyAccessorProphecy->setValue($actual, 'ownerOnlyProperty', 'secret')->shouldHaveBeenCalled();
×
636
    }
637

638
    public function testDenormalizeUpdateWithDeniedSecuredProperty(): void
639
    {
640
        $dummy = new SecuredDummy();
×
641
        $dummy->setOwnerOnlyProperty('secret');
×
642

643
        $data = [
×
644
            'title' => 'foo',
×
645
            'ownerOnlyProperty' => 'should not be set',
×
646
        ];
×
647

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

651
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
652

653
        // BC layer for api-platform/metadata < 4.1
654
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
655
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
656
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withWritable(true)->withSecurity('false'));
×
657
        } else {
658
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
659
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true)->withWritable(true)->withSecurity('false'));
×
660
        }
661

662
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
663

664
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
665

666
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
667
        $resourceClassResolverProphecy->getResourceClass(null, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
668
        $resourceClassResolverProphecy->getResourceClass($dummy, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
669
        $resourceClassResolverProphecy->isResourceClass(SecuredDummy::class)->willReturn(true);
×
670

671
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
672
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
673

674
        $resourceAccessChecker = $this->prophesize(ResourceAccessCheckerInterface::class);
×
675
        $resourceAccessChecker->isGranted(
×
676
            SecuredDummy::class,
×
677
            'false',
×
678
            ['object' => null, 'property' => 'ownerOnlyProperty']
×
679
        )->willReturn(false);
×
680
        $resourceAccessChecker->isGranted(
×
681
            SecuredDummy::class,
×
682
            'false',
×
683
            ['object' => $dummy, 'property' => 'ownerOnlyProperty']
×
684
        )->willReturn(false);
×
685

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

689
        $context = [AbstractItemNormalizer::OBJECT_TO_POPULATE => $dummy];
×
690
        $actual = $normalizer->denormalize($data, SecuredDummy::class, null, $context);
×
691

692
        $this->assertInstanceOf(SecuredDummy::class, $actual);
×
693

694
        $propertyAccessorProphecy->setValue($actual, 'title', 'foo')->shouldHaveBeenCalled();
×
695
        $propertyAccessorProphecy->setValue($actual, 'ownerOnlyProperty', 'should not be set')->shouldNotHaveBeenCalled();
×
696
    }
697

698
    public function testDenormalizeUpdateWithDeniedPostDenormalizeSecuredProperty(): void
699
    {
700
        $dummy = new SecuredDummy();
×
701
        $dummy->setOwnerOnlyProperty('secret');
×
702

703
        $data = [
×
704
            'title' => 'foo',
×
705
            'ownerOnlyProperty' => 'should not be set',
×
706
        ];
×
707

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

711
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
712

713
        // BC layer for api-platform/metadata < 4.1
714
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
715
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
716
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true)->withWritable(true)->withSecurityPostDenormalize('false'));
×
717
        } else {
718
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'title', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
719
            $propertyMetadataFactoryProphecy->create(SecuredDummy::class, 'ownerOnlyProperty', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true)->withWritable(true)->withSecurityPostDenormalize('false'));
×
720
        }
721

722
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
723

724
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
725
        $propertyAccessorProphecy->getValue($dummy, 'ownerOnlyProperty')->willReturn('secret');
×
726

727
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
728
        $resourceClassResolverProphecy->getResourceClass($dummy, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
729
        $resourceClassResolverProphecy->getResourceClass(null, SecuredDummy::class)->willReturn(SecuredDummy::class);
×
730
        $resourceClassResolverProphecy->isResourceClass(SecuredDummy::class)->willReturn(true);
×
731

732
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
733
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
734

735
        $resourceAccessChecker = $this->prophesize(ResourceAccessCheckerInterface::class);
×
736
        $resourceAccessChecker->isGranted(
×
737
            SecuredDummy::class,
×
738
            'false',
×
739
            ['object' => $dummy, 'previous_object' => $dummy, 'property' => 'ownerOnlyProperty']
×
740
        )->willReturn(false);
×
741

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

745
        $context = [AbstractItemNormalizer::OBJECT_TO_POPULATE => $dummy];
×
746
        $actual = $normalizer->denormalize($data, SecuredDummy::class, null, $context);
×
747

748
        $this->assertInstanceOf(SecuredDummy::class, $actual);
×
749

750
        $propertyAccessorProphecy->setValue($actual, 'title', 'foo')->shouldHaveBeenCalled();
×
751
        $propertyAccessorProphecy->setValue($actual, 'ownerOnlyProperty', 'should not be set')->shouldHaveBeenCalled();
×
752
        $propertyAccessorProphecy->setValue($actual, 'ownerOnlyProperty', 'secret')->shouldHaveBeenCalled();
×
753
    }
754

755
    public function testNormalizeReadableLinks(): void
756
    {
757
        $relatedDummy = new RelatedDummy();
×
758

759
        $dummy = new Dummy();
×
760
        $dummy->setRelatedDummy($relatedDummy);
×
761
        $dummy->relatedDummies->add(new RelatedDummy());
×
762

763
        $relatedDummies = new ArrayCollection([$relatedDummy]);
×
764

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

768
        // BC layer for api-platform/metadata < 4.1
769
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
770
            $relatedDummyType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class);
×
771
            $relatedDummiesType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $relatedDummyType);
×
772

773
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
774
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withReadable(true)->withWritable(false)->withReadableLink(true));
×
775
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(true)->withWritable(false)->withReadableLink(true));
×
776
        } else {
777
            $relatedDummyType = Type::object(RelatedDummy::class);
×
778
            $relatedDummiesType = Type::collection(Type::object(ArrayCollection::class), $relatedDummyType, Type::int());
×
779

780
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
781
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType($relatedDummyType)->withReadable(true)->withWritable(false)->withReadableLink(true));
×
782
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType($relatedDummiesType)->withReadable(true)->withWritable(false)->withReadableLink(true));
×
783
        }
784

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

788
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
789
        $propertyAccessorProphecy->getValue($dummy, 'relatedDummy')->willReturn($relatedDummy);
×
790
        $propertyAccessorProphecy->getValue($dummy, 'relatedDummies')->willReturn($relatedDummies);
×
791

792
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
793
        $resourceClassResolverProphecy->getResourceClass($dummy, null)->willReturn(Dummy::class);
×
794
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
795
        $resourceClassResolverProphecy->getResourceClass($relatedDummy, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
796
        $resourceClassResolverProphecy->getResourceClass($relatedDummies, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
797
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
798
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
799

800
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
801
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
802
        $relatedDummyChildContext = Argument::allOf(
×
803
            Argument::type('array'),
×
804
            Argument::withEntry('resource_class', RelatedDummy::class),
×
805
            Argument::not(Argument::withKey('iri')),
×
806
            Argument::not(Argument::withKey('force_resource_class'))
×
807
        );
×
808
        $serializerProphecy->normalize($relatedDummy, null, $relatedDummyChildContext)->willReturn(['foo' => 'hello']);
×
809
        $serializerProphecy->normalize(['foo' => 'hello'], null, Argument::type('array'))->willReturn(['foo' => 'hello']);
×
810
        $serializerProphecy->normalize([['foo' => 'hello']], null, Argument::type('array'))->willReturn([['foo' => 'hello']]);
×
811

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

815
        $expected = [
×
816
            'relatedDummy' => ['foo' => 'hello'],
×
817
            'relatedDummies' => [['foo' => 'hello']],
×
818
        ];
×
819
        $this->assertSame($expected, $normalizer->normalize($dummy, null, [
×
820
            'resources' => [],
×
821
            'force_resource_class' => Dummy::class,
×
822
        ]));
×
823
    }
824

825
    public function testNormalizePolymorphicRelations(): void
826
    {
827
        $concreteDummy = new DummyTableInheritanceChild();
×
828

829
        $dummy = new DummyTableInheritanceRelated();
×
830
        $dummy->addChild($concreteDummy);
×
831

832
        $abstractDummies = new ArrayCollection([$concreteDummy]);
×
833

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

837
        // BC layer for api-platform/metadata < 4.1
838
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
839
            $abstractDummyType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, DummyTableInheritance::class);
×
840
            $abstractDummiesType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $abstractDummyType);
×
841

842
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
843
            $propertyMetadataFactoryProphecy->create(DummyTableInheritanceRelated::class, 'children', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$abstractDummiesType])->withReadable(true)->withWritable(false)->withReadableLink(true));
×
844
        } else {
845
            $abstractDummyType = Type::object(DummyTableInheritance::class);
×
846
            $abstractDummiesType = Type::collection(Type::object(ArrayCollection::class), $abstractDummyType, Type::int());
×
847

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

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

855
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
856
        $propertyAccessorProphecy->getValue($dummy, 'children')->willReturn($abstractDummies);
×
857

858
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
859
        $resourceClassResolverProphecy->getResourceClass($dummy, null)->willReturn(DummyTableInheritanceRelated::class);
×
860
        $resourceClassResolverProphecy->getResourceClass(null, DummyTableInheritanceRelated::class)->willReturn(DummyTableInheritanceRelated::class);
×
861
        $resourceClassResolverProphecy->getResourceClass($concreteDummy, DummyTableInheritance::class)->willReturn(DummyTableInheritanceChild::class);
×
862
        $resourceClassResolverProphecy->getResourceClass($abstractDummies, DummyTableInheritance::class)->willReturn(DummyTableInheritance::class);
×
863
        $resourceClassResolverProphecy->isResourceClass(DummyTableInheritanceRelated::class)->willReturn(true);
×
864
        $resourceClassResolverProphecy->isResourceClass(DummyTableInheritance::class)->willReturn(true);
×
865

866
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
867
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
868
        $concreteDummyChildContext = Argument::allOf(
×
869
            Argument::type('array'),
×
870
            Argument::not(Argument::withKey('iri'))
×
871
        );
×
872
        $serializerProphecy->normalize($concreteDummy, null, $concreteDummyChildContext)->willReturn(['foo' => 'concrete']);
×
873
        $serializerProphecy->normalize([['foo' => 'concrete']], null, Argument::type('array'))->willReturn([['foo' => 'concrete']]);
×
874

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

878
        $expected = [
×
879
            'children' => [['foo' => 'concrete']],
×
880
        ];
×
881
        $this->assertSame($expected, $normalizer->normalize($dummy, null, [
×
882
            'resources' => [],
×
883
        ]));
×
884
    }
885

886
    public function testDenormalize(): void
887
    {
888
        $data = [
×
889
            'name' => 'foo',
×
890
            'relatedDummy' => '/dummies/1',
×
891
            'relatedDummies' => ['/dummies/2'],
×
892
        ];
×
893

894
        $relatedDummy1 = new RelatedDummy();
×
895
        $relatedDummy2 = new RelatedDummy();
×
896

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

900
        // BC layer for api-platform/metadata < 4.1
901
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
902
            $relatedDummyType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class);
×
903
            $relatedDummiesType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $relatedDummyType);
×
904

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

908
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
909
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
910
        } else {
911
            $relatedDummyType = Type::object(RelatedDummy::class);
×
912
            $relatedDummiesType = Type::collection(Type::object(ArrayCollection::class), $relatedDummyType, Type::int());
×
913

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

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

921
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
922
        $iriConverterProphecy->getResourceFromIri('/dummies/1', Argument::type('array'))->willReturn($relatedDummy1);
×
923
        $iriConverterProphecy->getResourceFromIri('/dummies/2', Argument::type('array'))->willReturn($relatedDummy2);
×
924

925
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
926

927
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
928
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
929
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
930
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
931
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
932

933
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
934
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
935

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

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

941
        $this->assertInstanceOf(Dummy::class, $actual);
×
942

943
        $propertyAccessorProphecy->setValue($actual, 'name', 'foo')->shouldHaveBeenCalled();
×
944
        $propertyAccessorProphecy->setValue($actual, 'relatedDummy', $relatedDummy1)->shouldHaveBeenCalled();
×
945
        $propertyAccessorProphecy->setValue($actual, 'relatedDummies', [$relatedDummy2])->shouldHaveBeenCalled();
×
946
    }
947

948
    public function testCanDenormalizeInputClassWithDifferentFieldsThanResourceClass(): void
949
    {
950
        $this->markTestSkipped('TODO: check why this test has been commented');
×
951

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

1005
    public function testDenormalizeWritableLinks(): void
1006
    {
1007
        $data = [
×
1008
            'name' => 'foo',
×
1009
            'relatedDummy' => ['foo' => 'bar'],
×
1010
            'relatedDummies' => [['bar' => 'baz']],
×
1011
            'relatedDummiesWithUnionTypes' => [0 => ['bar' => 'qux'], 1 => ['bar' => 'quux']],
×
1012
        ];
×
1013

1014
        $relatedDummy1 = new RelatedDummy();
×
1015
        $relatedDummy2 = new RelatedDummy();
×
1016
        $relatedDummy3 = new RelatedDummy();
×
1017
        $relatedDummy4 = new RelatedDummy();
×
1018

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

1022
        // BC layer for api-platform/metadata < 4.1
1023
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1024
            $relatedDummyType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class);
×
1025
            $relatedDummiesType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $relatedDummyType);
×
1026
            $relatedDummiesWithUnionTypesIntType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $relatedDummyType);
×
1027
            $relatedDummiesWithUnionTypesFloatType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT), $relatedDummyType);
×
1028

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

1040
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1041
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
1042
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType($relatedDummyType)->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
1043
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType($relatedDummiesType)->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
1044
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummiesWithUnionTypes', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::union($relatedDummiesWithUnionTypesIntType, $relatedDummiesWithUnionTypesFloatType))->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
1045
        }
1046

1047
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1048

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

1051
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1052
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1053
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
1054
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1055
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
1056
        $resourceClassResolverProphecy->isResourceClass(ArrayCollection::class)->willReturn(false);
×
1057

1058
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1059
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1060
        $serializerProphecy->denormalize(['foo' => 'bar'], RelatedDummy::class, null, Argument::type('array'))->willReturn($relatedDummy1);
×
1061
        $serializerProphecy->denormalize(['bar' => 'baz'], RelatedDummy::class, null, Argument::type('array'))->willReturn($relatedDummy2);
×
1062
        $serializerProphecy->denormalize(['bar' => 'qux'], RelatedDummy::class, null, Argument::type('array'))->willReturn($relatedDummy3);
×
1063
        $serializerProphecy->denormalize(['bar' => 'quux'], RelatedDummy::class, null, Argument::type('array'))->willReturn($relatedDummy4);
×
1064

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

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

1070
        $this->assertInstanceOf(Dummy::class, $actual);
×
1071

1072
        $propertyAccessorProphecy->setValue($actual, 'name', 'foo')->shouldHaveBeenCalled();
×
1073
        $propertyAccessorProphecy->setValue($actual, 'relatedDummy', $relatedDummy1)->shouldHaveBeenCalled();
×
1074
        $propertyAccessorProphecy->setValue($actual, 'relatedDummies', [$relatedDummy2])->shouldHaveBeenCalled();
×
1075
        $propertyAccessorProphecy->setValue($actual, 'relatedDummiesWithUnionTypes', [0 => $relatedDummy3, 1 => $relatedDummy4])->shouldHaveBeenCalled();
×
1076
    }
1077

1078
    public function testDenormalizeRelationNotFoundReturnsNull(): void
1079
    {
NEW
1080
        $data = [
×
NEW
1081
            'relatedDummy' => '/dummies/not_found',
×
NEW
1082
        ];
×
1083

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

1087
        // BC layer for api-platform/metadata < 4.1
NEW
1088
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
NEW
1089
            $relatedDummyType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class);
×
1090

NEW
1091
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
1092
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1093
        } else {
NEW
1094
            $relatedDummyType = Type::object(RelatedDummy::class);
×
1095

NEW
1096
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
1097
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType($relatedDummyType)->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1098
        }
1099

NEW
1100
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
NEW
1101
        $iriConverterProphecy->getResourceFromIri('/dummies/not_found', Argument::type('array'))->willThrow(new ItemNotFoundException());
×
1102

NEW
1103
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1104

NEW
1105
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
NEW
1106
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
NEW
1107
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
NEW
1108
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
NEW
1109
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1110

NEW
1111
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
NEW
1112
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1113

NEW
1114
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
NEW
1115
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1116

NEW
1117
        $actual = $normalizer->denormalize($data, Dummy::class, null, [
×
NEW
1118
            'denormalize_throw_on_relation_not_found' => false,
×
NEW
1119
            'not_normalizable_value_exceptions' => [],
×
NEW
1120
        ]);
×
1121

NEW
1122
        $this->assertInstanceOf(Dummy::class, $actual);
×
NEW
1123
        $propertyAccessorProphecy->setValue($actual, 'relatedDummy', null)->shouldHaveBeenCalled();
×
1124
    }
1125

1126
    public function testBadRelationType(): void
1127
    {
1128
        $this->expectException(NotNormalizableValueException::class);
×
1129
        $this->expectExceptionMessage('The type of the "relatedDummy" attribute must be "array" (nested document) or "string" (IRI), "integer" given.');
×
1130

1131
        $data = [
×
1132
            'relatedDummy' => 22,
×
1133
        ];
×
1134

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

1138
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1139

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

1151
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1152

1153
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1154
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1155
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
1156
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1157
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
1158

1159
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1160

1161
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1162
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1163

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

1167
        $normalizer->denormalize($data, Dummy::class);
×
1168
    }
1169

1170
    public function testBadRelationTypeWithExceptionToValidationErrors(): void
1171
    {
1172
        $data = [
×
1173
            'relatedDummy' => 22,
×
1174
        ];
×
1175

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

1179
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1180

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

1192
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1193

1194
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1195
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1196
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
1197
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1198
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
1199

1200
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1201

1202
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1203
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1204

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

1208
        // 'not_normalizable_value_exceptions' is set by Serializer thanks to DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS
1209
        $actual = $normalizer->denormalize($data, Dummy::class, null, ['not_normalizable_value_exceptions' => []]);
×
1210
        $this->assertNull($actual->relatedDummy);
×
1211
    }
1212

1213
    public function testDeserializationPathForNotDenormalizableRelations(): void
1214
    {
1215
        $data = [
×
1216
            'relatedDummies' => ['wrong'],
×
1217
        ];
×
1218

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

1222
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1223

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

1235
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1236
        $iriConverterProphecy->getResourceFromIri(Argument::cetera())->willThrow(new InvalidArgumentException('Invalid IRI'));
×
1237

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

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

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

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

1252
        $errors = [];
×
1253
        $actual = $normalizer->denormalize($data, Dummy::class, null, ['not_normalizable_value_exceptions' => &$errors]);
×
1254
        $this->assertEmpty($actual->relatedDummies);
×
1255
        $this->assertCount(1, $errors);
×
1256
        $this->assertInstanceOf(NotNormalizableValueException::class, $errors[0]);
×
1257
        $this->assertSame('relatedDummies[0]', $errors[0]->getPath());
×
1258
        $this->assertSame('Invalid IRI "wrong".', $errors[0]->getMessage());
×
1259
    }
1260

1261
    public function testDeserializationPathForNotDenormalizableResource(): void
1262
    {
1263
        $this->expectException(NotNormalizableValueException::class);
×
1264
        $this->expectExceptionMessage('Invalid IRI "wrong IRI".');
×
1265

1266
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1267

1268
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1269

1270
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1271
        $iriConverterProphecy->getResourceFromIri(Argument::cetera())->willThrow(new InvalidArgumentException('Invalid IRI'));
×
1272

1273
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1274
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1275
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1276

1277
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1278

1279
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1280
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1281

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

1285
        $normalizer->denormalize('wrong IRI', Dummy::class, null, ['not_normalizable_value_exceptions' => []]);
×
1286
    }
1287

1288
    public function testDeserializationPathForNotFoundResource(): void
1289
    {
1290
        $this->expectException(NotNormalizableValueException::class);
×
1291
        $this->expectExceptionMessage('Some item not found exception.');
×
1292

1293
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1294

1295
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1296

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

1300
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1301
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1302
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1303

1304
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1305

1306
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1307
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1308

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

1312
        $normalizer->denormalize('/some-iri', Dummy::class, null, ['not_normalizable_value_exceptions' => []]);
×
1313
    }
1314

1315
    public function testInnerDocumentNotAllowed(): void
1316
    {
1317
        $this->expectException(UnexpectedValueException::class);
×
1318
        $this->expectExceptionMessage('Nested documents for attribute "relatedDummy" are not allowed. Use IRIs instead.');
×
1319

1320
        $data = [
×
1321
            'relatedDummy' => [
×
1322
                'foo' => 'bar',
×
1323
            ],
×
1324
        ];
×
1325

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

1329
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1330

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

1342
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1343

1344
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1345
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1346
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
1347
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
1348
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1349

1350
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1351

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

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

1358
        $normalizer->denormalize($data, Dummy::class);
×
1359
    }
1360

1361
    public function testBadType(): void
1362
    {
1363
        $this->expectException(UnexpectedValueException::class);
×
1364
        $this->expectExceptionMessage('The type of the "foo" attribute must be "float", "integer" given.');
×
1365

1366
        $data = [
×
1367
            'foo' => 42,
×
1368
        ];
×
1369

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

1373
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1374

1375
        // BC layer for api-platform/metadata < 4.1
1376
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1377
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1378
        } else {
1379
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::float())->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1380
        }
1381

1382
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1383

1384
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1385
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1386
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1387

1388
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1389

1390
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1391
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1392

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

1396
        $normalizer->denormalize($data, Dummy::class);
×
1397
    }
1398

1399
    public function testTypeChecksCanBeDisabled(): void
1400
    {
1401
        $data = [
×
1402
            'foo' => 42,
×
1403
        ];
×
1404

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

1408
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1409

1410
        // BC layer for api-platform/metadata < 4.1
1411
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1412
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1413
        } else {
1414
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::float())->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1415
        }
1416

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

1419
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1420
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1421
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1422

1423
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1424

1425
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1426
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1427

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

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

1433
        $this->assertInstanceOf(Dummy::class, $actual);
×
1434

1435
        $propertyAccessorProphecy->setValue($actual, 'foo', 42)->shouldHaveBeenCalled();
×
1436
    }
1437

1438
    public function testJsonAllowIntAsFloat(): void
1439
    {
1440
        $data = [
×
1441
            'foo' => 42,
×
1442
        ];
×
1443

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

1447
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1448

1449
        // BC layer for api-platform/metadata < 4.1
1450
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1451
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1452
        } else {
1453
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::float())->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1454
        }
1455

1456
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1457

1458
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1459
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1460
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1461

1462
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1463

1464
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1465
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1466

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

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

1472
        $this->assertInstanceOf(Dummy::class, $actual);
×
1473

1474
        $propertyAccessorProphecy->setValue($actual, 'foo', 42)->shouldHaveBeenCalled();
×
1475
    }
1476

1477
    public function testDenormalizeBadKeyType(): void
1478
    {
1479
        $this->expectException(NotNormalizableValueException::class);
×
1480
        $this->expectExceptionMessage('The type of the key "a" must be "int", "string" given.');
×
1481

1482
        $data = [
×
1483
            'name' => 'foo',
×
1484
            'relatedDummy' => [
×
1485
                'foo' => 'bar',
×
1486
            ],
×
1487
            'relatedDummies' => [
×
1488
                'a' => [
×
1489
                    'bar' => 'baz',
×
1490
                ],
×
1491
            ],
×
1492
            'relatedDummiesWithUnionTypes' => [
×
1493
                'a' => [
×
1494
                    'bar' => 'baz',
×
1495
                ],
×
1496
            ],
×
1497
        ];
×
1498

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

1502
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1503

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

1509
            $type = new LegacyType(
×
1510
                LegacyType::BUILTIN_TYPE_OBJECT,
×
1511
                false,
×
1512
                ArrayCollection::class,
×
1513
                true,
×
1514
                new LegacyType(LegacyType::BUILTIN_TYPE_INT),
×
1515
                new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class)
×
1516
            );
×
1517
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$type])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
1518
        } else {
1519
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
1520
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::object(RelatedDummy::class))->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1521

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

1526
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1527

1528
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1529
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1530
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
1531
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1532
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
1533

1534
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1535

1536
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1537
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1538

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

1542
        $normalizer->denormalize($data, Dummy::class);
×
1543
    }
1544

1545
    public function testNullable(): void
1546
    {
1547
        $data = [
×
1548
            'name' => null,
×
1549
        ];
×
1550

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

1554
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1555

1556
        // BC layer for api-platform/metadata < 4.1
1557
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1558
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING, true)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1559
        } else {
1560
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::nullable(Type::string()))->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
1561
        }
1562

1563
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1564

1565
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1566
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1567
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1568

1569
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1570

1571
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1572
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1573

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

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

1579
        $this->assertInstanceOf(Dummy::class, $actual);
×
1580

1581
        $propertyAccessorProphecy->setValue($actual, 'name', null)->shouldHaveBeenCalled();
×
1582
    }
1583

1584
    public function testDenormalizeBasicTypePropertiesFromXml(): void
1585
    {
1586
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1587
        $propertyNameCollectionFactoryProphecy->create(ObjectWithBasicProperties::class, Argument::type('array'))->willReturn(new PropertyNameCollection([
×
1588
            'boolTrue1',
×
1589
            'boolFalse1',
×
1590
            'boolTrue2',
×
1591
            'boolFalse2',
×
1592
            'int1',
×
1593
            'int2',
×
1594
            'float1',
×
1595
            'float2',
×
1596
            'float3',
×
1597
            'floatNaN',
×
1598
            'floatInf',
×
1599
            'floatNegInf',
×
1600
        ]));
×
1601

1602
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1603

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

1633
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1634

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

1649
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1650
        $resourceClassResolverProphecy->getResourceClass(null, ObjectWithBasicProperties::class)->willReturn(ObjectWithBasicProperties::class);
×
1651
        $resourceClassResolverProphecy->isResourceClass(ObjectWithBasicProperties::class)->willReturn(true);
×
1652

1653
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1654
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
1655

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

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

1678
        $this->assertInstanceOf(ObjectWithBasicProperties::class, $objectWithBasicProperties);
×
1679
    }
1680

1681
    public function testDenormalizeCollectionDecodedFromXmlWithOneChild(): void
1682
    {
1683
        $data = [
×
1684
            'relatedDummies' => [
×
1685
                'name' => 'foo',
×
1686
            ],
×
1687
        ];
×
1688

1689
        $relatedDummy = new RelatedDummy();
×
1690
        $relatedDummy->setName('foo');
×
1691

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

1695
        // BC layer for api-platform/metadata < 4.1
1696
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1697
            $relatedDummyType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class);
×
1698
            $relatedDummiesType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $relatedDummyType);
×
1699

1700
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1701
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(true));
×
1702
        } else {
1703
            $relatedDummyType = Type::object(RelatedDummy::class);
×
1704
            $relatedDummiesType = Type::collection(Type::object(ArrayCollection::class), $relatedDummyType, Type::int());
×
1705

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

1710
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1711

1712
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1713
        $propertyAccessorProphecy->setValue(Argument::type(Dummy::class), 'relatedDummies', Argument::type('array'))->shouldBeCalled();
×
1714

1715
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1716
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1717
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
1718
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
1719
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1720

1721
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1722
        $serializerProphecy->willImplement(DenormalizerInterface::class);
×
1723
        $serializerProphecy->denormalize(['name' => 'foo'], RelatedDummy::class, 'xml', Argument::type('array'))->willReturn($relatedDummy);
×
1724

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

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

1731
    public function testDenormalizePopulatingNonCloneableObject(): void
1732
    {
1733
        $dummy = new NonCloneableDummy();
×
1734
        $dummy->setName('foo');
×
1735

1736
        $data = [
×
1737
            'name' => 'bar',
×
1738
        ];
×
1739

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

1743
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1744

1745
        // BC layer for api-platform/metadata < 4.1
1746
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1747
            $propertyMetadataFactoryProphecy->create(NonCloneableDummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(false)->withWritable(true));
×
1748
        } else {
1749
            $propertyMetadataFactoryProphecy->create(NonCloneableDummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
1750
        }
1751

1752
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1753
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1754
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1755
        $resourceClassResolverProphecy->getResourceClass(null, NonCloneableDummy::class)->willReturn(NonCloneableDummy::class);
×
1756
        $resourceClassResolverProphecy->getResourceClass($dummy, NonCloneableDummy::class)->willReturn(NonCloneableDummy::class);
×
1757
        $resourceClassResolverProphecy->isResourceClass(NonCloneableDummy::class)->willReturn(true);
×
1758

1759
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1760
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
1761

1762
        $normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
×
1763
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1764
        $normalizer->setSerializer($serializerProphecy->reveal());
×
1765

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

1769
        $this->assertInstanceOf(NonCloneableDummy::class, $actual);
×
1770
        $this->assertSame($dummy, $actual);
×
1771
        $propertyAccessorProphecy->setValue($actual, 'name', 'bar')->shouldHaveBeenCalled();
×
1772
    }
1773

1774
    public function testDenormalizeObjectWithNullDisabledTypeEnforcement(): void
1775
    {
1776
        $data = [
×
1777
            'dummy' => null,
×
1778
        ];
×
1779

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

1783
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1784

1785
        // BC layer for api-platform/metadata < 4.1
1786
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1787
            $propertyMetadataFactoryProphecy->create(DtoWithNullValue::class, 'dummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, nullable: true)])->withDescription('')->withReadable(true)->withWritable(true));
×
1788
        } else {
1789
            $propertyMetadataFactoryProphecy->create(DtoWithNullValue::class, 'dummy', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::nullable(Type::object()))->withDescription('')->withReadable(true)->withWritable(true));
×
1790
        }
1791

1792
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1793
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1794
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1795
        $resourceClassResolverProphecy->getResourceClass(null, DtoWithNullValue::class)->willReturn(DtoWithNullValue::class);
×
1796
        $resourceClassResolverProphecy->isResourceClass(DtoWithNullValue::class)->willReturn(true);
×
1797

1798
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1799
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
1800

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

1804
        $context = [AbstractItemNormalizer::DISABLE_TYPE_ENFORCEMENT => true];
×
1805
        $actual = $normalizer->denormalize($data, DtoWithNullValue::class, null, $context);
×
1806

1807
        $this->assertInstanceOf(DtoWithNullValue::class, $actual);
×
1808
        $this->assertEquals(new DtoWithNullValue(), $actual);
×
1809
    }
1810

1811
    public function testCacheKey(): void
1812
    {
1813
        $relatedDummy = new RelatedDummy();
×
1814

1815
        $dummy = new Dummy();
×
1816
        $dummy->setName('foo');
×
1817
        $dummy->setAlias('ignored');
×
1818
        $dummy->setRelatedDummy($relatedDummy);
×
1819
        $dummy->relatedDummies->add(new RelatedDummy());
×
1820

1821
        $relatedDummies = new ArrayCollection([$relatedDummy]);
×
1822

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

1826
        // BC layer for api-platform/metadata < 4.1
1827
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
1828
            $relatedDummyType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class);
×
1829
            $relatedDummiesType = new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, ArrayCollection::class, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), $relatedDummyType);
×
1830

1831
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1832
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true));
×
1833
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'alias', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])->withDescription('')->withReadable(true));
×
1834
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withDescription('')->withReadable(true)->withWritable(false)->withReadableLink(false));
×
1835
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummiesType])->withReadable(true)->withWritable(false)->withReadableLink(false));
×
1836
        } else {
1837
            $relatedDummyType = Type::object(RelatedDummy::class);
×
1838
            $relatedDummiesType = Type::collection(Type::object(ArrayCollection::class), $relatedDummyType, Type::int());
×
1839

1840
            $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1841
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true));
×
1842
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'alias', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true));
×
1843
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType($relatedDummyType)->withDescription('')->withReadable(true)->withWritable(false)->withReadableLink(false));
×
1844
            $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::type('array'))->willReturn((new ApiProperty())->withNativeType($relatedDummiesType)->withReadable(true)->withWritable(false)->withReadableLink(false));
×
1845
        }
1846

1847
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
1848
        $iriConverterProphecy->getIriFromResource($dummy, Argument::cetera())->willReturn('/dummies/1');
×
1849
        $iriConverterProphecy->getIriFromResource($relatedDummy, Argument::cetera())->willReturn('/dummies/2');
×
1850

1851
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
1852
        $propertyAccessorProphecy->getValue($dummy, 'name')->willReturn('foo');
×
1853
        $propertyAccessorProphecy->getValue($dummy, 'relatedDummy')->willReturn($relatedDummy);
×
1854
        $propertyAccessorProphecy->getValue($dummy, 'relatedDummies')->willReturn($relatedDummies);
×
1855

1856
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
1857
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
1858
        $resourceClassResolverProphecy->getResourceClass($dummy, null)->willReturn(Dummy::class);
×
1859
        $resourceClassResolverProphecy->getResourceClass($relatedDummy, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
1860
        $resourceClassResolverProphecy->getResourceClass($relatedDummies, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
1861
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
1862
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
1863

1864
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
1865
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
1866
        $serializerProphecy->normalize('foo', null, Argument::type('array'))->willReturn('foo');
×
1867
        $serializerProphecy->normalize(['/dummies/2'], null, Argument::type('array'))->willReturn(['/dummies/2']);
×
1868

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

1872
        $expected = [
×
1873
            'name' => 'foo',
×
1874
            'relatedDummy' => '/dummies/2',
×
1875
            'relatedDummies' => ['/dummies/2'],
×
1876
        ];
×
1877
        $this->assertSame($expected, $normalizer->normalize($dummy, null, [
×
1878
            'resources' => [],
×
1879
            'groups' => ['group'],
×
1880
            'ignored_attributes' => ['alias'],
×
1881
            'operation_name' => 'operation_name',
×
1882
            'root_operation_name' => 'root_operation_name',
×
1883
        ]));
×
1884

1885
        $operationCacheKey = (new \ReflectionClass($normalizer))->getProperty('localFactoryOptionsCache')->getValue($normalizer);
×
1886
        $this->assertEquals(array_keys($operationCacheKey), [\sprintf('%s%s%s%s', Dummy::class, 'operation_name', 'root_operation_name', 'n')]);
×
1887
        $this->assertEquals(current($operationCacheKey), ['serializer_groups' => ['group'], 'api_allow_update' => false]);
×
1888
    }
1889
}
1890

1891
class ObjectWithBasicProperties
1892
{
1893
    /** @var bool */
1894
    public $boolTrue1;
1895

1896
    /** @var bool */
1897
    public $boolFalse1;
1898

1899
    /** @var bool */
1900
    public $boolTrue2;
1901

1902
    /** @var bool */
1903
    public $boolFalse2;
1904

1905
    /** @var int */
1906
    public $int1;
1907

1908
    /** @var int */
1909
    public $int2;
1910

1911
    /** @var float */
1912
    public $float1;
1913

1914
    /** @var float */
1915
    public $float2;
1916

1917
    /** @var float */
1918
    public $float3;
1919

1920
    /** @var float */
1921
    public $floatNaN;
1922

1923
    /** @var float */
1924
    public $floatInf;
1925

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

© 2025 Coveralls, Inc