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

api-platform / core / 20745879029

06 Jan 2026 10:43AM UTC coverage: 28.884% (+0.04%) from 28.843%
20745879029

Pull #7647

github

web-flow
Merge 4e69048e9 into d23ab4301
Pull Request #7647: fix(doctrine): fix partial fetch with same entity included multiple time with different fields

32 of 117 new or added lines in 2 files covered. (27.35%)

378 existing lines in 45 files now uncovered.

16832 of 58274 relevant lines covered (28.88%)

78.61 hits per line

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

0.0
/src/Doctrine/Orm/Tests/Extension/EagerLoadingExtensionTest.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\Doctrine\Orm\Tests\Extension;
15

16
use ApiPlatform\Doctrine\Orm\Extension\EagerLoadingExtension;
17
use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\AbstractDummy;
18
use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\ConcreteDummy;
19
use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\Dummy;
20
use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\EmbeddableDummy;
21
use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\PropertyCollectionIriOnly;
22
use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\PropertyCollectionIriOnlyRelation;
23
use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\RelatedDummy;
24
use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\ThirdLevel;
25
use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\UnknownDummy;
26
use ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator;
27
use ApiPlatform\Metadata\ApiProperty;
28
use ApiPlatform\Metadata\Exception\PropertyNotFoundException;
29
use ApiPlatform\Metadata\Exception\ResourceClassNotFoundException;
30
use ApiPlatform\Metadata\Exception\RuntimeException;
31
use ApiPlatform\Metadata\Get;
32
use ApiPlatform\Metadata\GetCollection;
33
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
34
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
35
use ApiPlatform\Metadata\Property\PropertyNameCollection;
36
use Doctrine\ORM\EntityManager;
37
use Doctrine\ORM\Mapping\ClassMetadata;
38
use Doctrine\ORM\Mapping\JoinColumn;
39
use Doctrine\ORM\Query\Expr\Join;
40
use Doctrine\ORM\QueryBuilder;
41
use PHPUnit\Framework\TestCase;
42
use Prophecy\Argument;
43
use Prophecy\PhpUnit\ProphecyTrait;
44
use Symfony\Component\Serializer\Mapping\AttributeMetadata;
45
use Symfony\Component\Serializer\Mapping\ClassMetadataInterface;
46
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
47
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
48

49
/**
50
 * @author Amrouche Hamza <hamza.simperfit@gmail.com>
51
 * @author Antoine Bluchet <soyuka@gmail.com>
52
 */
53
class EagerLoadingExtensionTest extends TestCase
54
{
55
    use ProphecyTrait;
56

57
    public function testApplyToCollection(): void
58
    {
59
        $context = ['groups' => ['foo']];
×
60
        $callContext = ['serializer_groups' => ['foo']];
×
61

62
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
63

64
        $relatedNameCollection = new PropertyNameCollection(['id', 'name', 'notindatabase', 'notreadable', 'embeddedDummy']);
×
65
        $relatedEmbedableCollection = new PropertyNameCollection(['name']);
×
66

67
        $propertyNameCollectionFactoryProphecy->create(RelatedDummy::class)->willReturn($relatedNameCollection)->shouldBeCalled();
×
68

69
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
70
        $relationPropertyMetadata = new ApiProperty();
×
71
        $relationPropertyMetadata = $relationPropertyMetadata->withReadableLink(true);
×
72

73
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', $callContext)->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
74
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy2', $callContext)->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
75
        $propertyNameCollectionFactoryProphecy->create(EmbeddableDummy::class)->willReturn($relatedEmbedableCollection)->shouldBeCalled();
×
76

77
        $idPropertyMetadata = new ApiProperty();
×
78
        $idPropertyMetadata = $idPropertyMetadata->withIdentifier(true);
×
79
        $namePropertyMetadata = new ApiProperty();
×
80
        $namePropertyMetadata = $namePropertyMetadata->withReadable(true);
×
81
        $embeddedPropertyMetadata = new ApiProperty();
×
82
        $embeddedPropertyMetadata = $embeddedPropertyMetadata->withReadable(true);
×
83
        $notInDatabasePropertyMetadata = new ApiProperty();
×
84
        $notInDatabasePropertyMetadata = $notInDatabasePropertyMetadata->withReadable(true);
×
85
        $notReadablePropertyMetadata = new ApiProperty();
×
86
        $notReadablePropertyMetadata = $notReadablePropertyMetadata->withReadable(false);
×
87

88
        $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'id', $callContext)->willReturn($idPropertyMetadata)->shouldBeCalled();
×
89
        $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'name', $callContext)->willReturn($namePropertyMetadata)->shouldBeCalled();
×
90
        $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'embeddedDummy', $callContext)->willReturn($embeddedPropertyMetadata)->shouldBeCalled();
×
91
        $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'notindatabase', $callContext)->willReturn($notInDatabasePropertyMetadata)->shouldBeCalled();
×
92
        $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'notreadable', $callContext)->willReturn($notReadablePropertyMetadata)->shouldBeCalled();
×
93

94
        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
×
95

96
        $classMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
97
        $classMetadataProphecy->associationMappings = [
×
98
            'relatedDummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [new JoinColumn(nullable: true)], 'targetEntity' => RelatedDummy::class],
×
99
            'relatedDummy2' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [new JoinColumn(nullable: false)], 'targetEntity' => RelatedDummy::class],
×
100
        ];
×
101

102
        $relatedClassMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
103

104
        foreach ($relatedNameCollection as $property) {
×
105
            if ('id' !== $property && 'embeddedDummy' !== $property) {
×
106
                $relatedClassMetadataProphecy->hasField($property)->willReturn('notindatabase' !== $property)->shouldBeCalled();
×
107
            }
108
        }
109
        $relatedClassMetadataProphecy->hasField('embeddedDummy.name')->willReturn(true)->shouldBeCalled();
×
110

111
        $relatedClassMetadataProphecy->embeddedClasses = ['embeddedDummy' => ['class' => EmbeddableDummy::class]];
×
112

113
        $relatedClassMetadataProphecy->associationMappings = [];
×
114

115
        $emProphecy = $this->prophesize(EntityManager::class);
×
116
        $emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
×
117
        $emProphecy->getClassMetadata(RelatedDummy::class)->shouldBeCalled()->willReturn($relatedClassMetadataProphecy->reveal());
×
118

119
        $queryBuilderProphecy->getRootAliases()->willReturn(['o']);
×
NEW
120
        $queryBuilderProphecy->getDQLPart('select')->willReturn([]);
×
UNCOV
121
        $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy);
×
122

123
        $queryBuilderProphecy->leftJoin('o.relatedDummy', 'relatedDummy_a1')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
124
        $queryBuilderProphecy->innerJoin('o.relatedDummy2', 'relatedDummy2_a2')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
125
        $queryBuilderProphecy->addSelect('partial relatedDummy_a1.{id,name,embeddedDummy.name}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
126
        $queryBuilderProphecy->addSelect('partial relatedDummy2_a2.{id,name,embeddedDummy.name}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
127
        $queryBuilderProphecy->getDQLPart('join')->willReturn([]);
×
128

129
        $queryBuilder = $queryBuilderProphecy->reveal();
×
130
        $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, false, true);
×
131
        $eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, null, $context);
×
132
    }
133

134
    public function testApplyToItem(): void
135
    {
136
        $context = ['groups' => ['foo']];
×
137
        $callContext = ['serializer_groups' => ['foo']];
×
138

139
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
140

141
        $relatedNameCollection = new PropertyNameCollection(['id', 'name', 'embeddedDummy', 'notindatabase', 'notreadable', 'relation']);
×
142
        $relatedEmbedableCollection = new PropertyNameCollection(['name']);
×
143

144
        $propertyNameCollectionFactoryProphecy->create(RelatedDummy::class)->willReturn($relatedNameCollection)->shouldBeCalled();
×
145
        $propertyNameCollectionFactoryProphecy->create(EmbeddableDummy::class)->willReturn($relatedEmbedableCollection)->shouldBeCalled();
×
146
        $propertyNameCollectionFactoryProphecy->create(UnknownDummy::class)->willReturn(new PropertyNameCollection(['id']))->shouldBeCalled();
×
147
        $propertyNameCollectionFactoryProphecy->create(ThirdLevel::class)->willReturn(new PropertyNameCollection(['id']))->shouldBeCalled();
×
148

149
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
150
        $relationPropertyMetadata = new ApiProperty();
×
151
        $relationPropertyMetadata = $relationPropertyMetadata->withReadableLink(true);
×
152

153
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', $callContext)->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
154
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy2', $callContext)->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
155
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy3', $callContext)->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
156
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy4', $callContext)->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
157
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy5', $callContext)->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
158
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'singleInheritanceRelation', $callContext)->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
159
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', $callContext)->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
160

