Completed
Pull Request — master (#5662)
by Jeremy
10:00
created

testMatchingWithLimit()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 17
rs 9.4285
cc 1
eloc 10
nc 1
nop 0
1
<?php
2
3
namespace Doctrine\Tests\ORM\Functional;
4
5
use Doctrine\Common\Collections\Criteria;
6
use Doctrine\Tests\Models\CMS\CmsUser,
7
    Doctrine\Tests\Models\CMS\CmsGroup,
8
    Doctrine\Common\Collections\ArrayCollection;
9
10
/**
11
 * Basic many-to-many association tests.
12
 * ("Working with associations")
13
 *
14
 * @author robo
15
 */
16
class ManyToManyBasicAssociationTest extends \Doctrine\Tests\OrmFunctionalTestCase
17
{
18
    protected function setUp()
19
    {
20
        $this->useModelSet('cms');
21
        parent::setUp();
22
    }
23
24
    public function testUnsetManyToMany()
25
    {
26
        $user = $this->addCmsUserGblancoWithGroups(1);
27
28
        unset($user->groups[0]->users[0]); // inverse side
29
        unset($user->groups[0]); // owning side!
30
31
        $this->_em->flush();
32
33
        // Check that the link in the association table has been deleted
34
        $this->assertGblancoGroupCountIs(0);
35
    }
36
37
    public function testBasicManyToManyJoin()
38
    {
39
        $user = $this->addCmsUserGblancoWithGroups(1);
0 ignored issues
show
Unused Code introduced by
$user is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
40
        $this->_em->clear();
41
42
        $this->assertEquals(0, $this->_em->getUnitOfWork()->size());
43
44
        $query = $this->_em->createQuery("select u, g from Doctrine\Tests\Models\CMS\CmsUser u join u.groups g");
45
46
        $result = $query->getResult();
47
48
        $this->assertEquals(2, $this->_em->getUnitOfWork()->size());
49
        $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0]);
50
        $this->assertEquals('Guilherme', $result[0]->name);
51
        $this->assertEquals(1, $result[0]->getGroups()->count());
52
        $groups = $result[0]->getGroups();
53
        $this->assertEquals('Developers_0', $groups[0]->getName());
54
55
        $this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_MANAGED, $this->_em->getUnitOfWork()->getEntityState($result[0]));
56
        $this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_MANAGED, $this->_em->getUnitOfWork()->getEntityState($groups[0]));
57
58
        $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $groups);
59
        $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $groups[0]->getUsers());
60
61
        $groups[0]->getUsers()->clear();
62
        $groups->clear();
63
64
        $this->_em->flush();
65
        $this->_em->clear();
66
67
        $query = $this->_em->createQuery("select u, g from Doctrine\Tests\Models\CMS\CmsUser u join u.groups g");
68
        $this->assertEquals(0, count($query->getResult()));
69
    }
70
71
    public function testManyToManyAddRemove()
72
    {
73
        $user = $this->addCmsUserGblancoWithGroups(2);
74
        $this->_em->clear();
75
76
        $uRep = $this->_em->getRepository(get_class($user));
77
78
        // Get user
79
        $user = $uRep->findOneById($user->getId());
0 ignored issues
show
Documentation Bug introduced by
The method findOneById does not exist on object<Doctrine\ORM\EntityRepository>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
80
81
        $this->assertNotNull($user, "Has to return exactly one entry.");
82
83
        $this->assertFalse($user->getGroups()->isInitialized());
84
85
        // Check groups
86
        $this->assertEquals(2, $user->getGroups()->count());
87
88
        $this->assertTrue($user->getGroups()->isInitialized());
89
90
        // Remove first group
91
        unset($user->groups[0]);
92
        //$user->getGroups()->remove(0);
93
94
        $this->_em->flush();
95
        $this->_em->clear();
96
97
        // Reload same user
98
        $user2 = $uRep->findOneById($user->getId());
0 ignored issues
show
Documentation Bug introduced by
The method findOneById does not exist on object<Doctrine\ORM\EntityRepository>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
99
100
        // Check groups
101
        $this->assertEquals(1, $user2->getGroups()->count());
102
    }
103
104
    public function testManyToManyInverseSideIgnored()
105
    {
106
        $user = $this->addCmsUserGblancoWithGroups(0);
107
108
        $group = new CmsGroup;
109
        $group->name = 'Humans';
110
111
        // modify directly, addUser() would also (properly) set the owning side
112
        $group->users[] = $user;
113
114
        $this->_em->persist($user);
115
        $this->_em->persist($group);
116
        $this->_em->flush();
117
        $this->_em->clear();
118
119
        // Association should not exist
120
        $user2 = $this->_em->find(get_class($user), $user->getId());
121
122
        $this->assertNotNull($user2, "Has to return exactly one entry.");
123
        $this->assertEquals(0, $user2->getGroups()->count());
124
    }
