Completed
Pull Request — master (#5662)
by Jeremy
13:02 queued 04:26
created

testManyToManyOrderByHonorsFieldNameColumnNameAliases()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 40
Code Lines 28

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 40
rs 8.8571
cc 1
eloc 28
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\CmsTag;
7
use Doctrine\Tests\Models\CMS\CmsUser,
8
    Doctrine\Tests\Models\CMS\CmsGroup,
9
    Doctrine\Common\Collections\ArrayCollection;
10
11
/**
12
 * Basic many-to-many association tests.
13
 * ("Working with associations")
14
 *
15
 * @author robo
16
 */
17
class ManyToManyBasicAssociationTest extends \Doctrine\Tests\OrmFunctionalTestCase
18
{
19
    protected function setUp()
20
    {
21
        $this->useModelSet('cms');
22
        parent::setUp();
23
    }
24
25
    public function testUnsetManyToMany()
26
    {
27
        $user = $this->addCmsUserGblancoWithGroups(1);
28
29
        unset($user->groups[0]->users[0]); // inverse side
30
        unset($user->groups[0]); // owning side!
31
32
        $this->_em->flush();
33
34
        // Check that the link in the association table has been deleted
35
        $this->assertGblancoGroupCountIs(0);
36
    }
37
38
    public function testBasicManyToManyJoin()
39
    {
40
        $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...
41
        $this->_em->clear();
42
43
        $this->assertEquals(0, $this->_em->getUnitOfWork()->size());
44
45
        $query = $this->_em->createQuery("select u, g from Doctrine\Tests\Models\CMS\CmsUser u join u.groups g");
46
47
        $result = $query->getResult();
48
49
        $this->assertEquals(2, $this->_em->getUnitOfWork()->size());
50
        $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0]);
51
        $this->assertEquals('Guilherme', $result[0]->name);
52
        $this->assertEquals(1, $result[0]->getGroups()->count());
53
        $groups = $result[0]->getGroups();
54
        $this->assertEquals('Developers_0', $groups[0]->getName());
55
56
        $this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_MANAGED, $this->_em->getUnitOfWork()->getEntityState($result[0]));
57
        $this->assertEquals(\Doctrine\ORM\UnitOfWork::STATE_MANAGED, $this->_em->getUnitOfWork()->getEntityState($groups[0]));
58
59
        $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $groups);
60
        $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $groups[0]->getUsers());
61
62
        $groups[0]->getUsers()->clear();
63
        $groups->clear();
64
65
        $this->_em->flush();
66
        $this->_em->clear();
67
68
        $query = $this->_em->createQuery("select u, g from Doctrine\Tests\Models\CMS\CmsUser u join u.groups g");
69
        $this->assertEquals(0, count($query->getResult()));
70
    }
71
72
    public function testManyToManyAddRemove()
73
    {
74
        $user = $this->addCmsUserGblancoWithGroups(2);
75
        $this->_em->clear();
76
77
        $uRep = $this->_em->getRepository(get_class($user));
78
79
        // Get user
80
        $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...
81
82
        $this->assertNotNull($user, "Has to return exactly one entry.");
83
84
        $this->assertFalse($user->getGroups()->isInitialized());
85
86
        // Check groups
87
        $this->assertEquals(2, $user->getGroups()->count());
88
89
        $this->assertTrue($user->getGroups()->isInitialized());
90
91
        // Remove first group
92
        unset($user->groups[0]);
93
        //$user->getGroups()->remove(0);
94
95
        $this->_em->flush();
96
        $this->_em->clear();
97
98
        // Reload same user
99
        $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...
100
101
        // Check groups
102
        $this->assertEquals(1, $user2->getGroups()->count());
103
    }
104
105
    public function testManyToManyInverseSideIgnored()
106
    {
107
        $user = $this->addCmsUserGblancoWithGroups(0);
108
109
        $group = new CmsGroup;
110
        $group->name = 'Humans';
111
112
        // modify directly, addUser() would also (properly) set the owning side
113
        $group->users[] = $user;
114
115
        $this->_em->persist($user);
116
        $this->_em->persist($group);
117
        $this->_em->flush();
118
        $this->_em->clear();
119
120
        // Association should not exist
121
        $user2 = $this->_em->find(get_class($user), $user->getId());
122
123
        $this->assertNotNull($user2, "Has to return exactly one entry.");
124
        $this->assertEquals(0, $user2->getGroups()->count());
125
    }
126
127
    public function testManyToManyCollectionClearing()