161
        $idPropertyMetadata = new ApiProperty();
×
162
        $idPropertyMetadata = $idPropertyMetadata->withIdentifier(true);
×
163
        $namePropertyMetadata = new ApiProperty();
×
164
        $namePropertyMetadata = $namePropertyMetadata->withReadable(true);
×
165
        $embeddedDummyPropertyMetadata = new ApiProperty();
×
166
        $embeddedDummyPropertyMetadata = $embeddedDummyPropertyMetadata->withReadable(true);
×
167
        $notInDatabasePropertyMetadata = new ApiProperty();
×
168
        $notInDatabasePropertyMetadata = $notInDatabasePropertyMetadata->withReadable(true);
×
169
        $notReadablePropertyMetadata = new ApiProperty();
×
170
        $notReadablePropertyMetadata = $notReadablePropertyMetadata->withReadable(false);
×
171

172
        $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'id', $callContext)->willReturn($idPropertyMetadata)->shouldBeCalled();
×
173
        $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'name', $callContext)->willReturn($namePropertyMetadata)->shouldBeCalled();
×
174
        $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'embeddedDummy', $callContext)->willReturn($embeddedDummyPropertyMetadata)->shouldBeCalled();
×
175
        $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'notindatabase', $callContext)->willReturn($notInDatabasePropertyMetadata)->shouldBeCalled();
×
176
        $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'notreadable', $callContext)->willReturn($notReadablePropertyMetadata)->shouldBeCalled();
×
177
        $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'relation', $callContext)->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
178
        $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'thirdLevel', $callContext)->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
179
        $propertyMetadataFactoryProphecy->create(UnknownDummy::class, 'id', $callContext)->willReturn($idPropertyMetadata)->shouldBeCalled();
×
180
        $propertyMetadataFactoryProphecy->create(ThirdLevel::class, 'id', $callContext)->willReturn($idPropertyMetadata)->shouldBeCalled();
×
181

182
        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
×
183

184
        $classMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
185
        $classMetadataProphecy->associationMappings = [
×
186
            'relatedDummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [new JoinColumn(nullable: true)], 'targetEntity' => RelatedDummy::class],
×
187
            'relatedDummy2' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [new JoinColumn(nullable: false)], 'targetEntity' => UnknownDummy::class],
×
188
            'relatedDummy3' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinTable' => ['joinColumns' => [new JoinColumn(nullable: false)]], 'targetEntity' => UnknownDummy::class],
×
189
            'relatedDummy4' => ['fetch' => ClassMetadata::FETCH_EAGER, 'targetEntity' => UnknownDummy::class],
×
190
            'relatedDummy5' => ['fetch' => ClassMetadata::FETCH_LAZY, 'targetEntity' => UnknownDummy::class],
×
191
            'singleInheritanceRelation' => ['fetch' => ClassMetadata::FETCH_EAGER, 'targetEntity' => AbstractDummy::class],
×
192
            'relatedDummies' => ['fetch' => ClassMetadata::FETCH_EAGER, 'targetEntity' => RelatedDummy::class],
×
193
        ];
×
194

195
        $relatedClassMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
196

197
        foreach ($relatedNameCollection as $property) {
×
198
            if ('id' !== $property && 'embeddedDummy' !== $property) {
×
199
                $relatedClassMetadataProphecy->hasField($property)->willReturn('notindatabase' !== $property)->shouldBeCalled();
×
200
            }
201
        }
202
        $relatedClassMetadataProphecy->hasField('embeddedDummy.name')->willReturn(true)->shouldBeCalled();
×
203

204
        $relatedClassMetadataProphecy->associationMappings = [
×
205
            'relation' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [new JoinColumn(nullable: false)], 'targetEntity' => UnknownDummy::class],
×
206
            'thirdLevel' => ['fetch' => ClassMetadata::FETCH_EAGER, 'targetEntity' => ThirdLevel::class, 'sourceEntity' => RelatedDummy::class, 'inversedBy' => 'relatedDummies', 'type' => ClassMetadata::TO_ONE],
×
207
        ];
×
208

209
        $relatedClassMetadataProphecy->embeddedClasses = ['embeddedDummy' => ['class' => EmbeddableDummy::class]];
×
210

211
        $singleInheritanceClassMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
212
        $singleInheritanceClassMetadataProphecy->subClasses = [ConcreteDummy::class];
×
213

214
        $unknownClassMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
215
        $unknownClassMetadataProphecy->associationMappings = [];
×
216

217
        $thirdLevelMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
218
        $thirdLevelMetadataProphecy->associationMappings = [];
×
219

220
        $emProphecy = $this->prophesize(EntityManager::class);
×
221
        $emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
×
222
        $emProphecy->getClassMetadata(RelatedDummy::class)->shouldBeCalled()->willReturn($relatedClassMetadataProphecy->reveal());
×
223
        $emProphecy->getClassMetadata(AbstractDummy::class)->shouldBeCalled()->willReturn($singleInheritanceClassMetadataProphecy->reveal());
×
224
        $emProphecy->getClassMetadata(UnknownDummy::class)->shouldBeCalled()->willReturn($unknownClassMetadataProphecy->reveal());
×
225
        $emProphecy->getClassMetadata(ThirdLevel::class)->shouldBeCalled()->willReturn($thirdLevelMetadataProphecy->reveal());
×
226

227
        $queryBuilderProphecy->getRootAliases()->willReturn(['o']);
×
NEW
228
        $queryBuilderProphecy->getDQLPart('select')->willReturn([]);
×
229
        $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy);
×
230
        $queryBuilderProphecy->leftJoin('o.relatedDummy', 'relatedDummy_a1')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
231
        $queryBuilderProphecy->leftJoin('relatedDummy_a1.relation', 'relation_a2')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
232
        $queryBuilderProphecy->leftJoin('relatedDummy_a1.thirdLevel', 'thirdLevel_a3')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
233
        $queryBuilderProphecy->innerJoin('o.relatedDummy2', 'relatedDummy2_a4')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
234
        $queryBuilderProphecy->leftJoin('o.relatedDummy3', 'relatedDummy3_a5')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
235
        $queryBuilderProphecy->leftJoin('o.relatedDummy4', 'relatedDummy4_a6')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
236
        $queryBuilderProphecy->leftJoin('o.singleInheritanceRelation', 'singleInheritanceRelation_a7')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
237
        $queryBuilderProphecy->leftJoin('o.relatedDummies', 'relatedDummies_a8')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
238
        $queryBuilderProphecy->leftJoin('relatedDummies_a8.relation', 'relation_a9')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
239
        $queryBuilderProphecy->leftJoin('relatedDummies_a8.thirdLevel', 'thirdLevel_a10')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