125
126
    public function testManyToManyCollectionClearing()
127
    {
128
        $user = $this->addCmsUserGblancoWithGroups($groupCount = 10);
129
130
        // Check that there are indeed 10 links in the association table
131
        $this->assertGblancoGroupCountIs($groupCount);
132
133
        $user->groups->clear();
134
135
        $this->_em->flush();
136
137
        // Check that the links in the association table have been deleted
138
        $this->assertGblancoGroupCountIs(0);
139
    }
140
141
    public function testManyToManyCollectionClearAndAdd()
142
    {
143
        $user = $this->addCmsUserGblancoWithGroups($groupCount = 10);
144
145
        $groups = $user->groups->toArray();
146
        $user->groups->clear();
147
148
        foreach ($groups AS $group) {
149
            $user->groups[] = $group;
150
        }
151
152
        $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $user->groups);
153
        $this->assertTrue($user->groups->isDirty());
154
155
        $this->assertEquals($groupCount, count($user->groups), "There should be 10 groups in the collection.");
156
157
        $this->_em->flush();
158
159
        $this->assertGblancoGroupCountIs($groupCount);
160
    }
161
162
    /**
163
     * @param int $expectedGroupCount
164
     */
165
    public function assertGblancoGroupCountIs($expectedGroupCount)
166
    {
167
        $countDql = "SELECT count(g.id) FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN u.groups g WHERE u.username = 'gblanco'";
168
        $this->assertEquals(
169
            $expectedGroupCount,
170
            $this->_em->createQuery($countDql)->getSingleScalarResult(),
171
            "Failed to verify that CmsUser with username 'gblanco' has a group count of 10 with a DQL count query."
172
        );
173
    }
174
175
    public function testRetrieveManyToManyAndAddMore()
176
    {
177
        $user = $this->addCmsUserGblancoWithGroups(2);
178
179
        $group = new CmsGroup();
180
        $group->name = 'Developers_Fresh';
181
        $this->_em->persist($group);
182
        $this->_em->flush();
183
184
        $this->_em->clear();
185
186
        /* @var $freshUser CmsUser */
187
        $freshUser = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $user->getId());
188
        $newGroup = new CmsGroup();
189
        $newGroup->setName('12Monkeys');
190
        $freshUser->addGroup($newGroup);
191
192
        $this->assertFalse($freshUser->groups->isInitialized(), "CmsUser::groups Collection has to be uninitialized for this test.");
0 ignored issues
show
Bug introduced by
The method isInitialized() does not seem to exist on object<Doctrine\Common\C...ctions\ArrayCollection>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
193
194
        $this->_em->flush();
195
196
        $this->assertFalse($freshUser->groups->isInitialized(), "CmsUser::groups Collection has to be uninitialized for this test.");
0 ignored issues
show
Bug introduced by
The method isInitialized() does not seem to exist on object<Doctrine\Common\C...ctions\ArrayCollection>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
197
        $this->assertEquals(3, count($freshUser->getGroups()));
198
        $this->assertEquals(3, count($freshUser->getGroups()->getSnapshot()), "Snapshot of CmsUser::groups should contain 3 entries.");
0 ignored issues
show
Bug introduced by
The method getSnapshot() does not seem to exist on object<Doctrine\Common\C...ctions\ArrayCollection>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
199
200
        $this->_em->clear();
201
202
        $freshUser = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $user->getId());
203
        $this->assertEquals(3, count($freshUser->getGroups()));
204
    }
205
206
    /**
207
     * @group DDC-130
208
     */
209
    public function testRemoveUserWithManyGroups()
210
    {
211
        $user = $this->addCmsUserGblancoWithGroups(2);
212
        $userId = $user->getId();
213
214
        $this->_em->remove($user);
215
        $this->_em->flush();
216
217
        $newUser = $this->_em->find(get_class($user), $userId);
218
        $this->assertNull($newUser);
219
    }
220
221
    /**
222
     * @group DDC-130
223
     */
224
    public function testRemoveGroupWithUser()
225
    {
226
        $user = $this->addCmsUserGblancoWithGroups(2);
227
228
        foreach ($user->getGroups() AS $group) {
0 ignored issues
show
Bug introduced by
The expression $user->getGroups() of type object<Doctrine\Common\C...yCollection>|array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
229
            $this->_em->remove($group);
230
        }
231
        $this->_em->flush();
232
        $this->_em->clear();
233
234
        $newUser = $this->_em->find(get_class($user), $user->getId());
235
        $this->assertEquals(0, count($newUser->getGroups()));
236
    }
237
238
    public function testDereferenceCollectionDelete()
