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

api-platform / core / 18089937549

29 Sep 2025 07:56AM UTC coverage: 21.764% (-0.3%) from 22.093%
18089937549

Pull #7416

github

web-flow
Merge 061bcc790 into abe0438be
Pull Request #7416: fix(laravel): serializer attributes on Eloquent methods

0 of 151 new or added lines in 11 files covered. (0.0%)

5028 existing lines in 173 files now uncovered.

11889 of 54626 relevant lines covered (21.76%)

25.32 hits per line

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

0.0
/src/Hal/Tests/Serializer/ItemNormalizerTest.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\Hal\Tests\Serializer;
15

16
use ApiPlatform\Hal\Serializer\ItemNormalizer;
17
use ApiPlatform\Metadata\ApiProperty;
18
use ApiPlatform\Metadata\IriConverterInterface;
19
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
20
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
21
use ApiPlatform\Metadata\Property\PropertyNameCollection;
22
use ApiPlatform\Metadata\ResourceClassResolverInterface;
23
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue5452\ActivableInterface;
24
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue5452\Author;
25
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue5452\Book;
26
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue5452\Library;
27
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue5452\TimestampableInterface;
28
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy;
29
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MaxDepthDummy;
30
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\RelatedDummy;
31
use PHPUnit\Framework\Attributes\DataProvider;
32
use PHPUnit\Framework\TestCase;
33
use Prophecy\Argument;
34
use Prophecy\PhpUnit\ProphecyTrait;
35
use Symfony\Component\Serializer\Exception\LogicException;
36
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
37
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
38
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
39
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
40
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
41
use Symfony\Component\Serializer\Serializer;
42
use Symfony\Component\Serializer\SerializerInterface;
43
use Symfony\Component\TypeInfo\Type;
44

45
/**
46
 * @author Kévin Dunglas <dunglas@gmail.com>
47
 */