240
        $queryBuilderProphecy->addSelect('partial relatedDummy_a1.{id,name,embeddedDummy.name}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
241
        $queryBuilderProphecy->addSelect('partial thirdLevel_a3.{id}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
242
        $queryBuilderProphecy->addSelect('partial relation_a2.{id}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
243
        $queryBuilderProphecy->addSelect('partial relatedDummy2_a4.{id}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
244
        $queryBuilderProphecy->addSelect('partial relatedDummy3_a5.{id}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
245
        $queryBuilderProphecy->addSelect('partial relatedDummy4_a6.{id}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
246
        $queryBuilderProphecy->addSelect('singleInheritanceRelation_a7')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
247
        $queryBuilderProphecy->addSelect('partial relatedDummies_a8.{id,name,embeddedDummy.name}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
248
        $queryBuilderProphecy->addSelect('partial relation_a9.{id}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
249
        $queryBuilderProphecy->addSelect('partial thirdLevel_a10.{id}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
250
        $queryBuilderProphecy->getDQLPart('join')->willReturn([]);
×
251
        $queryBuilderProphecy->getDQLPart('select')->willReturn([]);
×
252

253
        $queryBuilder = $queryBuilderProphecy->reveal();
×
254
        $orderExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, false, true);
×
255

256
        $orderExtensionTest->applyToItem($queryBuilder, new QueryNameGenerator(), Dummy::class, [], null, $context);
×
257
    }
258

259
    public function testCreateItemWithOperation(): void
260
    {
261
        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
×
262
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
263
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
264
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', ['serializer_groups' => ['foo']])->shouldBeCalled()->willReturn(new ApiProperty());
×
265

266
        $classMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
267
        $classMetadataProphecy->associationMappings = [
×
268
            'foo' => ['fetch' => 1],
×
269
        ];
×
270

271
        $emProphecy = $this->prophesize(EntityManager::class);
×
272
        $emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
×
273
        $queryBuilderProphecy->getRootAliases()->willReturn(['o']);
×
NEW
274
        $queryBuilderProphecy->getDQLPart('select')->willReturn([]);
×
UNCOV
275
        $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy);
×
276

277
        $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, false, true);
×
278
        $eagerExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, [], new Get(name: 'item_operation'), ['groups' => ['foo']]);
×
279
    }
280

281
    public function testCreateCollectionWithOperation(): void
282
    {
283
        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
×
284
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
285
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
286
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'foo', ['serializer_groups' => ['foo']])->shouldBeCalled()->willReturn(new ApiProperty());
×
287

288
        $classMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
289
        $classMetadataProphecy->associationMappings = [
×
290
            'foo' => ['fetch' => 1],
×
291
        ];
×
292

293
        $emProphecy = $this->prophesize(EntityManager::class);
×
294
        $emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
×
295
        $queryBuilderProphecy->getRootAliases()->willReturn(['o']);
×
NEW
296
        $queryBuilderProphecy->getDQLPart('select')->willReturn([]);
×
UNCOV
297
        $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy);
×
298

299
        $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, false, true);
×
300
        $eagerExtensionTest->applyToCollection($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, new GetCollection(name: 'collection_operation'), ['groups' => ['foo']]);
×
301
    }
302

303
    public function testDenormalizeItemWithCorrectResourceClass(): void
304
    {
305
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
306
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
307

308
        $classMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
309
        $classMetadataProphecy->associationMappings = [];
×
310

311
        // Dummy is the correct class for the denormalization context serialization groups, and we're fetching RelatedDummy
312
        $emProphecy = $this->prophesize(EntityManager::class);
×
313
        $emProphecy->getClassMetadata(Dummy::class)->shouldNotBeCalled();
×
314
        $emProphecy->getClassMetadata(RelatedDummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
×
315
        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
×
316
        $queryBuilderProphecy->getRootAliases()->willReturn(['o']);
×
NEW
317
        $queryBuilderProphecy->getDQLPart('select')->willReturn([]);
×
UNCOV
318
        $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy);
×
319

320
        $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, false, true);
×
321
        $eagerExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), RelatedDummy::class, ['id' => 1], new Get(name: 'get', normalizationContext: ['groups' => ['foo']]), ['resource_class' => Dummy::class]);
×
322
    }
323

324
    public function testDenormalizeItemWithExistingGroups(): void
325
    {
326
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
327
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
328

329
        $classMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
330
        $classMetadataProphecy->associationMappings = [];
×
331

332
        // groups exist from the context, we don't need to compute them again
333
        $emProphecy = $this->prophesize(EntityManager::class);
×
334
        $emProphecy->getClassMetadata(Dummy::class)->shouldNotBeCalled();
×
335
        $emProphecy->getClassMetadata(RelatedDummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
×
336
        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
×
337
        $queryBuilderProphecy->getRootAliases()->willReturn(['o']);
×
NEW
338
        $queryBuilderProphecy->getDQLPart('select')->willReturn([]);
×
UNCOV
339
        $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy);
×
340

341
        $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, false, true);
×
342
        $eagerExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), RelatedDummy::class, ['id' => 1], new Get(name: 'item_operation', normalizationContext: ['groups' => ['foo']]), [AbstractNormalizer::GROUPS => 'some_groups']);
×
343
    }
344

345
    public function testContextSwitch(): void
346
    {
347
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
348

349
        $relatedNameCollection = new PropertyNameCollection(['id', 'name']);
×
350
        $propertyNameCollectionFactoryProphecy->create(RelatedDummy::class)->willReturn($relatedNameCollection)->shouldBeCalled();
×
351

352
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
353
        $relationPropertyMetadata = new ApiProperty();
×
354
        $relationPropertyMetadata = $relationPropertyMetadata->withReadableLink(false);
×
355

356
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', ['serializer_groups' => ['foo'], 'normalization_groups' => 'foo'])->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
357
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', ['serializer_groups' => ['foo'], 'normalization_groups' => 'foo'])->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
358

359
        $idPropertyMetadata = new ApiProperty();
×
360
        $idPropertyMetadata = $idPropertyMetadata->withIdentifier(true);
×
361
        $namePropertyMetadata = new ApiProperty();
×
362
        $namePropertyMetadata = $namePropertyMetadata->withReadable(true);
×
363

364
        // When called via `relatedDummies` without context switch
365
        $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'id', ['serializer_groups' => ['foo'], 'normalization_groups' => 'foo'])->willReturn($idPropertyMetadata)->shouldBeCalled();
×
366
        $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'name', ['serializer_groups' => ['foo'], 'normalization_groups' => 'foo'])->willReturn($namePropertyMetadata)->shouldBeCalled();
×
367

368
        // When called via `relatedDummy` with context switch
369
        $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'id', ['normalization_groups' => ['bar'], 'denormalization_groups' => ['foo']])->willReturn($idPropertyMetadata)->shouldBeCalled();
×
370
        $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'name', ['normalization_groups' => ['bar'], 'denormalization_groups' => ['foo']])->willReturn($namePropertyMetadata)->shouldBeCalled();
×
371

372
        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
×
NEW
373
        $queryBuilderProphecy->getDQLPart('select')->willReturn([]);
×
374

375
        $classMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
376
        $classMetadataProphecy->associationMappings = [
×
377
            'relatedDummies' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => true]], 'targetEntity' => RelatedDummy::class],
×
378
            'relatedDummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => true]], 'targetEntity' => RelatedDummy::class],
×
379
        ];
×
380

381
        $relatedClassMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
382

383
        foreach ($relatedNameCollection as $property) {
×
384
            if ('id' !== $property && 'embeddedDummy' !== $property) {
×
NEW
385
                $relatedClassMetadataProphecy->hasField($property)->willReturn(true)->shouldBeCalled();
×
386
            }
387
        }
388

389
        $dummyClassMetadataInterfaceProphecy = $this->prophesize(ClassMetadataInterface::class);
×
390
        $relatedClassMetadataInterfaceProphecy = $this->prophesize(ClassMetadataInterface::class);
×
391
        $classMetadataFactoryProphecy = $this->prophesize(ClassMetadataFactoryInterface::class);
×
392

393
        $relatedDummyAttributeMetadata = new AttributeMetadata('relatedDummy');
×
394
        $relatedDummyAttributeMetadata->setNormalizationContextForGroups(['groups' => ['bar']], ['foo']);
×
395

396
        $dummyClassMetadataInterfaceProphecy->getAttributesMetadata()->willReturn(['relatedDummy' => $relatedDummyAttributeMetadata]);
×
397
        $relatedClassMetadataInterfaceProphecy->getAttributesMetadata()->willReturn([]);
×
398

399
        $classMetadataFactoryProphecy->getMetadataFor(RelatedDummy::class)->willReturn($relatedClassMetadataInterfaceProphecy->reveal());
×
400
        $classMetadataFactoryProphecy->getMetadataFor(Dummy::class)->willReturn($dummyClassMetadataInterfaceProphecy->reveal());
×
401

402
        $relatedClassMetadataProphecy->associationMappings = [];
×
403

404
        $emProphecy = $this->prophesize(EntityManager::class);
×
405
        $emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
×
406
        $emProphecy->getClassMetadata(RelatedDummy::class)->shouldBeCalled()->willReturn($relatedClassMetadataProphecy->reveal());
×
407

408
        $queryBuilderProphecy->getRootAliases()->willReturn(['o']);
×
409
        $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy);
×
410