239
    {
240
        $user = $this->addCmsUserGblancoWithGroups(2);
241
        $user->groups = null;
242
243
        $this->_em->flush();
244
        $this->_em->clear();
245
246
        $newUser = $this->_em->find(get_class($user), $user->getId());
247
        $this->assertEquals(0, count($newUser->getGroups()));
248
    }
249
250
    /**
251
     * @group DDC-839
252
     */
253
    public function testWorkWithDqlHydratedEmptyCollection()
254
    {
255
        $user = $this->addCmsUserGblancoWithGroups(0);
256
        $group = new CmsGroup();
257
        $group->name = "Developers0";
258
        $this->_em->persist($group);
259
260
        $this->_em->flush();
261
        $this->_em->clear();
262
263
        $newUser = $this->_em->createQuery('SELECT u, g FROM Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN u.groups g WHERE u.id = ?1')
264
                             ->setParameter(1, $user->getId())
265
                             ->getSingleResult();
266
        $this->assertEquals(0, count($newUser->groups));
267
        $this->assertInternalType('array', $newUser->groups->getMapping());
268
269
        $newUser->addGroup($group);
270
271
        $this->_em->flush();
272
        $this->_em->clear();
273
274
        $newUser = $this->_em->find(get_class($user), $user->getId());
275
        $this->assertEquals(1, count($newUser->groups));
276
    }
277
278
    /**
279
     * @param  int $groupCount
280
     * @return CmsUser
281
     */
282
    public function addCmsUserGblancoWithGroups($groupCount = 1)
283
    {
284
        $user = new CmsUser;
285
        $user->name = 'Guilherme';
286
        $user->username = 'gblanco';
287
        $user->status = 'developer';
288
289
        for ($i=0; $i < $groupCount; ++$i) {
290
            $group = new CmsGroup;
291
            $group->name = 'Developers_' . $i;
292
            $user->addGroup($group);
293
        }
294
295
        $this->_em->persist($user);
296
        $this->_em->flush();
297
298
        $this->assertNotNull($user->getId(), "User 'gblanco' should have an ID assigned after the persist()/flush() operation.");
299
300
        return $user;
301
    }
302
303
    /**
304
     * @group DDC-980
305
     */
306
    public function testUpdateDeleteSizeSubselectQueries()
307
    {
308
        $this->_em->createQuery("DELETE Doctrine\Tests\Models\CMS\CmsUser u WHERE SIZE(u.groups) = 10")->execute();
309
        $this->_em->createQuery("UPDATE Doctrine\Tests\Models\CMS\CmsUser u SET u.status = 'inactive' WHERE SIZE(u.groups) = 10")->execute();
310
    }
311
312
    /**
313
     * @group DDC-978
314
     */
315
    public function testClearAndResetCollection()
316
    {
317
        $user = $this->addCmsUserGblancoWithGroups(2);
318
        $group1 = new CmsGroup;
319
        $group1->name = 'Developers_New1';
320
        $group2 = new CmsGroup;
321
        $group2->name = 'Developers_New2';
322
323
        $this->_em->persist($group1);
324
        $this->_em->persist($group2);
325
        $this->_em->flush();
326
        $this->_em->clear();
327
328
        $user = $this->_em->find(get_class($user), $user->id);
329
330
        $coll = new ArrayCollection(array($group1, $group2));
331
        $user->groups = $coll;
332
        $this->_em->flush();
333
        $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $user->groups,
334
            "UnitOfWork should have replaced ArrayCollection with PersistentCollection.");
335
        $this->_em->flush();
336
337
        $this->_em->clear();
338
339
        $user = $this->_em->find(get_class($user), $user->id);
340
        $this->assertEquals(2, count($user->groups));
341
        $this->assertEquals('Developers_New1', $user->groups[0]->name);
342
        $this->assertEquals('Developers_New2', $user->groups[1]->name);
343
    }
344
345
    /**
346
     * @group DDC-733
347
     */
348
    public function testInitializePersistentCollection()
349
    {
350
        $user = $this->addCmsUserGblancoWithGroups(2);
351
        $this->_em->clear();
352
353
        $user = $this->_em->find(get_class($user), $user->id);
354
355
        $this->assertFalse($user->groups->isInitialized(), "Pre-condition: lazy collection");
356
        $this->_em->getUnitOfWork()->initializeObject($user->groups);
357
        $this->assertTrue($user->groups->isInitialized(), "Collection should be initialized after calling UnitOfWork::initializeObject()");
358
    }
359
360
    /**
361
     * @group DDC-1189
362
     * @group DDC-956
363
     */
364
    public function testClearBeforeLazyLoad()