128
    {
129
        $user = $this->addCmsUserGblancoWithGroups($groupCount = 10);
130
131
        // Check that there are indeed 10 links in the association table
132
        $this->assertGblancoGroupCountIs($groupCount);
133
134
        $user->groups->clear();
135
136
        $this->_em->flush();
137
138
        // Check that the links in the association table have been deleted
139
        $this->assertGblancoGroupCountIs(0);
140
    }
141
142
    public function testManyToManyCollectionClearAndAdd()
143
    {
144
        $user = $this->addCmsUserGblancoWithGroups($groupCount = 10);
145
146
        $groups = $user->groups->toArray();
147
        $user->groups->clear();
148
149
        foreach ($groups AS $group) {
150
            $user->groups[] = $group;
151
        }
152
153
        $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $user->groups);
154
        $this->assertTrue($user->groups->isDirty());
155
156
        $this->assertEquals($groupCount, count($user->groups), "There should be 10 groups in the collection.");
157
158
        $this->_em->flush();
159
160
        $this->assertGblancoGroupCountIs($groupCount);
161
    }
162
163
    /**
164
     * @param int $expectedGroupCount
165
     */
166
    public function assertGblancoGroupCountIs($expectedGroupCount)
167
    {
168
        $countDql = "SELECT count(g.id) FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN u.groups g WHERE u.username = 'gblanco'";
169
        $this->assertEquals(
170
            $expectedGroupCount,
171
            $this->_em->createQuery($countDql)->getSingleScalarResult(),
172
            "Failed to verify that CmsUser with username 'gblanco' has a group count of 10 with a DQL count query."
173
        );
174
    }
175
176
    public function testRetrieveManyToManyAndAddMore()
177
    {
178
        $user = $this->addCmsUserGblancoWithGroups(2);
179
180
        $group = new CmsGroup();
181
        $group->name = 'Developers_Fresh';
182
        $this->_em->persist($group);
183
        $this->_em->flush();
184
185
        $this->_em->clear();
186
187
        /* @var $freshUser CmsUser */
188
        $freshUser = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $user->getId());
189
        $newGroup = new CmsGroup();
190
        $newGroup->setName('12Monkeys');
191
        $freshUser->addGroup($newGroup);
192
193
        $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...
194
195
        $this->_em->flush();
196
197
        $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...
198
        $this->assertEquals(3, count($freshUser->getGroups()));
199
        $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...
200
201
        $this->_em->clear();
202
203
        $freshUser = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $user->getId());
204
        $this->assertEquals(3, count($freshUser->getGroups()));
205
    }
206
207
    /**
208
     * @group DDC-130
209
     */
210
    public function testRemoveUserWithManyGroups()
211
    {
212
        $user = $this->addCmsUserGblancoWithGroups(2);
213
        $userId = $user->getId();
214
215
        $this->_em->remove($user);
216
        $this->_em->flush();
217
218
        $newUser = $this->_em->find(get_class($user), $userId);
219
        $this->assertNull($newUser);
220
    }
221
222
    /**
223
     * @group DDC-130
224
     */
225
    public function testRemoveGroupWithUser()
226
    {
227
        $user = $this->addCmsUserGblancoWithGroups(2);
228
229
        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...
230
            $this->_em->remove($group);
231
        }
232
        $this->_em->flush();
233
        $this->_em->clear();
234
235
        $newUser = $this->_em->find(get_class($user), $user->getId());
236
        $this->assertEquals(0, count($newUser->getGroups()));
237
    }
238
239
    public function testDereferenceCollectionDelete()
240
    {
241
        $user = $this->addCmsUserGblancoWithGroups(2);
242
        $user->groups = null;
243
244
        $this->_em->flush();
245
        $this->_em->clear();
246
247
        $newUser = $this->_em->find(get_class($user), $user->getId());
248
        $this->assertEquals(0, count($newUser->getGroups()));
249
    }
250
251
    /**
252
     * @group DDC-839
253
     */
254
    public function testWorkWithDqlHydratedEmptyCollection()
255
    {
256
        $user = $this->addCmsUserGblancoWithGroups(0);
257
        $group = new CmsGroup();
258
        $group->name = "Developers0";
259
        $this->_em->persist($group);
260
261
        $this->_em->flush();
262
        $this->_em->clear();
263
264
        $newUser = $this->_em->createQuery('SELECT u, g FROM Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN u.groups g WHERE u.id = ?1')
265
                             ->setParameter(1, $user->getId())
266
                             ->getSingleResult();
267
        $this->assertEquals(0, count($newUser->groups));
268
        $this->assertInternalType('array', $newUser->groups->getMapping());
269
270
        $newUser->addGroup($group);
271
272
        $this->_em->flush();
273
        $this->_em->clear();
274
275
        $newUser = $this->_em->find(get_class($user), $user->getId());
276
        $this->assertEquals(1, count($newUser->groups));
277
    }