411
        $queryBuilderProphecy->leftJoin('o.relatedDummies', 'relatedDummies_a1')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
412
        $queryBuilderProphecy->leftJoin('o.relatedDummy', 'relatedDummy_a2')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
413
        $queryBuilderProphecy->addSelect('partial relatedDummies_a1.{id,name}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
414
        $queryBuilderProphecy->addSelect('partial relatedDummy_a2.{id,name}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
415
        $queryBuilderProphecy->getDQLPart('join')->willReturn([]);
×
416

417
        $queryBuilder = $queryBuilderProphecy->reveal();
×
418
        $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, false, true, $classMetadataFactoryProphecy->reveal());
×
419
        $eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, new GetCollection(normalizationContext: [AbstractNormalizer::GROUPS => 'foo']));
×
420
    }
421

422
    public function testSameEntityWithDifferentPartialProperties(): void
423
    {
NEW
424
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
425

NEW
426
        $relatedNameCollection = new PropertyNameCollection(['id', 'name']);
×
NEW
427
        $propertyNameCollectionFactoryProphecy->create(RelatedDummy::class)->willReturn($relatedNameCollection)->shouldBeCalled();
×
428

NEW
429
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
430
        $relationPropertyMetadata = new ApiProperty();
×
NEW
431
        $relationPropertyMetadata = $relationPropertyMetadata->withReadableLink(false);
×
432

NEW
433
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy1', ['serializer_groups' => ['foo'], 'normalization_groups' => 'foo'])->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
NEW
434
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy2', ['serializer_groups' => ['foo'], 'normalization_groups' => 'foo'])->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
435

NEW
436
        $idPropertyMetadata = (new ApiProperty())->withIdentifier(true);
×
NEW
437
        $namePropertyMetadataGroupA = (new ApiProperty())->withReadable(true);
×
438
        // the property Name IS NOT readable in group B
NEW
439
        $namePropertyMetadataGroupB = (new ApiProperty())->withReadable(false);
×
440

441
        // When called via `relatedDummy1`
NEW
442
        $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'id', ['normalization_groups' => ['A'], 'denormalization_groups' => ['foo']])->willReturn($idPropertyMetadata)->shouldBeCalled();
×
NEW
443
        $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'name', ['normalization_groups' => ['A'], 'denormalization_groups' => ['foo']])->willReturn($namePropertyMetadataGroupA)->shouldBeCalled();
×
444

445
        // When called via `relatedDummy2`
NEW
446
        $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'id', ['normalization_groups' => ['B'], 'denormalization_groups' => ['foo']])->willReturn($idPropertyMetadata)->shouldBeCalled();
×
NEW
447
        $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'name', ['normalization_groups' => ['B'], 'denormalization_groups' => ['foo']])->willReturn($namePropertyMetadataGroupB)->shouldBeCalled();
×
448

NEW
449
        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
×
NEW
450
        $queryBuilderProphecy->getDQLPart('select')->willReturn([]);
×
451

NEW
452
        $classMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
NEW
453
        $classMetadataProphecy->associationMappings = [
×
NEW
454
            'relatedDummy1' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => true]], 'targetEntity' => RelatedDummy::class],
×
NEW
455
            'relatedDummy2' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => true]], 'targetEntity' => RelatedDummy::class],
×
NEW
456
        ];
×
457

NEW
458
        $relatedClassMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
459

NEW
460
        foreach ($relatedNameCollection as $property) {
×
NEW
461
            if ('id' !== $property && 'embeddedDummy' !== $property) {
×
NEW
462
                $relatedClassMetadataProphecy->hasField($property)->willReturn(true)->shouldBeCalled();
×
463
            }
464
        }
465

NEW
466
        $dummyClassMetadataInterfaceProphecy = $this->prophesize(ClassMetadataInterface::class);
×
NEW
467
        $relatedClassMetadataInterfaceProphecy = $this->prophesize(ClassMetadataInterface::class);
×
NEW
468
        $classMetadataFactoryProphecy = $this->prophesize(ClassMetadataFactoryInterface::class);
×
469

NEW
470
        $relatedDummy1AttributeMetadata = new AttributeMetadata('relatedDummy');
×
NEW
471
        $relatedDummy1AttributeMetadata->setNormalizationContextForGroups(['groups' => ['A']], ['foo']);
×
472

NEW
473
        $relatedDummy2AttributeMetadata = new AttributeMetadata('relatedDummy');
×
NEW
474
        $relatedDummy2AttributeMetadata->setNormalizationContextForGroups(['groups' => ['B']], ['foo']);
×
475

NEW
476
        $dummyClassMetadataInterfaceProphecy->getAttributesMetadata()->willReturn([
×
NEW
477
            'relatedDummy1' => $relatedDummy1AttributeMetadata,
×
NEW
478
            'relatedDummy2' => $relatedDummy2AttributeMetadata,
×
NEW
479
        ]);
×
NEW
480
        $relatedClassMetadataInterfaceProphecy->getAttributesMetadata()->willReturn([]);
×
481

NEW
482
        $classMetadataFactoryProphecy->getMetadataFor(RelatedDummy::class)->willReturn($relatedClassMetadataInterfaceProphecy->reveal());
×
NEW
483
        $classMetadataFactoryProphecy->getMetadataFor(Dummy::class)->willReturn($dummyClassMetadataInterfaceProphecy->reveal());
×
484

NEW
485
        $relatedClassMetadataProphecy->associationMappings = [];
×
486

NEW
487
        $emProphecy = $this->prophesize(EntityManager::class);
×
NEW
488
        $emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
×
NEW
489
        $emProphecy->getClassMetadata(RelatedDummy::class)->shouldBeCalled()->willReturn($relatedClassMetadataProphecy->reveal());
×
490

NEW
491
        $queryBuilderProphecy->getRootAliases()->willReturn(['o']);
×
NEW
492
        $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy);
×
493

NEW
494
        $queryBuilderProphecy->leftJoin('o.relatedDummy1', 'relatedDummy1_a1')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
NEW
495
        $queryBuilderProphecy->leftJoin('o.relatedDummy2', 'relatedDummy2_a2')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
NEW
496
        $queryBuilderProphecy->addSelect('partial relatedDummy1_a1.{id,name}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
497
        // here is the purpose of this test: name is not readable in group B, BUT it is part of the partial selection because it is readable in group A
NEW
498
        $queryBuilderProphecy->addSelect('partial relatedDummy2_a2.{id,name}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
NEW
499
        $queryBuilderProphecy->getDQLPart('join')->willReturn([]);
×
500

NEW
501
        $queryBuilder = $queryBuilderProphecy->reveal();
×
NEW
502
        $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, false, true, $classMetadataFactoryProphecy->reveal());
×
NEW
503
        $eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, new GetCollection(normalizationContext: [AbstractNormalizer::GROUPS => 'foo']));
×
504
    }
505

506
    public function testMaxJoinsReached(): void
507
    {
508
        $this->expectException(RuntimeException::class);
×
509
        $this->expectExceptionMessage('The total number of joined relations has exceeded the specified maximum. Raise the limit if necessary with the "api_platform.eager_loading.max_joins" configuration key (https://api-platform.com/docs/core/performance/#eager-loading), or limit the maximum serialization depth using the "enable_max_depth" option of the Symfony serializer (https://symfony.com/doc/current/components/serializer.html#handling-serialization-depth).');
×
510

511
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
512

513
        $relatedNameCollection = new PropertyNameCollection(['dummy']);
×
514
        $dummyNameCollection = new PropertyNameCollection(['relatedDummy']);
×
515

516
        $propertyNameCollectionFactoryProphecy->create(RelatedDummy::class)->willReturn($relatedNameCollection)->shouldBeCalled();
×
517
        $propertyNameCollectionFactoryProphecy->create(Dummy::class)->willReturn($dummyNameCollection)->shouldBeCalled();
×
518

519
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
520
        $relationPropertyMetadata = new ApiProperty();
×
521
        $relationPropertyMetadata = $relationPropertyMetadata->withReadableLink(true);
×
522

523
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', ['serializer_groups' => ['foo']])->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
524

525
        $relatedPropertyMetadata = new ApiProperty();
×
526
        $relatedPropertyMetadata = $relatedPropertyMetadata->withReadableLink(true);
×
527

528
        $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'dummy', ['serializer_groups' => ['foo']])->willReturn($relatedPropertyMetadata)->shouldBeCalled();