365
    {
366
        $user = $this->addCmsUserGblancoWithGroups(4);
367
368
        $this->_em->clear();
369
370
        $user = $this->_em->find(get_class($user), $user->id);
371
        $user->groups->clear();
372
        $this->assertEquals(0, count($user->groups));
373
374
        $this->_em->flush();
375
376
        $user = $this->_em->find(get_class($user), $user->id);
377
        $this->assertEquals(0, count($user->groups));
378
    }
379
380
    /**
381
     * @group DDC-3952
382
     */
383
    public function testManyToManyOrderByIsNotIgnored()
384
    {
385
        $user = $this->addCmsUserGblancoWithGroups(1);
386
387
        $group = new CmsGroup;
388
        $group->name = 'C';
389
        $user->addGroup($group);
390
391
        $group = new CmsGroup;
392
        $group->name = 'A';
393
        $user->addGroup($group);
394
395
        $group = new CmsGroup;
396
        $group->name = 'B';
397
        $user->addGroup($group);
398
399
        $this->_em->persist($user);
400
        $this->_em->flush();
401
402
        $this->_em->clear();
403
404
        $user = $this->_em->find(get_class($user), $user->id);
405
406
        $criteria = Criteria::create()
407
            ->orderBy(['name' => Criteria::ASC]);
408
        $groups   = $user->getGroups()->matching($criteria);
409
410
        $existingOrder = [];
411
        foreach ($groups as $group) {
412
            $existingOrder[] = $group->getName();
413
        }
414
415
        $this->assertEquals(['A', 'B', 'C', 'Developers_0'], $existingOrder);
416
    }
417
418
    public function testMatchingWithLimit()
419
    {
420
        $user = $this->addCmsUserGblancoWithGroups(2);
421
        $this->_em->clear();
422
423
        $user = $this->_em->find(get_class($user), $user->id);
424
425
        $groups = $user->groups;
426
        $this->assertFalse($user->groups->isInitialized(), "Pre-condition: lazy collection");
427
428
        $criteria = Criteria::create()->setMaxResults(1);
429
        $result   = $groups->matching($criteria);
430
431
        $this->assertCount(1, $result);
432
433
        $this->assertFalse($user->groups->isInitialized(), "Post-condition: matching does not initialize collection");
434
    }
435
436
    public function testMatchingWithOffset()
437
    {
438
        $user = $this->addCmsUserGblancoWithGroups(2);
439
        $this->_em->clear();
440
441
        $user = $this->_em->find(get_class($user), $user->id);
442
443
        $groups = $user->groups;
444
        $this->assertFalse($user->groups->isInitialized(), "Pre-condition: lazy collection");
445
446
        $criteria = Criteria::create()->setFirstResult(1);
447
        $result   = $groups->matching($criteria);
448
449
        $this->assertCount(1, $result);
450
451
        $firstGroup = $result->first();
452
        $this->assertEquals('Developers_1', $firstGroup->name);
453
454
        $this->assertFalse($user->groups->isInitialized(), "Post-condition: matching does not initialize collection");
455
    }
456
457
    public function testMatchingWithLimitAndOffset()
458
    {
459
        $user = $this->addCmsUserGblancoWithGroups(5);
460
        $this->_em->clear();
461
462
        $user = $this->_em->find(get_class($user), $user->id);
463
464
        $groups = $user->groups;
465
        $this->assertFalse($user->groups->isInitialized(), "Pre-condition: lazy collection");
466
467
        $criteria = Criteria::create()->setFirstResult(1)->setMaxResults(3);
468
        $result   = $groups->matching($criteria);
469
470
        $this->assertCount(3, $result);
471
472
        $firstGroup = $result->first();
473
        $this->assertEquals('Developers_1', $firstGroup->name);
474
475
        $lastGroup = $result->last();
476
        $this->assertEquals('Developers_3', $lastGroup->name);
477
478
        $this->assertFalse($user->groups->isInitialized(), "Post-condition: matching does not initialize collection");
479
    }
480
481
    public function testMatching()
482
    {
483
        $user = $this->addCmsUserGblancoWithGroups(2);
484
        $this->_em->clear();
485
486
        $user = $this->_em->find(get_class($user), $user->id);
487
488
        $groups = $user->groups;
489
        $this->assertFalse($user->groups->isInitialized(), "Pre-condition: lazy collection");
490
491
        $criteria = Criteria::create()->where(Criteria::expr()->eq('name', (string) 'Developers_0'));
492
        $result   = $groups->matching($criteria);
493
494
        $this->assertCount(1, $result);
495
496
        $firstGroup = $result->first();
497
        $this->assertEquals('Developers_0', $firstGroup->name);
498
499
        $this->assertFalse($user->groups->isInitialized(), "Post-condition: matching does not initialize collection");
500
    }
501
}
502