278
279
    /**
280
     * @param  int $groupCount
281
     * @return CmsUser
282
     */
283
    public function addCmsUserGblancoWithGroups($groupCount = 1)
284
    {
285
        $user = new CmsUser;
286
        $user->name = 'Guilherme';
287
        $user->username = 'gblanco';
288
        $user->status = 'developer';
289
290
        for ($i=0; $i < $groupCount; ++$i) {
291
            $group = new CmsGroup;
292
            $group->name = 'Developers_' . $i;
293
            $user->addGroup($group);
294
        }
295
296
        $this->_em->persist($user);
297
        $this->_em->flush();
298
299
        $this->assertNotNull($user->getId(), "User 'gblanco' should have an ID assigned after the persist()/flush() operation.");
300
301
        return $user;
302
    }
303
304
    /**
305
     * @group DDC-980
306
     */
307
    public function testUpdateDeleteSizeSubselectQueries()
308
    {
309
        $this->_em->createQuery("DELETE Doctrine\Tests\Models\CMS\CmsUser u WHERE SIZE(u.groups) = 10")->execute();
310
        $this->_em->createQuery("UPDATE Doctrine\Tests\Models\CMS\CmsUser u SET u.status = 'inactive' WHERE SIZE(u.groups) = 10")->execute();
311
    }
312
313
    /**
314
     * @group DDC-978
315
     */
316
    public function testClearAndResetCollection()
317
    {
318
        $user = $this->addCmsUserGblancoWithGroups(2);
319
        $group1 = new CmsGroup;
320
        $group1->name = 'Developers_New1';
321
        $group2 = new CmsGroup;
322
        $group2->name = 'Developers_New2';
323
324
        $this->_em->persist($group1);
325
        $this->_em->persist($group2);
326
        $this->_em->flush();
327
        $this->_em->clear();
328
329
        $user = $this->_em->find(get_class($user), $user->id);
330
331
        $coll = new ArrayCollection(array($group1, $group2));
332
        $user->groups = $coll;
333
        $this->_em->flush();
334
        $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $user->groups,
335
            "UnitOfWork should have replaced ArrayCollection with PersistentCollection.");
336
        $this->_em->flush();
337
338
        $this->_em->clear();
339
340
        $user = $this->_em->find(get_class($user), $user->id);
341
        $this->assertEquals(2, count($user->groups));
342
        $this->assertEquals('Developers_New1', $user->groups[0]->name);
343
        $this->assertEquals('Developers_New2', $user->groups[1]->name);
344
    }
345
346
    /**
347
     * @group DDC-733
348
     */
349
    public function testInitializePersistentCollection()
350
    {
351
        $user = $this->addCmsUserGblancoWithGroups(2);
352
        $this->_em->clear();
353
354
        $user = $this->_em->find(get_class($user), $user->id);
355
356
        $this->assertFalse($user->groups->isInitialized(), "Pre-condition: lazy collection");
357
        $this->_em->getUnitOfWork()->initializeObject($user->groups);
358
        $this->assertTrue($user->groups->isInitialized(), "Collection should be initialized after calling UnitOfWork::initializeObject()");
359
    }
360
361
    /**
362
     * @group DDC-1189
363
     * @group DDC-956
364
     */
365
    public function testClearBeforeLazyLoad()
366
    {
367
        $user = $this->addCmsUserGblancoWithGroups(4);
368
369
        $this->_em->clear();
370
371
        $user = $this->_em->find(get_class($user), $user->id);
372
        $user->groups->clear();
373
        $this->assertEquals(0, count($user->groups));
374
375
        $this->_em->flush();
376
377
        $user = $this->_em->find(get_class($user), $user->id);
378
        $this->assertEquals(0, count($user->groups));
379
    }
380
381
    /**
382
     * @group DDC-3952
383
     */
384
    public function testManyToManyOrderByIsNotIgnored()
385
    {
386
        $user = $this->addCmsUserGblancoWithGroups(1);
387
388
        $group1 = new CmsGroup;
389
        $group2 = new CmsGroup;
390
        $group3 = new CmsGroup;
391
392
        $group1->name = 'C';
393
        $group2->name = 'A';
394
        $group3->name = 'B';
395
396
        $user->addGroup($group1);
397
        $user->addGroup($group2);
398
        $user->addGroup($group3);
399
400
        $this->_em->persist($user);
401
        $this->_em->flush();
402
403
        $this->_em->clear();
404
405
        $user = $this->_em->find(get_class($user), $user->id);
406
407
        $criteria = Criteria::create()
408
            ->orderBy(['name' => Criteria::ASC]);
409
410
        $this->assertEquals(
411
            ['A', 'B', 'C', 'Developers_0'],
412
            $user
413
                ->getGroups()
414
                ->matching($criteria)
415
                ->map(function (CmsGroup $group) {
416
                    return $group->getName();
417
                })
418
                ->toArray()
419
        );
420
    }