×
529

530
        $classMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
531
        $classMetadataProphecy->associationMappings = [
×
532
            'relatedDummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [new JoinColumn(nullable: false)], 'targetEntity' => RelatedDummy::class],
×
533
        ];
×
534
        $classMetadataProphecy->hasField('relatedDummy')->willReturn(true);
×
535

536
        $relatedClassMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
537
        $relatedClassMetadataProphecy->associationMappings = [
×
538
            'dummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [new JoinColumn(nullable: false)], 'targetEntity' => Dummy::class],
×
539
        ];
×
540
        $relatedClassMetadataProphecy->hasField('dummy')->willReturn(true);
×
541

542
        $emProphecy = $this->prophesize(EntityManager::class);
×
543
        $emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
×
544
        $emProphecy->getClassMetadata(RelatedDummy::class)->shouldBeCalled()->willReturn($relatedClassMetadataProphecy->reveal());
×
545

546
        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
×
547
        $queryBuilderProphecy->getRootAliases()->willReturn(['o']);
×
NEW
548
        $queryBuilderProphecy->getDQLPart('select')->willReturn([]);
×
UNCOV
549
        $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy);
×
550

NEW
551
        $queryBuilderProphecy->innerJoin(Argument::type('string'), Argument::type('string'))->willReturn($queryBuilderProphecy);
×
NEW
552
        $queryBuilderProphecy->addSelect(Argument::type('string'))->willReturn($queryBuilderProphecy);
×
UNCOV
553
        $queryBuilderProphecy->getDQLPart('join')->willReturn([]);
×
554

555
        $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, false, true);
×
556
        $eagerExtensionTest->applyToCollection($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, null, ['groups' => ['foo']]);
×
557
    }
558

559
    public function testMaxDepth(): void
560
    {
561
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
562

563
        $relatedNameCollection = new PropertyNameCollection(['dummy']);
×
564
        $dummyNameCollection = new PropertyNameCollection(['relatedDummy']);
×
565

566
        $propertyNameCollectionFactoryProphecy->create(RelatedDummy::class)->willReturn($relatedNameCollection)->shouldBeCalled();
×
567
        $propertyNameCollectionFactoryProphecy->create(Dummy::class)->willReturn($dummyNameCollection)->shouldBeCalled();
×
568

569
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
570
        $relationPropertyMetadata = new ApiProperty();
×
571
        $relationPropertyMetadata = $relationPropertyMetadata->withReadableLink(true);
×
572

573
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', ['serializer_groups' => ['foo'], 'normalization_groups' => ['foo']])->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
574

575
        $relatedPropertyMetadata = new ApiProperty();
×
576
        $relatedPropertyMetadata = $relatedPropertyMetadata->withReadableLink(true);
×
577

578
        $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'dummy', ['serializer_groups' => ['foo'], 'normalization_groups' => ['foo']])->willReturn($relatedPropertyMetadata)->shouldBeCalled();
×
579

580
        $classMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
581
        $classMetadataProphecy->associationMappings = [
×
582
            'relatedDummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [new JoinColumn(nullable: false)], 'targetEntity' => RelatedDummy::class],
×
583
        ];
×
584
        $classMetadataProphecy->hasField('relatedDummy')->willReturn(true);
×
585

586
        $relatedClassMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
587
        $relatedClassMetadataProphecy->associationMappings = [
×
588
            'dummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [new JoinColumn(nullable: false)], 'targetEntity' => Dummy::class],
×
589
        ];
×
590
        $relatedClassMetadataProphecy->hasField('dummy')->willReturn(true);
×
591

592
        $dummyClassMetadataInterfaceProphecy = $this->prophesize(ClassMetadataInterface::class);
×
593
        $relatedClassMetadataInterfaceProphecy = $this->prophesize(ClassMetadataInterface::class);
×
594
        $classMetadataFactoryProphecy = $this->prophesize(ClassMetadataFactoryInterface::class);
×
595

596
        $dummyAttributeMetadata = new AttributeMetadata('dummy');
×
597
        $dummyAttributeMetadata->setMaxDepth(2);
×
598

599
        $relatedAttributeMetadata = new AttributeMetadata('relatedDummy');
×
600
        $relatedAttributeMetadata->setMaxDepth(4);
×
601

602
        $dummyClassMetadataInterfaceProphecy->getAttributesMetadata()->willReturn(['relatedDummy' => $dummyAttributeMetadata]);
×
603
        $relatedClassMetadataInterfaceProphecy->getAttributesMetadata()->willReturn(['dummy' => $relatedAttributeMetadata]);
×
604

605
        $classMetadataFactoryProphecy->getMetadataFor(RelatedDummy::class)->willReturn($relatedClassMetadataInterfaceProphecy->reveal());
×
606
        $classMetadataFactoryProphecy->getMetadataFor(Dummy::class)->willReturn($dummyClassMetadataInterfaceProphecy->reveal());
×
607

608
        $emProphecy = $this->prophesize(EntityManager::class);
×
609
        $emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
×
610
        $emProphecy->getClassMetadata(RelatedDummy::class)->shouldBeCalled()->willReturn($relatedClassMetadataProphecy->reveal());
×
611

612
        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
×
613
        $queryBuilderProphecy->getRootAliases()->willReturn(['o']);
×
NEW
614
        $queryBuilderProphecy->getDQLPart('select')->willReturn([]);
×
UNCOV
615
        $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy);
×
616

617
        $queryBuilderProphecy->innerJoin(Argument::type('string'), Argument::type('string'))->shouldBeCalledTimes(2)->willReturn($queryBuilderProphecy);
×
618
        $queryBuilderProphecy->addSelect(Argument::type('string'))->shouldBeCalled()->willReturn($queryBuilderProphecy);
×
619
        $queryBuilderProphecy->getDQLPart('join')->willReturn([]);
×
620

621
        $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, false, true, $classMetadataFactoryProphecy->reveal());
×
622
        $eagerExtensionTest->applyToCollection($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, new GetCollection(normalizationContext: ['enable_max_depth' => 'true', 'groups' => ['foo']]));
×
623
    }
624

625
    public function testForceEager(): void
626
    {
627
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
628
        $propertyNameCollectionFactoryProphecy->create(UnknownDummy::class)->willReturn(new PropertyNameCollection(['id']))->shouldBeCalled();
×
629

630
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
631
        $relationPropertyMetadata = new ApiProperty();
×
632
        $relationPropertyMetadata = $relationPropertyMetadata->withReadableLink(true);
×
633

634
        $idPropertyMetadata = new ApiProperty();
×
635
        $idPropertyMetadata = $idPropertyMetadata->withIdentifier(true);
×
636

637
        $propertyMetadataFactoryProphecy->create(UnknownDummy::class, 'id', ['serializer_groups' => ['foobar'], 'normalization_groups' => 'foobar'])->willReturn($idPropertyMetadata)->shouldBeCalled();
×
638
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relation', ['serializer_groups' => ['foobar'], 'normalization_groups' => 'foobar'])->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
639

640
        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
×
641

642
        $classMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
643
        $classMetadataProphecy->associationMappings = [
×
644
            'relation' => ['fetch' => ClassMetadata::FETCH_LAZY, 'targetEntity' => UnknownDummy::class, 'joinColumns' => [new JoinColumn(nullable: false)]],
×
645
        ];
×
646

647
        $unknownClassMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
648
        $unknownClassMetadataProphecy->associationMappings = [];
×
649

650
        $emProphecy = $this->prophesize(EntityManager::class);
×
651
        $emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
×
652
        $emProphecy->getClassMetadata(UnknownDummy::class)->shouldBeCalled()->willReturn($unknownClassMetadataProphecy->reveal());
×
653

654
        $queryBuilderProphecy->innerJoin('o.relation', 'relation_a1')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
655
        $queryBuilderProphecy->addSelect('partial relation_a1.{id}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
656
        $queryBuilderProphecy->getDQLPart('join')->willReturn([]);
×
657

658
        $queryBuilderProphecy->getRootAliases()->willReturn(['o']);
×
NEW
659
        $queryBuilderProphecy->getDQLPart('select')->willReturn([]);
×
UNCOV
660
        $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy);
×
661

662
        $orderExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, true, true);