48
class ItemNormalizerTest extends TestCase
49
{
50
    use ProphecyTrait;
51

52
    public function testDoesNotSupportDenormalization(): void
53
    {
54
        $this->expectException(LogicException::class);
×
UNCOV
55
        $this->expectExceptionMessage('jsonhal is a read-only format.');
×
56

57
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
58
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
59
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
60
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
61
        $nameConverter = $this->prophesize(NameConverterInterface::class);
×
62

63
        $normalizer = new ItemNormalizer(
×
UNCOV
64
            $propertyNameCollectionFactoryProphecy->reveal(),
×
65
            $propertyMetadataFactoryProphecy->reveal(),
×
66
            $iriConverterProphecy->reveal(),
×
UNCOV
67
            $resourceClassResolverProphecy->reveal(),
×
UNCOV
68
            null,
×
UNCOV
69
            $nameConverter->reveal()
×
UNCOV
70
        );
×
71

72
        $this->assertFalse($normalizer->supportsDenormalization('foo', ItemNormalizer::FORMAT));
×
73
        $normalizer->denormalize(['foo'], 'Foo');
×
74
    }
75

76
    #[\PHPUnit\Framework\Attributes\Group('legacy')]
77
    public function testSupportsNormalization(): void
78
    {
79
        $std = new \stdClass();
×
80
        $dummy = new Dummy();
×
81
        $dummy->setDescription('hello');
×
82

83
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
UNCOV
84
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
85
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
86

87
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
88
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
89
        $resourceClassResolverProphecy->isResourceClass(\stdClass::class)->willReturn(false);
×
90

91
        $nameConverter = $this->prophesize(NameConverterInterface::class);
×
92

93
        $normalizer = new ItemNormalizer(
×
94
            $propertyNameCollectionFactoryProphecy->reveal(),
×
UNCOV
95
            $propertyMetadataFactoryProphecy->reveal(),
×
96
            $iriConverterProphecy->reveal(),
×
97
            $resourceClassResolverProphecy->reveal(),
×
98
            null,
×
99
            $nameConverter->reveal()
×
100
        );
×
101

UNCOV
102
        $this->assertTrue($normalizer->supportsNormalization($dummy, $normalizer::FORMAT));
×
UNCOV
103
        $this->assertFalse($normalizer->supportsNormalization($dummy, 'xml'));
×
UNCOV
104
        $this->assertFalse($normalizer->supportsNormalization($std, $normalizer::FORMAT));
×
105
        $this->assertEmpty($normalizer->getSupportedTypes('xml'));
×
106
        $this->assertSame(['object' => true], $normalizer->getSupportedTypes($normalizer::FORMAT));
×
107
    }
108

109
    public function testNormalize(): void
110
    {
111
        $relatedDummy = new RelatedDummy();
×
112
        $dummy = new Dummy();
×
UNCOV
113
        $dummy->setName('hello');
×
114
        $dummy->setRelatedDummy($relatedDummy);
×
115

116
        $propertyNameCollection = new PropertyNameCollection(['name', 'relatedDummy']);
×
117
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
118
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn($propertyNameCollection);
×
119

120
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
121
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn(
×
122
            (new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true)
×
123
        );
×
124
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn(
×
UNCOV
125
            (new ApiProperty())->withNativeType(Type::object(RelatedDummy::class))->withDescription('')->withReadable(true)->withWritable(false)->withWritableLink(false)
×
126
        );
×
127

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

132
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
UNCOV
133
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
134
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
135
        $resourceClassResolverProphecy->getResourceClass($dummy, null)->willReturn(Dummy::class);
×
136
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
137
        $resourceClassResolverProphecy->getResourceClass($dummy, Dummy::class)->willReturn(Dummy::class);
×
138
        $resourceClassResolverProphecy->getResourceClass($relatedDummy, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
139

140
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
141
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
UNCOV
142
        $serializerProphecy->normalize('hello', null, Argument::type('array'))->willReturn('hello');
×
143

144
        $nameConverter = $this->prophesize(NameConverterInterface::class);
×
UNCOV
145
        $nameConverter->normalize('name', Argument::any(), Argument::any(), Argument::any())->willReturn('name');
×
146
        $nameConverter->normalize('relatedDummy', Argument::any(), Argument::any(), Argument::any())->willReturn('related_dummy');
×
147

148
        $normalizer = new ItemNormalizer(
×
149
            $propertyNameCollectionFactoryProphecy->reveal(),
×
150
            $propertyMetadataFactoryProphecy->reveal(),
×
UNCOV
151
            $iriConverterProphecy->reveal(),
×
UNCOV
152
            $resourceClassResolverProphecy->reveal(),
×
153
            null,
×
154
            $nameConverter->reveal()
×
155
        );
×
UNCOV
156
        $normalizer->setSerializer($serializerProphecy->reveal());
×
157

158
        $expected = [
×
159
            '_links' => [
×
160
                'self' => [
×
161
                    'href' => '/dummies/1',
×
162
                ],
×
163
                'related_dummy' => [
×
164
                    'href' => '/related-dummies/2',
×
165
                ],
×
UNCOV
166
            ],
×
167
            'name' => 'hello',
×
168
        ];
×
169
        $this->assertEquals($expected, $normalizer->normalize($dummy));
×
170
    }
171

172
    public function testNormalizeWithUnionIntersectTypes(): void
173
    {
174
        $author = new Author(id: 2, name: 'Isaac Asimov');
×
175
        $library = new Library(id: 3, name: 'Le Bâteau Livre');
×
176
        $book = new Book();
×
177
        $book->author = $author;
×
178
        $book->library = $library;
×
179

UNCOV
180
        $propertyNameCollection = new PropertyNameCollection(['author', 'library']);
×
UNCOV
181
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
UNCOV
182
        $propertyNameCollectionFactoryProphecy->create(Book::class, Argument::type('array'))->willReturn($propertyNameCollection);
×
183

184
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
185
        $propertyMetadataFactoryProphecy->create(Book::class, 'author', Argument::type('array'))->willReturn(
×
186
            (new ApiProperty())->withNativeType(Type::intersection(Type::object(ActivableInterface::class), Type::object(TimestampableInterface::class)))->withReadable(true)
×
187
        );
×
UNCOV
188
        $propertyMetadataFactoryProphecy->create(Book::class, 'library', Argument::type('array'))->willReturn(
×
189
            (new ApiProperty())->withNativeType(Type::intersection(Type::object(ActivableInterface::class), Type::object(TimestampableInterface::class)))->withReadable(true)
×
190
        );
×
191

UNCOV
192
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
193
        $iriConverterProphecy->getIriFromResource($book, Argument::cetera())->willReturn('/books/1');
×
194

195
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
196
        $resourceClassResolverProphecy->isResourceClass(Book::class)->willReturn(true);
×
197
        $resourceClassResolverProphecy->isResourceClass(ActivableInterface::class)->willReturn(false);
×
198
        $resourceClassResolverProphecy->isResourceClass(TimestampableInterface::class)->willReturn(false);
×
199
        $resourceClassResolverProphecy->getResourceClass($book, null)->willReturn(Book::class);
×
200
        $resourceClassResolverProphecy->getResourceClass(null, Book::class)->willReturn(Book::class);
×
201

202
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
203
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
204

205
        $nameConverter = $this->prophesize(NameConverterInterface::class);
×
206
        $nameConverter->normalize('author', Argument::any(), Argument::any(), Argument::any())->willReturn('author');
×
UNCOV
207
        $nameConverter->normalize('library', Argument::any(), Argument::any(), Argument::any())->willReturn('library');
×
208

209
        $normalizer = new ItemNormalizer(
×
210
            $propertyNameCollectionFactoryProphecy->reveal(),
×
211
            $propertyMetadataFactoryProphecy->reveal(),
×
212
            $iriConverterProphecy->reveal(),
×
213
            $resourceClassResolverProphecy->reveal(),
×
214
            null,
×
215
            $nameConverter->reveal()
×
216
        );
×
217
        $normalizer->setSerializer($serializerProphecy->reveal());
×
218

219
        $expected = [
×
UNCOV
220
            '_links' => [
×
221
                'self' => [
×
222
                    'href' => '/books/1',
×
223
                ],
×
224
            ],
×
UNCOV
225
            'author' => null,
×
226
            'library' => null,
×
227
        ];
×
228
        $this->assertEquals($expected, $normalizer->normalize($book));
×
229
    }
230

231
    public function testNormalizeWithoutCache(): void
232
    {
233
        $relatedDummy = new RelatedDummy();
×
234
        $dummy = new Dummy();
×
UNCOV
235
        $dummy->setName('hello');
×
236
        $dummy->setRelatedDummy($relatedDummy);
×
237

238
        $propertyNameCollection = new PropertyNameCollection(['name', 'relatedDummy']);
×
239
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
240
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn($propertyNameCollection);
×
241

242
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
243
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::type('array'))->willReturn(
×
244
            (new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true)
×
245
        );
×
UNCOV
246
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn(
×
UNCOV
247
            (new ApiProperty())->withNativeType(Type::object(RelatedDummy::class))->withDescription('')->withReadable(true)->withWritable(false)->withReadableLink(false)
×
UNCOV
248
        );
×
249

250
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
251
        $iriConverterProphecy->getIriFromResource($dummy, Argument::cetera())->willReturn('/dummies/1');
×
252
        $iriConverterProphecy->getIriFromResource($relatedDummy, Argument::cetera())->willReturn('/related-dummies/2');
×
253

UNCOV
254
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
255
        $resourceClassResolverProphecy->getResourceClass($dummy, null)->willReturn(Dummy::class);
×
256
        $resourceClassResolverProphecy->getResourceClass($dummy, Dummy::class)->willReturn(Dummy::class);
×
257
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
UNCOV
258
        $resourceClassResolverProphecy->getResourceClass($relatedDummy, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
259
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
260
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
261

262
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
263
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
UNCOV
264
        $serializerProphecy->normalize('hello', null, Argument::type('array'))->willReturn('hello');
×
265

266
        $nameConverter = $this->prophesize(NameConverterInterface::class);
×
267
        $nameConverter->normalize('name', Argument::any(), Argument::any(), Argument::any())->willReturn('name');
×
268
        $nameConverter->normalize('relatedDummy', Argument::any(), Argument::any(), Argument::any())->willReturn('related_dummy');
×
269

UNCOV
270
        $normalizer = new ItemNormalizer(
×
271
            $propertyNameCollectionFactoryProphecy->reveal(),
×
272
            $propertyMetadataFactoryProphecy->reveal(),
×
UNCOV
273
            $iriConverterProphecy->reveal(),
×
UNCOV
274
            $resourceClassResolverProphecy->reveal(),
×
275
            null,
×
276
            $nameConverter->reveal()
×
277
        );
×
UNCOV
278
        $normalizer->setSerializer($serializerProphecy->reveal());
×
279

280
        $expected = [
×
281
            '_links' => [
×
282
                'self' => [
×
283
                    'href' => '/dummies/1',
×
284
                ],
×
285
                'related_dummy' => [
×
286
                    'href' => '/related-dummies/2',
×
UNCOV
287
                ],
×
288
            ],
×
289
            'name' => 'hello',
×
UNCOV
290
        ];
×
291
        $this->assertEquals($expected, $normalizer->normalize($dummy, null, ['not_serializable' => function (): void {}]));
×
292
    }
293

294
    public function testMaxDepth(): void
295
    {
UNCOV
296
        $setId = function (MaxDepthDummy $dummy, int $id): void {
×
UNCOV
297
            $prop = new \ReflectionProperty($dummy, 'id');
×
298
            $prop->setAccessible(true);
×
299
            $prop->setValue($dummy, $id);
×
300
        };
×
301

302
        $level1 = new MaxDepthDummy();
×
303
        $setId($level1, 1);
×
304
        $level1->name = 'level 1';
×
305

306
        $level2 = new MaxDepthDummy();
×
307
        $setId($level2, 2);
×
308
        $level2->name = 'level 2';
×
309
        $level1->child = $level2;
×
310

UNCOV
311
        $level3 = new MaxDepthDummy();
×
312
        $setId($level3, 3);
×
313
        $level3->name = 'level 3';
×
314
        $level2->child = $level3;
×
315

316
        $propertyNameCollection = new PropertyNameCollection(['id', 'name', 'child']);
×
317
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
318
        $propertyNameCollectionFactoryProphecy->create(MaxDepthDummy::class, Argument::type('array'))->willReturn($propertyNameCollection);
×
319

320
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
321
        $propertyMetadataFactoryProphecy->create(MaxDepthDummy::class, 'id', Argument::type('array'))->willReturn(
×
322
            (new ApiProperty())->withNativeType(Type::int())->withDescription('')->withReadable(true)
×
323
        );
×
UNCOV
324
        $propertyMetadataFactoryProphecy->create(MaxDepthDummy::class, 'name', Argument::type('array'))->willReturn(
×
UNCOV
325
            (new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true)
×
UNCOV
326
        );
×
UNCOV
327
        $propertyMetadataFactoryProphecy->create(MaxDepthDummy::class, 'child', Argument::type('array'))->willReturn(
×
328
            (new ApiProperty())->withNativeType(Type::object(MaxDepthDummy::class))->withDescription('')->withReadable(true)->withWritable(false)->withReadableLink(true)
×
329
        );
×
330

331
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
332
        $iriConverterProphecy->getIriFromResource($level1, Argument::cetera())->willReturn('/max_depth_dummies/1');
×
UNCOV
333
        $iriConverterProphecy->getIriFromResource($level2, Argument::cetera())->willReturn('/max_depth_dummies/2');
×
334
        $iriConverterProphecy->getIriFromResource($level3, Argument::cetera())->willReturn('/max_depth_dummies/3');
×
335

336
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
UNCOV
337
        $resourceClassResolverProphecy->getResourceClass($level1, null)->willReturn(MaxDepthDummy::class);
×
338
        $resourceClassResolverProphecy->getResourceClass($level1, MaxDepthDummy::class)->willReturn(MaxDepthDummy::class);
×
339
        $resourceClassResolverProphecy->getResourceClass($level2, MaxDepthDummy::class)->willReturn(MaxDepthDummy::class);
×
340
        $resourceClassResolverProphecy->getResourceClass($level3, MaxDepthDummy::class)->willReturn(MaxDepthDummy::class);
×
341
        $resourceClassResolverProphecy->getResourceClass(null, MaxDepthDummy::class)->willReturn(MaxDepthDummy::class);
×
UNCOV
342
        $resourceClassResolverProphecy->isResourceClass(MaxDepthDummy::class)->willReturn(true);
×
343

344
        $normalizer = new ItemNormalizer(
×
345
            $propertyNameCollectionFactoryProphecy->reveal(),
×
346
            $propertyMetadataFactoryProphecy->reveal(),
×
UNCOV
347
            $iriConverterProphecy->reveal(),
×
348
            $resourceClassResolverProphecy->reveal(),
×
349
            null,
×
350
            null,
×
UNCOV
351
            new ClassMetadataFactory(new AttributeLoader())
×
352
        );
×
353
        $serializer = new Serializer([$normalizer]);
×
354
        $normalizer->setSerializer($serializer);
×
355

356
        $expected = [
×
357
            '_links' => [
×
UNCOV
358
                'self' => [
×
359
                    'href' => '/max_depth_dummies/1',
×
360
                ],
×
361
                'child' => [
×
362
                    'href' => '/max_depth_dummies/2',
×
363
                ],
×
UNCOV
364
            ],
×
365
            '_embedded' => [
×
366
                'child' => [
×
UNCOV
367
                    '_links' => [
×
368
                        'self' => [
×
369
                            'href' => '/max_depth_dummies/2',
×
UNCOV
370
                        ],
×
UNCOV
371
                        'child' => [
×
372
                            'href' => '/max_depth_dummies/3',
×
373
                        ],
×
374
                    ],
×
UNCOV
375
                    '_embedded' => [
×
376
                        'child' => [
×
377
                            '_links' => [
×
378
                                'self' => [
×
379
                                    'href' => '/max_depth_dummies/3',
×
380
                                ],
×
381
                            ],
×
382
                            'id' => 3,
×
383
                            'name' => 'level 3',
×
384
                        ],
×
UNCOV
385
                    ],
×
386
                    'id' => 2,
×
387
                    'name' => 'level 2',
×
388
                ],
×
389
            ],
×
390
            'id' => 1,
×
391
            'name' => 'level 1',
×
392
        ];
×
393

394
        $this->assertEquals($expected, $normalizer->normalize($level1, ItemNormalizer::FORMAT));
×
395
        $this->assertEquals($expected, $normalizer->normalize($level1, ItemNormalizer::FORMAT, [ObjectNormalizer::ENABLE_MAX_DEPTH => false]));
×
396

UNCOV
397
        $expected = [
×
398
            '_links' => [
×
399
                'self' => [
×
400
                    'href' => '/max_depth_dummies/1',
×
401
                ],
×
402
                'child' => [
×
403
                    'href' => '/max_depth_dummies/2',
×
404
                ],
×
405
            ],
×
406
            '_embedded' => [
×
407
                'child' => [
×
408
                    '_links' => [
×
409
                        'self' => [
×
410
                            'href' => '/max_depth_dummies/2',
×
411
                        ],
×
412
                    ],
×
413
                    'id' => 2,
×
414
                    'name' => 'level 2',
×
415
                ],
×
416
            ],
×
417
            'id' => 1,
×
418
            'name' => 'level 1',
×
419
        ];
×
420

421
        $this->assertEquals($expected, $normalizer->normalize($level1, ItemNormalizer::FORMAT, [ObjectNormalizer::ENABLE_MAX_DEPTH => true]));
×
422
    }
423

424
    /**
425
     * @param array<int,mixed> $context
426
     * @param array<int,mixed> $expected
427
     */
428
    #[DataProvider('getSkipNullToOneRelationCases')]
429
    public function testSkipNullToOneRelation(array $context, array $expected): void
430
    {
431
        $dummy = new Dummy();
×
432
        $dummy->setAlias(null);
×
433
        $dummy->relatedDummy = null;
×
434

UNCOV
435
        $propertyNameCollection = new PropertyNameCollection(['alias', 'relatedDummy']);
×
436
        $propertyNameCollectionFactory = $this->createMock(PropertyNameCollectionFactoryInterface::class);
×
437
        $propertyNameCollectionFactory->method('create')->willReturn($propertyNameCollection);
×
438

439
        $propertyMetadataFactory = $this->createMock(PropertyMetadataFactoryInterface::class);
×
440
        $propertyMetadataFactory->method('create')->willReturnCallback(function ($resourceClass, $propertyName, $groups) {
×
441
            if ('alias' == $propertyName) {
×
442
                return (new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(true);
×
443
            }
444
            if ('relatedDummy' == $propertyName) {
×
445
                return (new ApiProperty())->withNativeType(Type::object(RelatedDummy::class))->withDescription('')->withReadable(true)->withWritable(false);
×
446
            }
447
        });
×
448

449
        $iriConverter = $this->createMock(IriConverterInterface::class);
×
450
        $iriConverter->method('getIriFromResource')->willReturn('/dummies/1');
×
451

452
        $resourceClassResolver = $this->createMock(ResourceClassResolverInterface::class);
×
453
        $resourceClassResolver->method('getResourceClass')->willReturnCallback(function ($resource) {
×
454
            if ($resource instanceof Dummy) {
×
455
                return Dummy::class;
×
456
            }
457
            if (null == $resource) {
×
458
                return RelatedDummy::class;
×
459
            }
460
        });
×
461
        $resourceClassResolver->method('isResourceClass')->willReturn(true);
×
462

463
        $serializer = $this->createMockForIntersectionOfInterfaces([SerializerInterface::class, NormalizerInterface::class]);
×
UNCOV
464
        $serializer->method('normalize')->with(null, null, self::anything())->willReturn(null);
×
465

UNCOV
466
        $nameConverter = self::createMock(NameConverterInterface::class);
×
UNCOV
467
        $nameConverter->method('normalize')->willReturnCallback(function ($propertyName) {
×
UNCOV
468
            if ('alias' == $propertyName) {
×
UNCOV
469
                return 'alias';
×
470
            }
UNCOV
471
            if ('relatedDummy' == $propertyName) {
×
UNCOV
472
                return 'related_dummy';
×
473
            }
UNCOV
474
        });
×
475

UNCOV
476
        $normalizer = new ItemNormalizer(
×
UNCOV
477
            propertyNameCollectionFactory: $propertyNameCollectionFactory,
×
UNCOV
478
            propertyMetadataFactory: $propertyMetadataFactory,
×
UNCOV
479
            iriConverter: $iriConverter,
×
UNCOV
480
            resourceClassResolver: $resourceClassResolver,
×
UNCOV
481
            propertyAccessor: null,
×
UNCOV
482
            nameConverter: $nameConverter,
×
UNCOV
483
            classMetadataFactory: null,
×
UNCOV
484
            defaultContext: [],
×
UNCOV
485
            resourceMetadataCollectionFactory: null,
×
UNCOV
486
            resourceAccessChecker: null,
×
UNCOV
487
            tagCollector: null,
×
UNCOV
488
        );
×
489

UNCOV
490
        $normalizer->setSerializer($serializer); // @phpstan-ignore-line
×
491

UNCOV
492
        self::assertThat($expected, self::equalTo($normalizer->normalize($dummy, null, $context)));
×
493
    }
494

495
    public static function getSkipNullToOneRelationCases(): iterable
496
    {
UNCOV
497
        yield [
×
UNCOV
498
            ['skip_null_to_one_relations' => true],
×
UNCOV
499
            [
×
UNCOV
500
                '_links' => [
×
UNCOV
501
                    'self' => [
×
UNCOV
502
                        'href' => '/dummies/1',
×
UNCOV
503
                    ],
×
UNCOV
504
                ],
×
UNCOV
505
                'alias' => null,
×
UNCOV
506
            ],
×
UNCOV
507
        ];
×
508

UNCOV
509
        yield [
×
UNCOV
510
            ['skip_null_to_one_relations' => false],
×
UNCOV
511
            [
×
UNCOV
512
                '_links' => [
×
UNCOV
513
                    'self' => [
×
UNCOV
514
                        'href' => '/dummies/1',
×
UNCOV
515
                    ],
×
UNCOV
516
                    'related_dummy' => null,
×
UNCOV
517
                ],
×
UNCOV
518
                'alias' => null,
×
UNCOV
519
            ]];
×
520
    }
521
}
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