d417ca0004a3a493e5fc144a9bb3ca5903a73272
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Tests / Functional / Persistence / RelationTest.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Tests\Functional\Persistence;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use ExtbaseTeam\BlogExample\Domain\Model\Blog;
18 use ExtbaseTeam\BlogExample\Domain\Model\Post;
19 use ExtbaseTeam\BlogExample\Domain\Model\Tag;
20 use ExtbaseTeam\BlogExample\Domain\Repository\BlogRepository;
21 use ExtbaseTeam\BlogExample\Domain\Repository\PersonRepository;
22 use ExtbaseTeam\BlogExample\Domain\Repository\PostRepository;
23 use TYPO3\CMS\Core\Database\ConnectionPool;
24 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
25 use TYPO3\CMS\Core\Utility\GeneralUtility;
26 use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
27 use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
28 use TYPO3\CMS\Extbase\Persistence\QueryInterface;
29
30 class RelationTest extends \TYPO3\TestingFramework\Core\Functional\FunctionalTestCase
31 {
32 /**
33 * @var Blog
34 */
35 protected $blog;
36
37 /**
38 * @var \TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager
39 */
40 protected $persistentManager;
41
42 protected $testExtensionsToLoad = ['typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example'];
43
44 protected $coreExtensionsToLoad = ['extbase', 'fluid'];
45
46 /**
47 * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface The object manager
48 */
49 protected $objectManager;
50
51 /**
52 * Sets up this test suite.
53 */
54 protected function setUp()
55 {
56 parent::setUp();
57
58 $this->importDataSet('PACKAGE:typo3/testing-framework/Resources/Core/Functional/Fixtures/pages.xml');
59 $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/blogs.xml');
60 $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/posts.xml');
61 $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/persons.xml');
62 $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/tags.xml');
63 $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/tags-mm.xml');
64 $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/post-tag-mm.xml');
65 $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/categories.xml');
66 $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/category-mm.xml');
67
68 $this->objectManager = GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
69 $this->persistentManager = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager::class);
70 /* @var $blogRepository \TYPO3\CMS\Extbase\Persistence\Repository */
71 $blogRepository = $this->objectManager->get(BlogRepository::class);
72 $this->blog = $blogRepository->findByUid(1);
73 }
74
75 /**
76 * Tests adding object at the end of sorted 1:M relation (Blog:Posts)
77 *
78 * @test
79 */
80 public function attachPostToBlogAtTheEnd()
81 {
82 $queryBuilder = (new ConnectionPool())->getQueryBuilderForTable('tx_blogexample_domain_model_post');
83 $queryBuilder->getRestrictions()->removeAll();
84 $countPostsOriginal = $queryBuilder
85 ->count('*')
86 ->from('tx_blogexample_domain_model_post')
87 ->where(
88 $queryBuilder->expr()->eq(
89 'blog',
90 $queryBuilder->createNamedParameter($this->blog->getUid(), \PDO::PARAM_INT)
91 )
92 )->execute()
93 ->fetchColumn(0);
94
95 $newPostTitle = 'sdufhisdhuf';
96 /** @var Post $newPost */
97 $newPost = $this->objectManager->get(Post::class);
98 $newPost->setBlog($this->blog);
99 $newPost->setTitle($newPostTitle);
100 $newPost->setContent('Bla Bla Bla');
101
102 $this->blog->addPost($newPost);
103 $this->updateAndPersistBlog();
104
105 $queryBuilder->resetQueryParts();
106 $countPosts = $queryBuilder
107 ->count('*')
108 ->from('tx_blogexample_domain_model_post')
109 ->where(
110 $queryBuilder->expr()->eq(
111 'blog',
112 $queryBuilder->createNamedParameter($this->blog->getUid(), \PDO::PARAM_INT)
113 )
114 )->execute()
115 ->fetchColumn(0);
116 $this->assertEquals($countPostsOriginal + 1, $countPosts);
117
118 $queryBuilder->resetQueryParts();
119 $post = $queryBuilder
120 ->select('title', 'sorting')
121 ->from('tx_blogexample_domain_model_post')
122 ->where(
123 $queryBuilder->expr()->eq(
124 'blog',
125 $queryBuilder->createNamedParameter($this->blog->getUid(), \PDO::PARAM_INT)
126 )
127 )->orderBy('sorting', 'DESC')
128 ->execute()
129 ->fetch();
130 $this->assertSame($newPostTitle, $post['title']);
131 $this->assertEquals($countPostsOriginal + 1, $post['sorting']);
132 }
133
134 /**
135 * Tests removing object from the end of sorted 1:M relation (Blog:Posts)
136 *
137 * @test
138 */
139 public function removeLastPostFromBlog()
140 {
141 $queryBuilder = (new ConnectionPool())->getQueryBuilderForTable('tx_blogexample_domain_model_post');
142 $queryBuilder->getRestrictions()
143 ->removeAll()->add(new DeletedRestriction());
144 $countPostsOriginal = $queryBuilder
145 ->count('*')
146 ->from('tx_blogexample_domain_model_post')
147 ->execute()
148 ->fetchColumn(0);
149
150 $queryBuilder->resetQueryParts();
151 $post = $queryBuilder
152 ->select('title', 'sorting')
153 ->from('tx_blogexample_domain_model_post')
154 ->where(
155 $queryBuilder->expr()->eq(
156 'blog',
157 $queryBuilder->createNamedParameter($this->blog->getUid(), \PDO::PARAM_INT)
158 )
159 )->orderBy('sorting', 'DESC')
160 ->execute()
161 ->fetch();
162 $this->assertEquals(10, $post['sorting']);
163
164 $posts = $this->blog->getPosts();
165 $postsArray = $posts->toArray();
166 $latestPost = array_pop($postsArray);
167
168 $this->assertEquals(10, $latestPost->getUid());
169
170 $this->blog->removePost($latestPost);
171 $this->updateAndPersistBlog();
172
173 $queryBuilder->resetQueryParts();
174 $countPosts = $queryBuilder
175 ->count('*')
176 ->from('tx_blogexample_domain_model_post')
177 ->execute()
178 ->fetchColumn(0);
179 $this->assertEquals($countPostsOriginal - 1, $countPosts);
180
181 $queryBuilder->resetQueryParts();
182 $post = $queryBuilder
183 ->select('title', 'sorting')
184 ->from('tx_blogexample_domain_model_post')
185 ->where(
186 $queryBuilder->expr()->eq(
187 'uid',
188 $queryBuilder->createNamedParameter($latestPost->getUid(), \PDO::PARAM_INT)
189 )
190 )->orderBy('sorting', 'DESC')
191 ->execute()
192 ->fetch();
193 $this->assertNull($post['uid']);
194
195 $queryBuilder->resetQueryParts();
196 $post = $queryBuilder
197 ->select('title', 'sorting')
198 ->from('tx_blogexample_domain_model_post')
199 ->where(
200 $queryBuilder->expr()->eq(
201 'blog',
202 $queryBuilder->createNamedParameter($this->blog->getUid(), \PDO::PARAM_INT)
203 )
204 )->orderBy('sorting', 'DESC')
205 ->execute()
206 ->fetch();
207 $this->assertSame('Post9', $post['title']);
208 $this->assertEquals(9, $post['sorting']);
209 }
210
211 /**
212 * Tests adding object in the middle of the sorted 1:M relation (Blog:Posts)
213 *
214 * @test
215 */
216 public function addPostToBlogInTheMiddle()
217 {
218 $queryBuilder = (new ConnectionPool())->getQueryBuilderForTable('tx_blogexample_domain_model_post');
219 $queryBuilder->getRestrictions()
220 ->removeAll()->add(new DeletedRestriction());
221 $countPostsOriginal = $queryBuilder
222 ->count('*')
223 ->from('tx_blogexample_domain_model_post')
224 ->execute()
225 ->fetchColumn(0);
226
227 /** @var Post $newPost */
228 $newPost = $this->objectManager->get(Post::class);
229
230 $posts = clone $this->blog->getPosts();
231 $this->blog->getPosts()->removeAll($posts);
232 $counter = 1;
233 $newPostTitle = 'INSERTED POST at position 6';
234 foreach ($posts as $post) {
235 $this->blog->addPost($post);
236 if ($counter === 5) {
237 $newPost->setBlog($this->blog);
238 $newPost->setTitle($newPostTitle);
239 $newPost->setContent('Bla Bla Bla');
240 $this->blog->addPost($newPost);
241 }
242 $counter++;
243 }
244 $this->updateAndPersistBlog();
245
246 $queryBuilder->resetQueryParts();
247 $countPosts = $queryBuilder
248 ->count('*')
249 ->from('tx_blogexample_domain_model_post')
250 ->execute()
251 ->fetchColumn(0);
252 $this->assertEquals($countPostsOriginal + 1, $countPosts);
253
254 //last post
255 $queryBuilder->resetQueryParts();
256 $post = $queryBuilder
257 ->select('title', 'sorting')
258 ->from('tx_blogexample_domain_model_post')
259 ->where(
260 $queryBuilder->expr()->eq(
261 'blog',
262 $queryBuilder->createNamedParameter($this->blog->getUid(), \PDO::PARAM_INT)
263 )
264 )->orderBy('sorting', 'DESC')
265 ->execute()
266 ->fetch();
267 $this->assertSame('Post10', $post['title']);
268 $this->assertEquals(11, $post['sorting']);
269
270 // check sorting of the post added in the middle
271 $queryBuilder->resetQueryParts();
272 $post = $queryBuilder
273 ->select('title', 'sorting')
274 ->from('tx_blogexample_domain_model_post')
275 ->where(
276 $queryBuilder->expr()->eq(
277 'uid',
278 $queryBuilder->createNamedParameter($newPost->getUid(), \PDO::PARAM_INT)
279 )
280 )->orderBy('sorting', 'DESC')
281 ->execute()
282 ->fetch();
283 $this->assertSame($newPostTitle, $post['title']);
284 $this->assertEquals(6, $post['sorting']);
285 }
286
287 /**
288 * Tests removing object from the middle of sorted 1:M relation (Blog:Posts)
289 *
290 * @test
291 */
292 public function removeMiddlePostFromBlog()
293 {
294 $queryBuilder = (new ConnectionPool())->getQueryBuilderForTable('tx_blogexample_domain_model_post');
295 $queryBuilder->getRestrictions()
296 ->removeAll()->add(new DeletedRestriction());
297 $countPostsOriginal = $queryBuilder
298 ->count('*')
299 ->from('tx_blogexample_domain_model_post')
300 ->execute()
301 ->fetchColumn(0);
302
303 $posts = clone $this->blog->getPosts();
304 $counter = 1;
305 foreach ($posts as $post) {
306 if ($counter === 5) {
307 $this->blog->removePost($post);
308 }
309 $counter++;
310 }
311 $this->updateAndPersistBlog();
312
313 $queryBuilder->resetQueryParts();
314 $countPosts = $queryBuilder
315 ->count('*')
316 ->from('tx_blogexample_domain_model_post')
317 ->execute()
318 ->fetchColumn(0);
319 $this->assertEquals($countPostsOriginal - 1, $countPosts);
320
321 $queryBuilder->resetQueryParts();
322 $post = $queryBuilder
323 ->select('title', 'sorting')
324 ->from('tx_blogexample_domain_model_post')
325 ->where(
326 $queryBuilder->expr()->eq(
327 'blog',
328 $queryBuilder->createNamedParameter($this->blog->getUid(), \PDO::PARAM_INT)
329 )
330 )->orderBy('sorting', 'DESC')
331 ->execute()
332 ->fetch();
333 $this->assertSame('Post10', $post['title']);
334 $this->assertEquals(10, $post['sorting']);
335 }
336
337 /**
338 * Tests moving object from the end to the middle of the sorted 1:M relation (Blog:Posts)
339 *
340 * @test
341 */
342 public function movePostFromEndToTheMiddle()
343 {
344 $queryBuilder = (new ConnectionPool())->getQueryBuilderForTable('tx_blogexample_domain_model_post');
345 $queryBuilder->getRestrictions()
346 ->removeAll()->add(new DeletedRestriction());
347 $countPostsOriginal = $queryBuilder
348 ->count('*')
349 ->from('tx_blogexample_domain_model_post')
350 ->execute()
351 ->fetchColumn(0);
352
353 $posts = clone $this->blog->getPosts();
354 $postsArray = $posts->toArray();
355 $latestPost = array_pop($postsArray);
356
357 $this->blog->getPosts()->removeAll($posts);
358 $counter = 0;
359 $postCount = $posts->count();
360 foreach ($posts as $post) {
361 if ($counter !== ($postCount - 1)) {
362 $this->blog->addPost($post);
363 }
364 if ($counter === 4) {
365 $latestPost->setTitle('MOVED POST ' . $latestPost->getTitle());
366 $this->blog->addPost($latestPost);
367 }
368 $counter++;
369 }
370 $this->updateAndPersistBlog();
371
372 $queryBuilder->resetQueryParts();
373 $countPosts = $queryBuilder
374 ->count('*')
375 ->from('tx_blogexample_domain_model_post')
376 ->execute()
377 ->fetchColumn(0);
378 $this->assertEquals($countPostsOriginal, $countPosts);
379
380 $queryBuilder->getRestrictions()->removeAll();
381 $post = $queryBuilder
382 ->select('title', 'sorting')
383 ->from('tx_blogexample_domain_model_post')
384 ->where(
385 $queryBuilder->expr()->eq(
386 'blog',
387 $queryBuilder->createNamedParameter($this->blog->getUid(), \PDO::PARAM_INT)
388 )
389 )->orderBy('sorting', 'DESC')
390 ->execute()
391 ->fetch();
392 $this->assertSame('Post9', $post['title']);
393 $this->assertEquals(10, $post['sorting']);
394
395 $queryBuilder->resetQueryParts();
396 $post = $queryBuilder
397 ->select('title', 'uid')
398 ->from('tx_blogexample_domain_model_post')
399 ->where(
400 $queryBuilder->expr()->andX(
401 $queryBuilder->expr()->eq(
402 'blog',
403 $queryBuilder->createNamedParameter($this->blog->getUid(), \PDO::PARAM_INT)
404 ),
405 $queryBuilder->expr()->eq('sorting', $queryBuilder->createNamedParameter(6, \PDO::PARAM_INT))
406 )
407 )
408 ->execute()
409 ->fetch();
410 $this->assertSame('MOVED POST Post10', $post['title']);
411 $this->assertEquals(10, $post['uid']);
412 }
413
414 /**
415 * Tests adding object at the end of sorted M:M relation (Post:Tag)
416 *
417 * @test
418 */
419 public function attachTagToPostAtTheEnd()
420 {
421 $queryBuilder = (new ConnectionPool())->getQueryBuilderForTable('tx_blogexample_domain_model_tag');
422 $queryBuilder->getRestrictions()
423 ->removeAll();
424 $countOriginal = $queryBuilder
425 ->count('*')
426 ->from('tx_blogexample_domain_model_tag')
427 ->execute()
428 ->fetchColumn(0);
429
430 $newTagTitle = 'sdufhisdhuf';
431
432 /** @var Tag $newTag */
433 $newTag = $this->objectManager->get('ExtbaseTeam\\BlogExample\\Domain\\Model\\Tag', $newTagTitle);
434
435 /** @var PostRepository $postRepository */
436 $postRepository = $this->objectManager->get(PostRepository::class);
437 $post = $postRepository->findByUid(1);
438 $post->addTag($newTag);
439
440 $postRepository->update($post);
441 $this->persistentManager->persistAll();
442
443 $queryBuilder->resetQueryParts();
444 $count = $queryBuilder
445 ->count('*')
446 ->from('tx_blogexample_domain_model_tag')
447 ->execute()
448 ->fetchColumn(0);
449 $this->assertEquals($countOriginal + 1, $count);
450
451 $queryBuilder = (new ConnectionPool())->getQueryBuilderForTable('tx_blogexample_post_tag_mm');
452 $queryBuilder->getRestrictions()
453 ->removeAll();
454 $tag = $queryBuilder
455 ->select('uid_foreign')
456 ->from('tx_blogexample_post_tag_mm')
457 ->where(
458 $queryBuilder->expr()->eq(
459 'uid_local',
460 $queryBuilder->createNamedParameter($post->getUid(), \PDO::PARAM_INT)
461 )
462 )->orderBy('sorting', 'DESC')
463 ->execute()
464 ->fetch();
465 $this->assertEquals($newTag->getUid(), $tag['uid_foreign']);
466 }
467
468 /**
469 * Tests removing object from the end of sorted M:M relation (Post:Tag)
470 *
471 * @test
472 */
473 public function removeLastTagFromPost()
474 {
475 $queryBuilder = (new ConnectionPool())->getQueryBuilderForTable('tx_blogexample_domain_model_tag');
476 $queryBuilder->getRestrictions()
477 ->removeAll()->add(new DeletedRestriction());
478 $countOriginal = $queryBuilder
479 ->count('*')
480 ->from('tx_blogexample_domain_model_tag')
481 ->execute()
482 ->fetchColumn(0);
483
484 /** @var PostRepository $postRepository */
485 $postRepository = $this->objectManager->get(PostRepository::class);
486 $post = $postRepository->findByUid(1);
487 $tags = $post->getTags();
488 $tagsArray = $tags->toArray();
489 $latestTag = array_pop($tagsArray);
490
491 $this->assertEquals(10, $latestTag->getUid());
492
493 $post->removeTag($latestTag);
494
495 $postRepository->update($post);
496 $this->persistentManager->persistAll();
497
498 $queryBuilder->resetQueryParts();
499 $countTags = $queryBuilder
500 ->count('*')
501 ->from('tx_blogexample_domain_model_tag')
502 ->execute()
503 ->fetchColumn(0);
504 $this->assertEquals($countOriginal, $countTags);
505
506 $queryBuilder = (new ConnectionPool())->getQueryBuilderForTable('tx_blogexample_post_tag_mm');
507 $queryBuilder->getRestrictions()
508 ->removeAll();
509 $tag = $queryBuilder
510 ->select('uid_foreign')
511 ->from('tx_blogexample_post_tag_mm')
512 ->where(
513 $queryBuilder->expr()->eq(
514 'uid_local',
515 $queryBuilder->createNamedParameter($post->getUid(), \PDO::PARAM_INT)
516 )
517 )->orderBy('sorting', 'DESC')
518 ->execute()
519 ->fetch();
520 $this->assertEquals(9, $tag['uid_foreign']);
521
522 $queryBuilder->resetQueryParts();
523 $tag = $queryBuilder
524 ->select('uid_foreign')
525 ->from('tx_blogexample_post_tag_mm')
526 ->where(
527 $queryBuilder->expr()->andX(
528 $queryBuilder->expr()->eq(
529 'uid_local',
530 $queryBuilder->createNamedParameter($post->getUid(), \PDO::PARAM_INT)
531 ),
532 $queryBuilder->expr()->eq(
533 'uid_foreign',
534 $queryBuilder->createNamedParameter($latestTag->getUid(), \PDO::PARAM_INT)
535 )
536 )
537 )->orderBy('sorting', 'DESC')
538 ->execute()
539 ->fetch();
540 $this->assertNull($tag['uid_foreign']);
541 }
542
543 /**
544 * Tests adding object in the middle of sorted M:M relation (Post:Tag)
545 *
546 * @test
547 */
548 public function addTagToPostInTheMiddle()
549 {
550 $queryBuilder = (new ConnectionPool())->getQueryBuilderForTable('tx_blogexample_post_tag_mm');
551 $queryBuilder->getRestrictions()
552 ->removeAll();
553 $countTagsOriginal = $queryBuilder
554 ->count('*')
555 ->from('tx_blogexample_post_tag_mm')
556 ->where(
557 $queryBuilder->expr()->eq('uid_local', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT))
558 )
559 ->execute()
560 ->fetchColumn(0);
561
562 /** @var PostRepository $postRepository */
563 $postRepository = $this->objectManager->get(PostRepository::class);
564 $post = $postRepository->findByUid(1);
565 $tags = clone $post->getTags();
566 $post->setTags(new ObjectStorage());
567
568 /** @var Tag $newTag */
569 $newTag = $this->objectManager->get(Tag::class, 'INSERTED TAG at position 6 : ' . strftime(''));
570
571 $counter = 1;
572 foreach ($tags as $tag) {
573 $post->addTag($tag);
574 if ($counter === 5) {
575 $post->addTag($newTag);
576 }
577 $counter++;
578 }
579
580 $postRepository->update($post);
581 $this->persistentManager->persistAll();
582
583 $queryBuilder->resetQueryParts();
584 $countTags = $queryBuilder
585 ->count('*')
586 ->from('tx_blogexample_post_tag_mm')
587 ->where(
588 $queryBuilder->expr()->eq('uid_local', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT))
589 )
590 ->execute()
591 ->fetchColumn(0);
592 $this->assertEquals($countTagsOriginal + 1, $countTags);
593
594 $queryBuilder->resetQueryParts();
595 $tag = $queryBuilder
596 ->select('uid_foreign')
597 ->from('tx_blogexample_post_tag_mm')
598 ->where(
599 $queryBuilder->expr()->eq(
600 'uid_local',
601 $queryBuilder->createNamedParameter($post->getUid(), \PDO::PARAM_INT)
602 )
603 )->orderBy('sorting', 'DESC')
604 ->execute()
605 ->fetch();
606 $this->assertEquals(10, $tag['uid_foreign']);
607
608 $queryBuilder->resetQueryParts();
609 $tag = $queryBuilder
610 ->select('uid_foreign')
611 ->from('tx_blogexample_post_tag_mm')
612 ->where(
613 $queryBuilder->expr()->andX(
614 $queryBuilder->expr()->eq(
615 'uid_local',
616 $queryBuilder->createNamedParameter($post->getUid(), \PDO::PARAM_INT)
617 ),
618 $queryBuilder->expr()->eq('sorting', $queryBuilder->createNamedParameter(6, \PDO::PARAM_INT))
619 )
620 )->orderBy('sorting', 'DESC')
621 ->execute()
622 ->fetch();
623 $this->assertEquals($newTag->getUid(), $tag['uid_foreign']);
624 }
625
626 /**
627 * Tests removing object from the middle of the sorted M:M relation (Post:Tag)
628 *
629 * @test
630 */
631 public function removeMiddleTagFromPost()
632 {
633 $queryBuilder = (new ConnectionPool())->getQueryBuilderForTable('tx_blogexample_post_tag_mm');
634 $queryBuilder->getRestrictions()
635 ->removeAll();
636 $countTags = $queryBuilder
637 ->count('*')
638 ->from('tx_blogexample_post_tag_mm')
639 ->where(
640 $queryBuilder->expr()->eq('uid_local', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT))
641 )
642 ->execute()
643 ->fetchColumn(0);
644 $this->assertEquals(10, $countTags);
645
646 /** @var PostRepository $postRepository */
647 $postRepository = $this->objectManager->get(PostRepository::class);
648 $post = $postRepository->findByUid(1);
649 $tags = clone $post->getTags();
650 $counter = 1;
651 foreach ($tags as $tag) {
652 if ($counter === 5) {
653 $post->removeTag($tag);
654 }
655 $counter++;
656 }
657
658 $postRepository->update($post);
659 $this->persistentManager->persistAll();
660
661 $queryBuilder->resetQueryParts();
662 $countTags = $queryBuilder
663 ->count('*')
664 ->from('tx_blogexample_post_tag_mm')
665 ->where(
666 $queryBuilder->expr()->eq('uid_local', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT))
667 )
668 ->execute()
669 ->fetchColumn(0);
670 $this->assertEquals(9, $countTags);
671
672 $queryBuilder->resetQueryParts();
673 $tag = $queryBuilder
674 ->select('uid_foreign', 'sorting')
675 ->from('tx_blogexample_post_tag_mm')
676 ->where(
677 $queryBuilder->expr()->eq(
678 'uid_local',
679 $queryBuilder->createNamedParameter($post->getUid(), \PDO::PARAM_INT)
680 )
681 )->orderBy('sorting', 'DESC')
682 ->execute()
683 ->fetch();
684 $this->assertEquals(10, $tag['uid_foreign']);
685 $this->assertEquals(10, $tag['sorting']);
686
687 $queryBuilder->resetQueryParts();
688 $tag = $queryBuilder
689 ->select('uid_foreign')
690 ->from('tx_blogexample_post_tag_mm')
691 ->where(
692 $queryBuilder->expr()->andX(
693 $queryBuilder->expr()->eq(
694 'uid_local',
695 $queryBuilder->createNamedParameter($post->getUid(), \PDO::PARAM_INT)
696 ),
697 $queryBuilder->expr()->eq('sorting', $queryBuilder->createNamedParameter(5, \PDO::PARAM_INT))
698 )
699 )
700 ->execute()
701 ->fetch();
702 $this->assertNull($tag['uid_foreign']);
703 }
704
705 /**
706 * Tests moving object from the end to the middle of sorted M:M relation (Post:Tag)
707 *
708 * @test
709 */
710 public function moveTagFromEndToTheMiddle()
711 {
712 $queryBuilder = (new ConnectionPool())->getQueryBuilderForTable('tx_blogexample_post_tag_mm');
713 $queryBuilder->getRestrictions()
714 ->removeAll();
715 $countTags = $queryBuilder
716 ->count('*')
717 ->from('tx_blogexample_post_tag_mm')
718 ->where(
719 $queryBuilder->expr()->eq('uid_local', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT))
720 )
721 ->execute()
722 ->fetchColumn(0);
723 $this->assertEquals(10, $countTags);
724
725 /** @var PostRepository $postRepository */
726 $postRepository = $this->objectManager->get(PostRepository::class);
727 $post = $postRepository->findByUid(1);
728 $tags = clone $post->getTags();
729 $tagsArray = $tags->toArray();
730 $latestTag = array_pop($tagsArray);
731 $post->removeTag($latestTag);
732 $post->setTags(new ObjectStorage());
733
734 $counter = 1;
735 $tagCount = $tags->count();
736 foreach ($tags as $tag) {
737 if ($counter !== $tagCount) {
738 $post->addTag($tag);
739 }
740 if ($counter === 5) {
741 $post->addTag($latestTag);
742 }
743 $counter++;
744 }
745 $post->addTag($latestTag);
746
747 $postRepository->update($post);
748 $this->persistentManager->persistAll();
749
750 $queryBuilder->resetQueryParts();
751 $countTags = $queryBuilder
752 ->count('*')
753 ->from('tx_blogexample_post_tag_mm')
754 ->where(
755 $queryBuilder->expr()->eq('uid_local', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT))
756 )
757 ->execute()
758 ->fetchColumn(0);
759 $this->assertEquals(10, $countTags);
760
761 $queryBuilder->resetQueryParts();
762 $tag = $queryBuilder
763 ->select('uid_foreign', 'sorting')
764 ->from('tx_blogexample_post_tag_mm')
765 ->where(
766 $queryBuilder->expr()->eq(
767 'uid_local',
768 $queryBuilder->createNamedParameter($post->getUid(), \PDO::PARAM_INT)
769 )
770 )->orderBy('sorting', 'DESC')
771 ->execute()
772 ->fetch();
773 $this->assertEquals(9, $tag['uid_foreign']);
774 $this->assertEquals(10, $tag['sorting']);
775
776 $sorting = '6';
777 $queryBuilder->resetQueryParts();
778 $tag = $queryBuilder
779 ->select('uid_foreign')
780 ->from('tx_blogexample_post_tag_mm')
781 ->where(
782 $queryBuilder->expr()->andX(
783 $queryBuilder->expr()->eq(
784 'uid_local',
785 $queryBuilder->createNamedParameter($post->getUid(), \PDO::PARAM_INT)
786 ),
787 $queryBuilder->expr()->eq(
788 'sorting',
789 $queryBuilder->createNamedParameter($sorting, \PDO::PARAM_STR)
790 )
791 )
792 )
793 ->execute()
794 ->fetch();
795 $this->assertEquals(10, $tag['uid_foreign']);
796 }
797
798 /**
799 * Test if timestamp field is updated when updating a record
800 *
801 * @test
802 */
803 public function timestampFieldIsUpdatedOnPostSave()
804 {
805 $queryBuilder = (new ConnectionPool())->getQueryBuilderForTable('tx_blogexample_domain_model_post');
806 $queryBuilder->getRestrictions()
807 ->removeAll();
808 $rawPost = $queryBuilder
809 ->select('*')
810 ->from('tx_blogexample_domain_model_post')
811 ->where(
812 $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT))
813 )
814 ->execute()
815 ->fetch();
816
817 /** @var PostRepository $postRepository */
818 $postRepository = $this->objectManager->get(PostRepository::class);
819 $post = $postRepository->findByUid(1);
820 $post->setTitle('newTitle');
821
822 $postRepository->update($post);
823 $this->persistentManager->persistAll();
824
825 $queryBuilder->resetQueryParts();
826 $rawPost2 = $queryBuilder
827 ->select('*')
828 ->from('tx_blogexample_domain_model_post')
829 ->where(
830 $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT))
831 )
832 ->execute()
833 ->fetch();
834 $this->assertTrue($rawPost2['tstamp'] > $rawPost['tstamp']);
835 }
836
837 /**
838 * Test query matching for mm relation without MM_match_fields defined
839 *
840 * @test
841 */
842 public function mmRelationWithoutMatchFieldIsResolved()
843 {
844 /** @var PostRepository $postRepository */
845 $postRepository = $this->objectManager->get(PostRepository::class);
846 $posts = $postRepository->findByTagAndBlog('Tag2', $this->blog);
847 $this->assertCount(1, $posts);
848 }
849
850 /**
851 * @test
852 */
853 public function mmRelationWithMatchFieldIsResolvedFromLocalSide()
854 {
855 $queryBuilder = (new ConnectionPool())->getQueryBuilderForTable('sys_category_record_mm');
856 $queryBuilder->getRestrictions()
857 ->removeAll();
858 $countCategories = $queryBuilder
859 ->count('*')
860 ->from('sys_category_record_mm')
861 ->where(
862 $queryBuilder->expr()->andX(
863 $queryBuilder->expr()->eq('uid_foreign', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT)),
864 $queryBuilder->expr()->eq(
865 'tablenames',
866 $queryBuilder->createNamedParameter('tx_blogexample_domain_model_post', \PDO::PARAM_STR)
867 ),
868 $queryBuilder->expr()->eq(
869 'fieldname',
870 $queryBuilder->createNamedParameter('categories', \PDO::PARAM_STR)
871 )
872 )
873 )
874 ->execute()
875 ->fetchColumn(0);
876 $this->assertEquals(3, $countCategories);
877
878 /** @var PostRepository $postRepository */
879 $postRepository = $this->objectManager->get(PostRepository::class);
880 $post = $postRepository->findByUid(1);
881 $this->assertSame(3, count($post->getCategories()));
882 }
883
884 /**
885 * Test query matching respects MM_match_fields
886 *
887 * @test
888 */
889 public function mmRelationWithMatchFieldIsResolvedFromForeignSide()
890 {
891 /** @var PostRepository $postRepository */
892 $postRepository = $this->objectManager->get(PostRepository::class);
893 $posts = $postRepository->findByCategory(1);
894 $this->assertSame(2, count($posts));
895
896 $posts = $postRepository->findByCategory(4);
897 $this->assertSame(0, count($posts));
898 }
899
900 /**
901 * @test
902 */
903 public function mmRelationWithMatchFieldIsCreatedFromLocalSide()
904 {
905 $queryBuilder = (new ConnectionPool())->getQueryBuilderForTable('sys_category_record_mm');
906 $queryBuilder->getRestrictions()
907 ->removeAll();
908 $countCategories = $queryBuilder
909 ->count('*')
910 ->from('sys_category_record_mm')
911 ->where(
912 $queryBuilder->expr()->andX(
913 $queryBuilder->expr()->eq('uid_foreign', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT)),
914 $queryBuilder->expr()->eq(
915 'tablenames',
916 $queryBuilder->createNamedParameter('tx_blogexample_domain_model_post', \PDO::PARAM_STR)
917 ),
918 $queryBuilder->expr()->eq(
919 'fieldname',
920 $queryBuilder->createNamedParameter('categories', \PDO::PARAM_STR)
921 )
922 )
923 )
924 ->execute()
925 ->fetchColumn(0);
926 $this->assertEquals(3, $countCategories);
927
928 /** @var PostRepository $postRepository */
929 $postRepository = $this->objectManager->get(PostRepository::class);
930 $post = $postRepository->findByUid(1);
931
932 /** @var \TYPO3\CMS\Extbase\Domain\Model\Category $newCategory */
933 $newCategory = $this->objectManager->get(\TYPO3\CMS\Extbase\Domain\Model\Category::class);
934 $newCategory->setTitle('New Category');
935
936 $post->addCategory($newCategory);
937
938 $postRepository->update($post);
939 $this->persistentManager->persistAll();
940
941 $queryBuilder->resetQueryParts();
942 $countCategories = $queryBuilder
943 ->count('*')
944 ->from('sys_category_record_mm')
945 ->where(
946 $queryBuilder->expr()->andX(
947 $queryBuilder->expr()->eq('uid_foreign', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT)),
948 $queryBuilder->expr()->eq(
949 'tablenames',
950 $queryBuilder->createNamedParameter('tx_blogexample_domain_model_post', \PDO::PARAM_STR)
951 ),
952 $queryBuilder->expr()->eq(
953 'fieldname',
954 $queryBuilder->createNamedParameter('categories', \PDO::PARAM_STR)
955 )
956 )
957 )
958 ->execute()
959 ->fetchColumn(0);
960 $this->assertEquals(4, $countCategories);
961 }
962
963 /**
964 * Test if adjusting existing mm relations do not relations with other objects
965 *
966 * @test
967 */
968 public function adjustingMmRelationWithTablesnameAndFieldnameFieldDoNotTouchOtherRelations()
969 {
970 /** @var PostRepository $postRepository */
971 $postRepository = $this->objectManager->get(PostRepository::class);
972 /** @var Post $post */
973 $post = $postRepository->findByUid(1);
974 // Move category down
975 foreach ($post->getCategories() as $category) {
976 $post->removeCategory($category);
977 $post->addCategory($category);
978 break;
979 }
980 $postRepository->update($post);
981 $this->persistentManager->persistAll();
982
983 // re-fetch Post and Blog
984 $queryBuilder = (new ConnectionPool())->getQueryBuilderForTable('sys_category_record_mm');
985 $queryBuilder->getRestrictions()
986 ->removeAll();
987 $newBlogCategoryCount = $queryBuilder
988 ->count('*')
989 ->from('sys_category_record_mm')
990 ->where(
991 $queryBuilder->expr()->andX(
992 $queryBuilder->expr()->eq(
993 'uid_foreign',
994 $queryBuilder->createNamedParameter($this->blog->getUid(), \PDO::PARAM_INT)
995 ),
996 $queryBuilder->expr()->eq(
997 'tablenames',
998 $queryBuilder->createNamedParameter('tx_blogexample_domain_model_post', \PDO::PARAM_STR)
999 ),
1000 $queryBuilder->expr()->eq(
1001 'fieldname',
1002 $queryBuilder->createNamedParameter('categories', \PDO::PARAM_STR)
1003 )
1004 )
1005 )
1006 ->execute()
1007 ->fetchColumn(0);
1008
1009 $this->assertEquals($this->blog->getCategories()->count(), $newBlogCategoryCount);
1010 }
1011
1012 /**
1013 * @return array
1014 */
1015 public function distinctDataProvider()
1016 {
1017 return [
1018 'order default' => [
1019 []
1020 ],
1021 'order default, offset 0' => [
1022 [
1023 'offset' => 0
1024 ]
1025 ],
1026 'order default, limit 100' => [
1027 [
1028 'limit' => 100
1029 ]
1030 ],
1031 'order default, offset 0, limit 100' => [
1032 [
1033 'offset' => 0,
1034 'limit' => 100
1035 ]
1036 ],
1037 'order false' => [
1038 [
1039 'order' => false
1040 ]
1041 ],
1042 'order false, offset 0' => [
1043 [
1044 'order' => false,
1045 'offset' => 0
1046 ]
1047 ],
1048 'order false, limit 100' => [
1049 [
1050 'order' => false, 'limit' => 100
1051 ]
1052 ],
1053 'order false, offset 0, limit 100' => [
1054 [
1055 'order' => false,
1056 'offset' => 0,
1057 'limit' => 100
1058 ]
1059 ],
1060 'order uid, offset 0' => [
1061 [
1062 'order' => ['uid' => QueryInterface::ORDER_ASCENDING],
1063 'offset' => 0
1064 ]
1065 ],
1066 'order uid, limit 100' => [
1067 [
1068 'order' => ['uid' => QueryInterface::ORDER_ASCENDING],
1069 'limit' => 100
1070 ]
1071 ],
1072 'order uid, offset 0, limit 100' => [
1073 [
1074 'order' => ['uid' => QueryInterface::ORDER_ASCENDING],
1075 'offset' => 0,
1076 'limit' => 100
1077 ]
1078 ],
1079 ];
1080 }
1081
1082 /**
1083 * @param QueryInterface $query
1084 * @param array $queryRequest
1085 */
1086 protected function applyQueryRequest(QueryInterface $query, array $queryRequest)
1087 {
1088 if (isset($queryRequest['order']) && !$queryRequest['order']) {
1089 $query->setOrderings([]);
1090 } elseif (!empty($queryRequest['order'])) {
1091 $query->setOrderings($queryRequest['order']);
1092 }
1093 if (isset($queryRequest['offset'])) {
1094 $query->setOffset($queryRequest['offset']);
1095 }
1096 if (isset($queryRequest['limit'])) {
1097 $query->setLimit($queryRequest['limit']);
1098 }
1099 }
1100
1101 /**
1102 * Addresses ColumnMap::RELATION_HAS_ONE relations.
1103 *
1104 * @param $queryRequest
1105 *
1106 * @test
1107 * @dataProvider distinctDataProvider
1108 */
1109 public function distinctPersonEntitiesAreFoundByPublisher(array $queryRequest)
1110 {
1111 $query = $this->provideFindPostsByPublisherQuery(1);
1112 $this->applyQueryRequest($query, $queryRequest);
1113 $posts = $query->execute();
1114 $postCount = $posts->count();
1115
1116 $postIds = $this->resolveEntityIds($posts->toArray());
1117
1118 $this->assertEquals($this->countDistinctIds($postIds), $postCount);
1119 $this->assertDistinctIds($postIds);
1120 }
1121
1122 /**
1123 * Addresses ColumnMap::RELATION_HAS_ONE relations.
1124 *
1125 * @param $queryRequest
1126 *
1127 * @test
1128 * @dataProvider distinctDataProvider
1129 */
1130 public function distinctPersonRecordsAreFoundByPublisher(array $queryRequest)
1131 {
1132 $query = $this->provideFindPostsByPublisherQuery(1);
1133 $this->applyQueryRequest($query, $queryRequest);
1134 $postRecords = $query->execute(true);
1135 $postIds = $this->resolveRecordIds($postRecords);
1136
1137 $this->assertDistinctIds($postIds);
1138 }
1139
1140 /**
1141 * @param int $publisherId
1142 * @return QueryInterface
1143 */
1144 protected function provideFindPostsByPublisherQuery(int $publisherId)
1145 {
1146 $postRepository = $this->objectManager->get(PostRepository::class);
1147 $query = $postRepository->createQuery();
1148 $query->matching(
1149 $query->logicalOr([
1150 $query->equals('author.uid', $publisherId),
1151 $query->equals('reviewer.uid', $publisherId)
1152 ])
1153 );
1154 return $query;
1155 }
1156
1157 /**
1158 * Addresses ColumnMap::RELATION_HAS_MANY relations.
1159 *
1160 * @param $queryRequest
1161 *
1162 * @test
1163 * @dataProvider distinctDataProvider
1164 */
1165 public function distinctBlogEntitiesAreFoundByPostsSince(array $queryRequest)
1166 {
1167 $query = $this->provideFindBlogsByPostsSinceQuery(
1168 new \DateTime('2017-08-01')
1169 );
1170 $this->applyQueryRequest($query, $queryRequest);
1171 $blogs = $query->execute();
1172 $blogCount = $blogs->count();
1173
1174 $blogIds = $this->resolveEntityIds($blogs->toArray());
1175
1176 $this->assertEquals($this->countDistinctIds($blogIds), $blogCount);
1177 $this->assertDistinctIds($blogIds);
1178 }
1179
1180 /**
1181 * Addresses ColumnMap::RELATION_HAS_MANY relations.
1182 *
1183 * @param $queryRequest
1184 *
1185 * @test
1186 * @dataProvider distinctDataProvider
1187 */
1188 public function distinctBlogRecordsAreFoundByPostsSince(array $queryRequest)
1189 {
1190 $query = $this->provideFindBlogsByPostsSinceQuery(
1191 new \DateTime('2017-08-01')
1192 );
1193 $this->applyQueryRequest($query, $queryRequest);
1194 $blogRecords = $query->execute(true);
1195 $blogIds = $this->resolveRecordIds($blogRecords);
1196
1197 $this->assertDistinctIds($blogIds);
1198 }
1199
1200 /**
1201 * @param \DateTime $date
1202 * @return QueryInterface
1203 */
1204 protected function provideFindBlogsByPostsSinceQuery(\DateTime $date)
1205 {
1206 $blogRepository = $this->objectManager->get(BlogRepository::class);
1207 $query = $blogRepository->createQuery();
1208 $query->matching(
1209 $query->greaterThanOrEqual('posts.date', $date)
1210 );
1211 return $query;
1212 }
1213
1214 /**
1215 * Addresses ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY relations.
1216 *
1217 * @param $queryRequest
1218 *
1219 * @test
1220 * @dataProvider distinctDataProvider
1221 */
1222 public function distinctPersonEntitiesAreFoundByTagNameAreFiltered(array $queryRequest)
1223 {
1224 $query = $this->provideFindPersonsByTagNameQuery('SharedTag');
1225 $this->applyQueryRequest($query, $queryRequest);
1226 $persons = $query->execute();
1227 $personCount = $persons->count();
1228
1229 $personIds = $this->resolveEntityIds($persons->toArray());
1230
1231 $this->assertEquals($this->countDistinctIds($personIds), $personCount);
1232 $this->assertDistinctIds($personIds);
1233 }
1234
1235 /**
1236 * Addresses ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY relations.
1237 *
1238 * @param $queryRequest
1239 *
1240 * @test
1241 * @dataProvider distinctDataProvider
1242 */
1243 public function distinctPersonRecordsAreFoundByTagNameAreFiltered(array $queryRequest)
1244 {
1245 $query = $this->provideFindPersonsByTagNameQuery('SharedTag');
1246 $this->applyQueryRequest($query, $queryRequest);
1247 $personRecords = $query->execute(true);
1248 $personIds = $this->resolveRecordIds($personRecords);
1249
1250 $this->assertDistinctIds($personIds);
1251 }
1252
1253 /**
1254 * @param string $tagName
1255 * @return QueryInterface
1256 */
1257 protected function provideFindPersonsByTagNameQuery(string $tagName)
1258 {
1259 $personRepository = $this->objectManager->get(PersonRepository::class);
1260 $query = $personRepository->createQuery();
1261 $query->matching(
1262 $query->logicalOr([
1263 $query->equals('tags.name', $tagName),
1264 $query->equals('tagsSpecial.name', $tagName)
1265 ])
1266 );
1267 return $query;
1268 }
1269
1270 /**
1271 * Addresses ColumnMap::RELATION_HAS_ONE, ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY relations.
1272 *
1273 * @param $queryRequest
1274 *
1275 * @test
1276 * @dataProvider distinctDataProvider
1277 */
1278 public function distinctPostEntitiesAreFoundByAuthorTagNameAreFiltered(array $queryRequest)
1279 {
1280 $query = $this->provideFindPostsByAuthorTagName('SharedTag');
1281 $this->applyQueryRequest($query, $queryRequest);
1282 $posts = $query->execute();
1283 $postCount = $posts->count();
1284
1285 $postsIds = $this->resolveEntityIds($posts->toArray());
1286
1287 $this->assertEquals($this->countDistinctIds($postsIds), $postCount);
1288 $this->assertDistinctIds($postsIds);
1289 }
1290
1291 /**
1292 * Addresses ColumnMap::RELATION_HAS_ONE, ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY relations.
1293 *
1294 * @param $queryRequest
1295 *
1296 * @test
1297 * @dataProvider distinctDataProvider
1298 */
1299 public function distinctPostRecordsAreFoundByAuthorTagNameAreFiltered(array $queryRequest)
1300 {
1301 $query = $this->provideFindPostsByAuthorTagName('SharedTag');
1302 $this->applyQueryRequest($query, $queryRequest);
1303 $postRecords = $query->execute(true);
1304 $postsIds = $this->resolveRecordIds($postRecords);
1305
1306 $this->assertDistinctIds($postsIds);
1307 }
1308
1309 /**
1310 * @param string $tagName
1311 * @return QueryInterface
1312 */
1313 protected function provideFindPostsByAuthorTagName(string $tagName)
1314 {
1315 $postRepository = $this->objectManager->get(PostRepository::class);
1316 $query = $postRepository->createQuery();
1317 $query->matching(
1318 $query->logicalOr([
1319 $query->equals('author.tags.name', $tagName),
1320 $query->equals('author.tagsSpecial.name', $tagName)
1321 ])
1322 );
1323 return $query;
1324 }
1325
1326 /**
1327 * Helper method for persisting blog
1328 */
1329 protected function updateAndPersistBlog()
1330 {
1331 /** @var BlogRepository $blogRepository */
1332 $blogRepository = $this->objectManager->get(BlogRepository::class);
1333 $blogRepository->update($this->blog);
1334 $this->persistentManager->persistAll();
1335 }
1336
1337 /**
1338 * @param AbstractEntity[] $entities
1339 * @return int[]
1340 */
1341 protected function resolveEntityIds(array $entities)
1342 {
1343 return array_map(
1344 function (AbstractEntity $entity) {
1345 return $entity->getUid();
1346 },
1347 $entities
1348 );
1349 }
1350
1351 /**
1352 * @param array $records
1353 * @return int[]
1354 */
1355 protected function resolveRecordIds(array $records)
1356 {
1357 return array_column($records, 'uid');
1358 }
1359
1360 /**
1361 * Counts amount of distinct IDS.
1362 *
1363 * @param array $ids
1364 * @return int
1365 */
1366 protected function countDistinctIds(array $ids)
1367 {
1368 return count(array_count_values($ids));
1369 }
1370
1371 /**
1372 * Asserts distinct IDs by comparing the sum of the occurrence of
1373 * a particular ID to the amount of existing distinct IDs.
1374 *
1375 * @param array $ids
1376 */
1377 protected function assertDistinctIds(array $ids)
1378 {
1379 $counts = array_count_values($ids);
1380 $this->assertEquals(count($counts), array_sum($counts));
1381 }
1382 }