×
663
        $orderExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, [], new Get(normalizationContext: [AbstractNormalizer::GROUPS => 'foobar']));
×
664
    }
665

666
    public function testExtraLazy(): void
667
    {
668
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
669

670
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
671
        $relationPropertyMetadata = new ApiProperty();
×
672
        $relationPropertyMetadata = $relationPropertyMetadata->withReadableLink(true);
×
673

674
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relation', ['serializer_groups' => ['foobar'], 'normalization_groups' => 'foobar'])->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
675

676
        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
×
677

678
        $classMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
679
        $classMetadataProphecy->associationMappings = [
×
680
            'relation' => ['fetch' => ClassMetadata::FETCH_EXTRA_LAZY, 'targetEntity' => UnknownDummy::class, 'joinColumns' => [['nullable' => false]]],
×
681
        ];
×
682

683
        $unknownClassMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
684
        $unknownClassMetadataProphecy->associationMappings = [];
×
685

686
        $emProphecy = $this->prophesize(EntityManager::class);
×
687
        $emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
×
688

689
        $queryBuilderProphecy->getRootAliases()->willReturn(['o']);
×
NEW
690
        $queryBuilderProphecy->getDQLPart('select')->willReturn([]);
×
UNCOV
691
        $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy);
×
692

693
        $orderExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, true, true);
×
694
        $orderExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, [], new Get(normalizationContext: [AbstractNormalizer::GROUPS => 'foobar']));
×
695
    }
696

697
    public function testResourceClassNotFoundException(): void
698
    {
699
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
700

701
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
702
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relation', ['serializer_groups' => ['foo'], 'normalization_groups' => 'foo'])->willThrow(new ResourceClassNotFoundException());
×
703

704
        $classMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
705
        $classMetadataProphecy->associationMappings = [
×
706
            'relation' => ['fetch' => ClassMetadata::FETCH_LAZY, 'targetEntity' => UnknownDummy::class, 'joinColumns' => [['nullable' => false]]],
×
707
        ];
×
708
        $emProphecy = $this->prophesize(EntityManager::class);
×
709
        $emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
×
710
        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
×
711
        $queryBuilderProphecy->getRootAliases()->willReturn(['o']);
×
NEW
712
        $queryBuilderProphecy->getDQLPart('select')->willReturn([]);
×
UNCOV
713
        $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy);
×
714

715
        $orderExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, true, true);
×
716
        $orderExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, [], new Get(normalizationContext: [AbstractNormalizer::GROUPS => 'foo']));
×
717
    }
718

719
    public function testPropertyNotFoundException(): void
720
    {
721
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
722

723
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
724
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relation', ['serializer_groups' => ['foo'], 'normalization_groups' => 'foo'])->willThrow(new PropertyNotFoundException());
×
725

726
        $classMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
727
        $classMetadataProphecy->associationMappings = [
×
728
            'relation' => ['fetch' => ClassMetadata::FETCH_LAZY, 'targetEntity' => UnknownDummy::class, 'joinColumns' => [['nullable' => false]]],
×
729
        ];
×
730
        $emProphecy = $this->prophesize(EntityManager::class);
×
731
        $emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
×
732
        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
×
733
        $queryBuilderProphecy->getRootAliases()->willReturn(['o']);
×
NEW
734
        $queryBuilderProphecy->getDQLPart('select')->willReturn([]);
×
UNCOV
735
        $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy);
×
736

737
        $orderExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, true, true);
×
738
        $orderExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, [], new Get(normalizationContext: [AbstractNormalizer::GROUPS => 'foo']));
×
739
    }
740

741
    public function testResourceClassNotFoundExceptionPropertyNameCollection(): void
742
    {
743
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
744
        $propertyNameCollectionFactoryProphecy->create(UnknownDummy::class)->willThrow(new ResourceClassNotFoundException());
×
745

746
        $relationPropertyMetadata = new ApiProperty();
×
747
        $relationPropertyMetadata = $relationPropertyMetadata->withReadableLink(true);
×
748
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
749
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relation', ['serializer_groups' => ['foo'], 'normalization_groups' => 'foo'])->willReturn($relationPropertyMetadata);
×
750

751
        $classMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
752
        $classMetadataProphecy->associationMappings = [
×
753
            'relation' => ['fetch' => ClassMetadata::FETCH_LAZY, 'targetEntity' => UnknownDummy::class, 'joinColumns' => [new JoinColumn(nullable: false)]],
×
754
        ];
×
755
        $emProphecy = $this->prophesize(EntityManager::class);
×
756
        $emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
×
757
        $emProphecy->getClassMetadata(UnknownDummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
×
758
        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
×
759
        $queryBuilderProphecy->getRootAliases()->willReturn(['o']);
×
NEW
760
        $queryBuilderProphecy->getDQLPart('select')->willReturn([]);
×
761
        $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy);
×
762
        $queryBuilderProphecy->innerJoin('o.relation', 'relation_a1')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
763
        $queryBuilderProphecy->getDQLPart('join')->willReturn([]);
×
764

765
        $orderExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, true, true);
×
766
        $orderExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, [], new Get(normalizationContext: [AbstractNormalizer::GROUPS => 'foo']));
×
767
    }
768

769
    public function testAttributes(): void
770
    {
771
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
772

773
        $relatedNameCollection = new PropertyNameCollection(['id', 'name']);
×
774
        $propertyNameCollectionFactoryProphecy->create(RelatedDummy::class)->willReturn($relatedNameCollection)->shouldBeCalled();
×
775

776
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
777
        $relationPropertyMetadata = new ApiProperty();
×
778
        $relationPropertyMetadata = $relationPropertyMetadata->withReadableLink(false);
×
779

780
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', ['serializer_groups' => ['foo'], 'normalization_groups' => 'foo'])->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
781
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', ['serializer_groups' => ['foo'], 'normalization_groups' => 'foo'])->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
782

783
        $idPropertyMetadata = new ApiProperty();
×
784
        $idPropertyMetadata = $idPropertyMetadata->withIdentifier(true);
×
785
        $namePropertyMetadata = new ApiProperty();
×
786
        $namePropertyMetadata = $namePropertyMetadata->withReadable(true);
×
787

788
        $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'id', ['serializer_groups' => ['foo'], 'normalization_groups' => 'foo'])->willReturn($idPropertyMetadata)->shouldBeCalled();
×
789
        $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'name', ['serializer_groups' => ['foo'], 'normalization_groups' => 'foo'])->willReturn($namePropertyMetadata)->shouldBeCalled();
×
790

791
        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
×
792

793
        $classMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
794
        $classMetadataProphecy->associationMappings = [
×
795
            'relatedDummies' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => true]], 'targetEntity' => RelatedDummy::class],
×
796
            'relatedDummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => true]], 'targetEntity' => RelatedDummy::class],
×
797
        ];
×
798

799
        $relatedClassMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
800

801
        foreach ($relatedNameCollection as $property) {
×
802
            if ('id' !== $property) {
×
803
                $relatedClassMetadataProphecy->hasField($property)->willReturn(true)->shouldBeCalled();
×
804
            }
805
        }
806

807
        $relatedClassMetadataProphecy->associationMappings = [];
×
808

809
        $emProphecy = $this->prophesize(EntityManager::class);
×
810
        $emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
×
811
        $emProphecy->getClassMetadata(RelatedDummy::class)->shouldBeCalled()->willReturn($relatedClassMetadataProphecy->reveal());
×
812

813
        $queryBuilderProphecy->getRootAliases()->willReturn(['o']);
×
NEW
814
        $queryBuilderProphecy->getDQLPart('select')->willReturn([]);
×
UNCOV
815
        $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy);
×
816

817
        $queryBuilderProphecy->leftJoin('o.relatedDummies', 'relatedDummies_a1')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
818
        $queryBuilderProphecy->leftJoin('o.relatedDummy', 'relatedDummy_a2')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
819
        $queryBuilderProphecy->addSelect('partial relatedDummies_a1.{id,name}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
820
        $queryBuilderProphecy->addSelect('partial relatedDummy_a2.{id,name}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
821
        $queryBuilderProphecy->getDQLPart('join')->willReturn([]);
×
822

823
        $queryBuilder = $queryBuilderProphecy->reveal();
×
824
        $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, false, true);
×
825
        $eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, new GetCollection(normalizationContext: [AbstractNormalizer::GROUPS => 'foo']));