421
422
    /**
423
     * @group DDC-3952
424
     */
425
    public function testManyToManyOrderByHonorsFieldNameColumnNameAliases()
426
    {
427
        $user = new CmsUser;
428
        $user->name = 'Guilherme';
429
        $user->username = 'gblanco';
430
        $user->status = 'developer';
431
432
        $tag1 = new CmsTag;
433
        $tag2 = new CmsTag;
434
        $tag3 = new CmsTag;
435
436
        $tag1->name = 'C';
437
        $tag2->name = 'A';
438
        $tag3->name = 'B';
439
440
        $user->addTag($tag1);
441
        $user->addTag($tag2);
442
        $user->addTag($tag3);
443
444
        $this->_em->persist($user);
445
        $this->_em->flush();
446
447
        $this->_em->clear();
448
449
        $user = $this->_em->find(get_class($user), $user->id);
450
451
        $criteria = Criteria::create()
452
            ->orderBy(['name' => Criteria::ASC]);
453
454
        $this->assertEquals(
455
            ['A', 'B', 'C'],
456
            $user
457
                ->getTags()
458
                ->matching($criteria)
459
                ->map(function (CmsTag $tag) {
460
                    return $tag->getName();
461
                })
462
                ->toArray()
463
        );
464
    }
465
466
    public function testMatchingWithLimit()
467
    {
468
        $user = $this->addCmsUserGblancoWithGroups(2);
469
        $this->_em->clear();
470
471
        $user = $this->_em->find(get_class($user), $user->id);
472
473
        $groups = $user->groups;
474
        $this->assertFalse($user->groups->isInitialized(), "Pre-condition: lazy collection");
475
476
        $criteria = Criteria::create()->setMaxResults(1);
477
        $result   = $groups->matching($criteria);
478
479
        $this->assertCount(1, $result);
480
481
        $this->assertFalse($user->groups->isInitialized(), "Post-condition: matching does not initialize collection");
482
    }
483
484
    public function testMatchingWithOffset()
485
    {
486
        $user = $this->addCmsUserGblancoWithGroups(2);
487
        $this->_em->clear();
488
489
        $user = $this->_em->find(get_class($user), $user->id);
490
491
        $groups = $user->groups;
492
        $this->assertFalse($user->groups->isInitialized(), "Pre-condition: lazy collection");
493
494
        $criteria = Criteria::create()->setFirstResult(1);
495
        $result   = $groups->matching($criteria);
496
497
        $this->assertCount(1, $result);
498
499
        $firstGroup = $result->first();
500
        $this->assertEquals('Developers_1', $firstGroup->name);
501
502
        $this->assertFalse($user->groups->isInitialized(), "Post-condition: matching does not initialize collection");
503
    }
504
505
    public function testMatchingWithLimitAndOffset()
506
    {
507
        $user = $this->addCmsUserGblancoWithGroups(5);
508
        $this->_em->clear();
509
510
        $user = $this->_em->find(get_class($user), $user->id);
511
512
        $groups = $user->groups;
513
        $this->assertFalse($user->groups->isInitialized(), "Pre-condition: lazy collection");
514
515
        $criteria = Criteria::create()->setFirstResult(1)->setMaxResults(3);
516
        $result   = $groups->matching($criteria);
517
518
        $this->assertCount(3, $result);
519
520
        $firstGroup = $result->first();
521
        $this->assertEquals('Developers_1', $firstGroup->name);
522
523
        $lastGroup = $result->last();
524
        $this->assertEquals('Developers_3', $lastGroup->name);
525
526
        $this->assertFalse($user->groups->isInitialized(), "Post-condition: matching does not initialize collection");
527
    }
528
529
    public function testMatching()
530
    {
531
        $user = $this->addCmsUserGblancoWithGroups(2);
532
        $this->_em->clear();
533
534
        $user = $this->_em->find(get_class($user), $user->id);
535
536
        $groups = $user->groups;
537
        $this->assertFalse($user->groups->isInitialized(), "Pre-condition: lazy collection");
538
539
        $criteria = Criteria::create()->where(Criteria::expr()->eq('name', (string) 'Developers_0'));
540
        $result   = $groups->matching($criteria);
541
542
        $this->assertCount(1, $result);
543
544
        $firstGroup = $result->first();
545
        $this->assertEquals('Developers_0', $firstGroup->name);
546
547
        $this->assertFalse($user->groups->isInitialized(), "Post-condition: matching does not initialize collection");
548
    }
549
}
550