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