×
826
    }
827

828
    public function testNotInAttributes(): void
829
    {
830
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
831
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
832
        $relationPropertyMetadata = new ApiProperty();
×
833
        $relationPropertyMetadata = $relationPropertyMetadata->withReadableLink(true);
×
834

835
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', ['serializer_groups' => ['foo'], 'normalization_groups' => 'foo'])->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
836

837
        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
×
838

839
        $classMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
840
        $classMetadataProphecy->associationMappings = [
×
841
            'relatedDummy' => ['fetch' => 3, 'joinColumns' => [['nullable' => true]], 'targetEntity' => RelatedDummy::class],
×
842
        ];
×
843

844
        $relatedClassMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
845
        $relatedClassMetadataProphecy->associationMappings = [];
×
846

847
        $emProphecy = $this->prophesize(EntityManager::class);
×
848
        $emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
×
849

850
        $queryBuilderProphecy->getRootAliases()->willReturn(['o']);
×
NEW
851
        $queryBuilderProphecy->getDQLPart('select')->willReturn([]);
×
UNCOV
852
        $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy);
×
853

854
        $queryBuilder = $queryBuilderProphecy->reveal();
×
855
        $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, false, true);
×
856
        $eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, new GetCollection(normalizationContext: [AbstractNormalizer::GROUPS => 'foo', AbstractNormalizer::ATTRIBUTES => ['relatedDummy']]));
×
857
    }
858

859
    public function testOnlyOneRelationNotInAttributes(): void
860
    {
861
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
862

863
        $relatedNameCollection = new PropertyNameCollection(['id', 'name']);
×
864
        $propertyNameCollectionFactoryProphecy->create(RelatedDummy::class)->willReturn($relatedNameCollection)->shouldBeCalled();
×
865

866
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
867
        $relationPropertyMetadata = new ApiProperty();
×
868
        $relationPropertyMetadata = $relationPropertyMetadata->withReadableLink(false);
×
869

870
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', ['serializer_groups' => ['foo'], 'normalization_groups' => 'foo'])->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
871
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', ['serializer_groups' => ['foo'], 'normalization_groups' => 'foo'])->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
872

873
        $idPropertyMetadata = new ApiProperty();
×
874
        $idPropertyMetadata = $idPropertyMetadata->withIdentifier(true);
×
875
        $namePropertyMetadata = new ApiProperty();
×
876
        $namePropertyMetadata = $namePropertyMetadata->withReadable(true);
×
877

878
        $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'id', ['serializer_groups' => ['foo'], 'normalization_groups' => 'foo'])->willReturn($idPropertyMetadata)->shouldBeCalled();
×
879
        $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'name', ['serializer_groups' => ['foo'], 'normalization_groups' => 'foo'])->willReturn($namePropertyMetadata)->shouldBeCalled();
×
880

881
        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
×
882

883
        $classMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
884
        $classMetadataProphecy->associationMappings = [
×
885
            'relatedDummies' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => true]], 'targetEntity' => RelatedDummy::class],
×
886
            'relatedDummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => true]], 'targetEntity' => RelatedDummy::class],
×
887
        ];
×
888

889
        $relatedClassMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
890

891
        foreach ($relatedNameCollection as $property) {
×
892
            if ('id' !== $property) {
×
893
                $relatedClassMetadataProphecy->hasField($property)->willReturn(true)->shouldBeCalled();
×
894
            }
895
        }
896

897
        $relatedClassMetadataProphecy->associationMappings = [];
×
898

899
        $emProphecy = $this->prophesize(EntityManager::class);
×
900
        $emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
×
901
        $emProphecy->getClassMetadata(RelatedDummy::class)->shouldBeCalled()->willReturn($relatedClassMetadataProphecy->reveal());
×
902

903
        $queryBuilderProphecy->getRootAliases()->willReturn(['o']);
×
NEW
904
        $queryBuilderProphecy->getDQLPart('select')->willReturn([]);
×
UNCOV
905
        $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy);
×
906

907
        $queryBuilderProphecy->leftJoin('o.relatedDummy', 'relatedDummy_a1')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
908
        $queryBuilderProphecy->addSelect('partial relatedDummy_a1.{id,name}')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
909
        $queryBuilderProphecy->getDQLPart('join')->willReturn([]);
×
910

911
        $queryBuilder = $queryBuilderProphecy->reveal();
×
912
        $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, false, true);
×
913
        $eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, new GetCollection(normalizationContext: [AbstractNormalizer::GROUPS => 'foo', AbstractNormalizer::ATTRIBUTES => ['relatedDummy' => ['id', 'name']]]));
×
914
    }
915

916
    public function testApplyToCollectionNoPartial(): void
917
    {
918
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
919

920
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
921
        $relationPropertyMetadata = new ApiProperty();
×
922
        $relationPropertyMetadata = $relationPropertyMetadata->withReadableLink(true);
×
923

924
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', ['serializer_groups' => ['foo'], 'normalization_groups' => 'foo'])->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
925
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy2', ['serializer_groups' => ['foo'], 'normalization_groups' => 'foo'])->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
926

927
        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
×
928

929
        $classMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
930
        $classMetadataProphecy->associationMappings = [
×
931
            'relatedDummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [new JoinColumn(nullable: true)], 'targetEntity' => RelatedDummy::class],
×
932
            'relatedDummy2' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [new JoinColumn(nullable: false)], 'targetEntity' => RelatedDummy::class],
×
933
        ];
×
934

935
        $emProphecy = $this->prophesize(EntityManager::class);
×
936
        $relatedClassMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
937
        $relatedClassMetadataProphecy->associationMappings = [];
×
938
        $emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
×
939
        $emProphecy->getClassMetadata(RelatedDummy::class)->shouldBeCalled()->willReturn($relatedClassMetadataProphecy->reveal());
×
940

941
        $queryBuilderProphecy->getRootAliases()->willReturn(['o']);
×
NEW
942
        $queryBuilderProphecy->getDQLPart('select')->willReturn([]);
×
UNCOV
943
        $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy);
×
944

945
        $queryBuilderProphecy->leftJoin('o.relatedDummy', 'relatedDummy_a1')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
946
        $queryBuilderProphecy->innerJoin('o.relatedDummy2', 'relatedDummy2_a2')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
947
        $queryBuilderProphecy->addSelect('relatedDummy_a1')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
948
        $queryBuilderProphecy->addSelect('relatedDummy2_a2')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
949
        $queryBuilderProphecy->getDQLPart('join')->willReturn([]);
×
950
        $queryBuilderProphecy->getDQLPart('select')->willReturn([]);
×
951

952
        $queryBuilder = $queryBuilderProphecy->reveal();
×
953
        $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30);
×
954
        $eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, new GetCollection(normalizationContext: [AbstractNormalizer::GROUPS => 'foo']));
×
955
    }
956

957
    public function testApplyToCollectionWithANonReadableButFetchEagerProperty(): void
958
    {
959
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
960

961
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
962
        $relationPropertyMetadata = new ApiProperty();
×
963
        $relationPropertyMetadata = $relationPropertyMetadata->withFetchEager(true);
×
964
        $relationPropertyMetadata = $relationPropertyMetadata->withReadableLink(false);
×
965
        $relationPropertyMetadata = $relationPropertyMetadata->withReadable(false);
×
966

967
        $relationPropertyMetadata = $relationPropertyMetadata->withReadableLink(false);
×
968
        $relationPropertyMetadata = $relationPropertyMetadata->withReadable(false);
×
969

970
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', ['serializer_groups' => ['foo'], 'normalization_groups' => 'foo'])->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
971
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy2', ['serializer_groups' => ['foo'], 'normalization_groups' => 'foo'])->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
972

973
        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
×
974

975
        $classMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
976
        $classMetadataProphecy->associationMappings = [
×
977
            'relatedDummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [new JoinColumn(nullable: true)], 'targetEntity' => RelatedDummy::class],
×
978
            'relatedDummy2' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [new JoinColumn(nullable: false)], 'targetEntity' => RelatedDummy::class],
×
979
        ];
×
980

981
        $emProphecy = $this->prophesize(EntityManager::class);
×
982
        $relatedClassMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
983
        $relatedClassMetadataProphecy->associationMappings = [];
×
984
        $emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
×
985
        $emProphecy->getClassMetadata(RelatedDummy::class)->shouldBeCalled()->willReturn($relatedClassMetadataProphecy->reveal());
×
986

987
        $queryBuilderProphecy->getRootAliases()->willReturn(['o']);
×
NEW
988
        $queryBuilderProphecy->getDQLPart('select')->willReturn([]);
×
UNCOV
989
        $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy);
×
990

991
        $queryBuilderProphecy->leftJoin('o.relatedDummy', 'relatedDummy_a1')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
992
        $queryBuilderProphecy->innerJoin('o.relatedDummy2', 'relatedDummy2_a2')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
993
        $queryBuilderProphecy->addSelect('relatedDummy_a1')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
994
        $queryBuilderProphecy->addSelect('relatedDummy2_a2')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
995
        $queryBuilderProphecy->getDQLPart('join')->willReturn([]);
×
996
        $queryBuilderProphecy->getDQLPart('select')->willReturn([]);
×
997

998
        $queryBuilder = $queryBuilderProphecy->reveal();
×
999
        $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30);
×
1000
        $eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, new GetCollection(normalizationContext: [AbstractNormalizer::GROUPS => 'foo']));
×
1001
    }
1002

1003
    #[\PHPUnit\Framework\Attributes\DataProvider('provideExistingJoinCases')]
1004
    public function testApplyToCollectionWithExistingJoin(string $joinType): void
1005
    {
1006
        $context = ['groups' => ['foo']];
×
1007
        $callContext = ['serializer_groups' => ['foo'], 'normalization_groups' => 'foo'];
×
1008

1009
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1010

1011
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1012
        $relationPropertyMetadata = new ApiProperty();
×
1013
        $relationPropertyMetadata = $relationPropertyMetadata->withReadableLink(true);
×
1014

1015
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', $callContext)->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
1016

1017
        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
×
1018

1019
        $classMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
1020
        $classMetadataProphecy->associationMappings = [
×
1021
            'relatedDummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => true]], 'targetEntity' => RelatedDummy::class],
×
1022
        ];
×
1023

1024
        $relatedClassMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
1025

1026
        $emProphecy = $this->prophesize(EntityManager::class);
×
1027
        $emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
×
1028
        $emProphecy->getClassMetadata(RelatedDummy::class)->shouldBeCalled()->willReturn($relatedClassMetadataProphecy->reveal());
×
1029

1030
        $queryBuilderProphecy->getRootAliases()->willReturn(['o']);
×
NEW
1031
        $queryBuilderProphecy->getDQLPart('select')->willReturn([]);
×
1032
        $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy);
×
1033
        $queryBuilderProphecy->getDQLPart('join')->willReturn([
×
1034
            'o' => [
×
1035
                new Join($joinType, 'o.relatedDummy', 'existing_join_alias'),
×
1036
            ],
×
1037
        ]);
×
1038
        $queryBuilderProphecy->getDQLPart('select')->willReturn([]);
×
1039
        $queryBuilderProphecy->addSelect('existing_join_alias')->shouldBeCalledTimes(1)->willReturn($queryBuilderProphecy);
×
1040

1041
        $queryBuilder = $queryBuilderProphecy->reveal();
×
1042
        $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30, false);
×
1043
        $eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, new GetCollection(normalizationContext: [AbstractNormalizer::GROUPS => 'foo']), $context);
×
1044
    }
1045

1046
    public static function provideExistingJoinCases(): iterable
1047
    {
1048
        yield [Join::LEFT_JOIN];
×
1049
        yield [Join::INNER_JOIN];
×
1050
    }
1051

1052
    public function testApplyToCollectionWithAReadableButNotFetchEagerProperty(): void
1053
    {
1054
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1055

1056
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1057
        $relationPropertyMetadata = new ApiProperty();
×
1058
        $relationPropertyMetadata = $relationPropertyMetadata->withFetchEager(false);
×
1059
        $relationPropertyMetadata = $relationPropertyMetadata->withReadableLink(true);
×
1060
        $relationPropertyMetadata = $relationPropertyMetadata->withReadable(true);
×
1061

1062
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', ['serializer_groups' => ['foo'], 'normalization_groups' => 'foo'])->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
1063
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy2', ['serializer_groups' => ['foo'], 'normalization_groups' => 'foo'])->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
1064

1065
        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
×
1066

1067
        $classMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
1068
        $classMetadataProphecy->associationMappings = [
×
1069
            'relatedDummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => true]], 'targetEntity' => RelatedDummy::class],
×
1070
            'relatedDummy2' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => false]], 'targetEntity' => RelatedDummy::class],
×
1071
        ];
×
1072

1073
        $emProphecy = $this->prophesize(EntityManager::class);
×
1074
        $emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
×
1075
        $emProphecy->getClassMetadata(RelatedDummy::class)->shouldNotBecalled();
×
1076

1077
        $queryBuilderProphecy->getRootAliases()->willReturn(['o']);
×
NEW
1078
        $queryBuilderProphecy->getDQLPart('select')->willReturn([]);
×
UNCOV
1079
        $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy);
×
1080

1081
        $queryBuilderProphecy->leftJoin('o.relatedDummy', 'relatedDummy_a1')->shouldNotBeCalled();
×
1082
        $queryBuilderProphecy->innerJoin('o.relatedDummy2', 'relatedDummy2_a2')->shouldNotBeCalled();
×
1083
        $queryBuilderProphecy->addSelect('relatedDummy_a1')->shouldNotBeCalled();
×
1084
        $queryBuilderProphecy->addSelect('relatedDummy2_a2')->shouldNotBeCalled();
×
1085

1086
        $queryBuilder = $queryBuilderProphecy->reveal();
×
1087
        $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30);
×
1088
        $eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, new GetCollection(normalizationContext: [AbstractNormalizer::GROUPS => 'foo']));
×
1089
    }
1090

1091
    public function testAvoidFetchCollectionOnIriOnlyProperty(): void
1092
    {
1093
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
1094

1095
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
1096
        $relationPropertyMetadata = new ApiProperty();
×
1097
        $relationPropertyMetadata = $relationPropertyMetadata->withFetchEager(true);
×
1098
        $relationPropertyMetadata = $relationPropertyMetadata->withReadableLink(true);
×
1099
        $relationPropertyMetadata = $relationPropertyMetadata->withReadable(true);
×
1100
        $relationPropertyMetadata = $relationPropertyMetadata->withUriTemplate('/property-collection-relations');
×
1101

1102
        $propertyMetadataFactoryProphecy->create(PropertyCollectionIriOnly::class, 'propertyCollectionIriOnlyRelation', ['serializer_groups' => ['read'], 'normalization_groups' => 'read'])->willReturn($relationPropertyMetadata)->shouldBeCalled();
×
1103

1104
        $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
×
1105

1106
        $classMetadataProphecy = $this->prophesize(ClassMetadata::class);
×
1107
        $classMetadataProphecy->associationMappings = [
×
1108
            'propertyCollectionIriOnlyRelation' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => true]], 'targetEntity' => PropertyCollectionIriOnlyRelation::class],
×
1109
        ];
×
1110

1111
        $emProphecy = $this->prophesize(EntityManager::class);
×
1112
        $emProphecy->getClassMetadata(PropertyCollectionIriOnly::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
×
1113
        $emProphecy->getClassMetadata(PropertyCollectionIriOnlyRelation::class)->shouldNotBecalled();
×
1114

1115
        $queryBuilderProphecy->getRootAliases()->willReturn(['o']);
×
NEW
1116
        $queryBuilderProphecy->getDQLPart('select')->willReturn([]);
×
UNCOV
1117
        $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy);
×
1118

1119
        $queryBuilderProphecy->leftJoin('o.propertyCollectionIriOnlyRelation', 'propertyCollectionIriOnlyRelation_a1')->shouldNotBeCalled();
×
1120
        $queryBuilderProphecy->addSelect('propertyCollectionIriOnlyRelation_a1')->shouldNotBeCalled();
×
1121

1122
        $queryBuilder = $queryBuilderProphecy->reveal();
×
1123
        $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), 30);
×
1124
        $eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), PropertyCollectionIriOnly::class, new GetCollection(normalizationContext: [AbstractNormalizer::GROUPS => 'read']));
×
1125
    }
1126
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc