3 namespace RedUNIT\Base;
5 use RedUNIT\Base as Base;
6 use RedBeanPHP\Facade as R;
7 use RedBeanPHP\AssociationManager as AssociationManager;
8 use RedBeanPHP\RedException\SQL as SQL;
9 use RedBeanPHP\RedException as RedException;
10 use RedBeanPHP\OODBBean as OODBBean;
15 * Tests self referential many-to-many relations, including the
18 * @file RedUNIT/Base/Cross.php
19 * @desc Tests associations within the same table (i.e. page_page2 alike)
20 * Cross tables, self referential many-to-many relations
21 * and aggregation techniques
22 * @author Gabor de Mooij and the RedBeanPHP Community
23 * @license New BSD/GPLv2
25 * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
26 * This source file is subject to the New BSD/GPLv2 License that is bundled
27 * with this source code in the file license.txt.
30 class Cross extends Base
34 * Test how well aggr handles fields with no
39 public function testAggrNullHandling()
42 $book = R::dispense( 'book' );
43 $page = R::dispense( 'page' );
44 $page->name = 'Page 3';
45 $book->xownPageList[] = $page;
47 $book = $book->fresh();
48 $texts = $book->aggr( 'ownPageList', 'text' );
50 asrt( count( $texts ), 0 );
51 asrt( is_array( $texts ), TRUE );
53 $book = R::dispense( 'book' );
54 $page1 = R::dispense( 'page' );
55 $page1->name = 'Page 1';
56 $text1 = R::dispense('text');
57 $text1->content = 'Text 1';
58 $page1->text = $text1;
59 $book->xownPageList[] = $page1;
60 $page2 = R::dispense( 'page' );
61 $page2->name = 'Page 2';
62 $text2 = R::dispense( 'text' );
63 $text2->content = 'Text 2';
64 $page2->text = $text2;
65 $book->xownPageList[] = $page2;
66 $page3 = R::dispense( 'page' );
67 $page3->name = 'Page 3';
68 $book->xownPageList[] = $page3;
70 $book = $book->fresh();
71 $texts = $book->aggr( 'ownPageList', 'text' );
73 asrt( count( $texts ), 2 );
77 * Test many different scenarios with self referential
78 * many-to-many relations.
82 public function testSelfReferentialCRUD()
85 $buddies = R::dispense( 'buddy', 4 );
86 $buddies[0]->name = 'A';
87 $buddies[1]->name = 'B';
88 $buddies[2]->name = 'C';
89 $buddies[3]->name = 'D';
90 $buddies[0]->sharedBuddyList = array( $buddies[1], $buddies[2] );
91 $buddies[3]->sharedBuddyList = array( $buddies[2] );
92 R::storeAll( $buddies );
93 $buddies[0] = $buddies[0]->fresh();
94 asrt( count( $buddies[0]->sharedBuddyList ), 2 );
96 //does this yield valid combinations - cross relations / self ref n-m
97 //need to symmetric....
99 $names = R::gatherLabels( $buddies[0]->sharedBuddyList );
101 $names = implode( ',', $names );
102 asrt( $names, 'B,C' );
104 unset( $buddies[0]->sharedBuddy );
105 R::storeAll( $buddies );
106 $buddies[0] = $buddies[0]->fresh();
107 asrt( count( $buddies[0]->sharedBuddyList ), 2 );
109 $buddies[3] = $buddies[3]->fresh();
110 asrt( count( $buddies[3]->sharedBuddyList ), 1 );
112 $names = R::gatherLabels( $buddies[3]->sharedBuddyList );
114 $names = implode( ',', $names );
117 $buddies[2] = $buddies[2]->fresh();
118 asrt( count( $buddies[2]->sharedBuddyList ), 2 );
120 $names = R::gatherLabels( $buddies[2]->sharedBuddyList );
122 $names = implode( ',', $names );
123 asrt( $names, 'A,D' );
125 $buddies[1] = $buddies[1]->fresh();
126 asrt( count( $buddies[1]->sharedBuddyList ), 1 );
128 $names = R::gatherLabels( $buddies[1]->sharedBuddyList );
130 $names = implode( ',', $names );
134 $buddies[1]->sharedBuddyList[] = R::dispense('buddy')->setAttr('name', 'E');
135 R::store( $buddies[1] );
137 $buddies[0] = $buddies[0]->fresh();
138 asrt( count( $buddies[0]->sharedBuddyList ), 2 );
140 $names = R::gatherLabels( $buddies[0]->sharedBuddyList );
142 $names = implode( ',', $names );
143 asrt( $names, 'B,C' );
145 $buddies[1] = $buddies[1]->fresh();
146 asrt( count( $buddies[1]->sharedBuddyList ), 2 );
148 $names = R::gatherLabels( $buddies[1]->sharedBuddyList );
150 $names = implode( ',', $names );
151 asrt( $names, 'A,E' );
153 $all = R::find( 'buddy' );
154 asrt( count( $all ), 5 );
156 foreach( $buddies[1]->sharedBuddy as $buddy ) {
157 if ( $buddy->name === 'E' ) {
162 asrt( isset( $buddyE ), TRUE );
163 asrt( $buddyE->name, 'E' );
166 foreach( $buddies[0]->sharedBuddy as $buddy ) {
167 if ( $buddy->name === 'C' ) {
172 R::store( $buddies[0] );
174 $buddies[0] = $buddies[0]->fresh();
175 asrt( count( $buddies[0]->sharedBuddyList ), 2 );
177 $names = R::gatherLabels( $buddies[0]->sharedBuddyList );
179 $names = implode( ',', $names );
180 asrt( $names, 'B,C2' );
182 $buddies[2] = $buddies[2]->fresh();
183 asrt( count( $buddies[2]->sharedBuddyList ), 2 );
185 $names = R::gatherLabels( $buddies[2]->sharedBuddyList );
187 $names = implode( ',', $names );
188 asrt( $names, 'A,D' );
191 foreach( $buddies[0]->sharedBuddyList as $id => $buddy ) {
192 if ( $buddy->name === 'B' ) {
193 unset( $buddies[0]->sharedBuddyList[$id] );
197 R::store( $buddies[0] );
199 $buddies[0] = $buddies[0]->fresh();
200 asrt( count( $buddies[0]->sharedBuddyList ), 1 );
202 $names = R::gatherLabels( $buddies[0]->sharedBuddyList );
204 $names = implode( ',', $names );
205 asrt( $names, 'C2' );
207 $buddies[1] = $buddies[1]->fresh();
208 asrt( count( $buddies[1]->sharedBuddyList ), 1 );
210 $names = R::gatherLabels( $buddies[1]->sharedBuddyList );
212 $names = implode( ',', $names );
215 asrt( R::count( 'buddy' ), 5 );
216 asrt( R::count( 'buddyBuddy' ), 3 );
218 $buddies[3] = $buddies[3]->fresh();
219 asrt( count( $buddies[3]->sharedBuddyList ), 1 );
221 $names = R::gatherLabels( $buddies[3]->sharedBuddyList );
223 $names = implode( ',', $names );
224 asrt( $names, 'C2' );
226 $buddies[2] = $buddies[2]->fresh();
227 asrt( count( $buddies[2]->sharedBuddyList ), 2 );
229 $names = R::gatherLabels( $buddies[2]->sharedBuddyList );
231 $names = implode( ',', $names );
232 asrt( $names, 'A,D' );
234 $buddyE = $buddyE->fresh();
235 asrt( count( $buddyE->sharedBuddyList ), 1 );
237 $names = R::gatherLabels( $buddyE->sharedBuddyList );
239 $names = implode( ',', $names );
242 //can we access linked_by -- o dear mysql again! cant have alias in WHERE!
243 if ( $this->currentlyActiveDriverID === 'mysql' ) {
244 $buddyE = $buddyE->fresh();
245 asrt( count( $buddyE->with(' HAVING linked_by > 0 ')->sharedBuddyList ), 1 );
246 $buddyE = $buddyE->fresh();
247 asrt( count( $buddyE->with(' HAVING linked_by < 0 ')->sharedBuddyList ), 0 );
250 //even postgres sucks. Only SQLite knows how to handle this.
251 //I dont give a fuck whether it's an SQL standard or not, it should just work.
252 if ( $this->currentlyActiveDriverID === 'sqlite' ) {
253 $buddyE = $buddyE->fresh();
254 asrt( count( $buddyE->withCondition(' linked_by > 0 ')->sharedBuddyList ), 1 );
255 $buddyE = $buddyE->fresh();
256 asrt( count( $buddyE->withCondition(' linked_by < 0 ')->sharedBuddyList ), 0 );
259 $buddyE = $buddyE->fresh();
260 asrt( count( $buddyE->withCondition(' buddy_buddy.buddy_id > 0 AND buddy_buddy.buddy2_id > 0 ')->sharedBuddyList ), 1 );
261 $buddyE = $buddyE->fresh();
262 asrt( count( $buddyE->withCondition(' buddy_buddy.buddy_id < 0 AND buddy_buddy.buddy2_id < 0 ')->sharedBuddyList ), 0 );
266 * Test self referential N-M relations (page_page).
270 public function testSelfReferential()
272 $page = R::dispense( 'page' )->setAttr( 'title', 'a' );
273 $page->sharedPage[] = R::dispense( 'page' )->setAttr( 'title', 'b' );
275 $page = $page->fresh();
276 $page = reset( $page->sharedPage );
277 asrt( $page->title, 'b' );
278 $tables = array_flip( R::inspect() );
279 asrt( isset( $tables['page_page'] ), true );
280 $columns = R::inspect( 'page_page' );
281 asrt( isset( $columns['page2_id'] ), true );
285 * Test the unique constraint.
286 * Just want to make sure it is not too limiting and functions
287 * properly for typical RedBeanPHP usage.
291 public function testUnique()
294 $page1 = R::dispense( 'page' );
295 $tag1 = R::dispense( 'tag' );
296 $page2 = R::dispense( 'page' );
297 $tag2 = R::dispense( 'tag' );
298 $page3 = R::dispense( 'page' );
299 $tag3 = R::dispense( 'tag' );
300 $page1->sharedTag[] = $tag1;
302 //can we save all combinations with unique?
303 asrt( R::count( 'pageTag' ), 1);
304 $page1->sharedTag[] = $tag2;
306 asrt( R::count( 'pageTag' ), 2 );
307 $page1->sharedTag[] = $tag3;
308 $page2->sharedTag[] = $tag1;
309 $page2->sharedTag[] = $tag2;
310 $page2->sharedTag[] = $tag3;
311 $page3->sharedTag[] = $tag1;
312 $page3->sharedTag[] = $tag2;
313 $page3->sharedTag[] = $tag3;
314 R::storeAll( array( $page1, $page2, $page3 ) );
315 asrt( R::count('pageTag'), 9 );
316 $page1 = $page1->fresh();
317 $page1->sharedTag[] = $tag3;
319 //cant add violates unique
320 asrt( R::count( 'pageTag' ), 9 );
324 * Shared lists can only be formed using types.
325 * If you happen to have two beans of the same type you can still
326 * have a shared list but not with a sense of direction.
327 * I.e. quest->sharedQuest returns all the quests that follow the first one,
328 * but also the ones that are followed by the first one.
329 * If you want to have some sort of direction; i.e. one quest follows another one
330 * you'll have to use an alias: quest->target, but now you can't use the shared list
331 * anymore because it will start looking for a type named 'target' (which is just an alias)
332 * for quest, but it cant find that table and it's not possible to 'keep remembering'
333 * the alias throughout the system.
335 * The aggr() method solves this inconvenience.
336 * Aggr iterates through the list identified by its first argument ('target' -> ownQuestTargetList)
337 * and fetches every ID of the target (quest_target.target_id), loads these beans in batch for
338 * optimal performance, puts them back in the beans (questTarget->target) and returns the
343 public function testAggregationInSelfRefNM()
346 $quest1 = R::dispense( 'quest' );
347 $quest1->name = 'Quest 1';
348 $quest2 = R::dispense( 'quest' );
349 $quest2->name = 'Quest 2';
350 $quest3 = R::dispense( 'quest' );
351 $quest3->name = 'Quest 3';
352 $quest4 = R::dispense( 'quest' );
353 $quest4->name = 'Quest 4';
355 $quest1->link( 'questTarget' )->target = $quest2;
356 $quest1->link( 'questTarget' )->target = $quest3;
357 $quest3->link( 'questTarget' )->target = $quest4;
358 $quest3->link( 'questTarget' )->target = $quest1;
360 R::storeAll( array( $quest1, $quest3 ) );
362 //There should be 4 links
363 asrt( (int) R::count('questTarget'), 4 );
365 $quest1 = $quest1->fresh();
366 $targets = $quest1->aggr( 'ownQuestTargetList', 'target', 'quest' );
368 //can we aggregate the targets over the link type?
369 asrt( count( $targets), 2 );
371 //are the all valid beans?
372 foreach( $targets as $target ) {
374 asrt( ( $target instanceof OODBBean ), TRUE );
375 //are they fetched as quest?
376 asrt( ( $target->getMeta( 'type' ) ), 'quest' );
379 //list target should already have been loaded, nuke has no effect
381 $links = $quest1->ownQuestTargetList;
383 //are the links still there, have they been set in the beans as well?
384 asrt( count( $links ), 2);
386 //are they references instead of copies, changes in the aggregation set should affect the beans in links!
387 foreach( $targets as $target ) {
388 $target->name .= 'b';
389 asrt( substr( $target->name, -1 ), 'b' );
392 //do the names end with a 'b' here as well ? i.e. have they been changed through references?
393 foreach( $links as $link ) {
394 asrt( substr( $target->name, -1 ), 'b' );
397 //now test the effect on existing shadow...
399 $quest1 = R::dispense('quest');
400 $quest1->name = 'Quest 1';
401 $quest2 = R::dispense('quest');
402 $quest2->name = 'Quest 2';
403 $quest3 = R::dispense('quest');
404 $quest3->name = 'Quest 3';
405 $quest4 = R::dispense('quest');
406 $quest4->name = 'Quest 4';
408 $quest1->link( 'questTarget' )->target = $quest2;
409 $quest1->link( 'questTarget' )->target = $quest3;
412 asrt( (int) R::count( 'questTarget' ), 2 );
414 //now lets first build a shadow
415 $quest1->link( 'questTarget' )->target = $quest4;
417 //$quest1 = $quest1->fresh();
418 $targets = $quest1->aggr( 'ownQuestTargetList', 'target', 'quest' );
420 //targets should not include the new bean...
421 asrt( count($targets), 2 );
423 //this should not overwrite the existing beans
425 asrt( (int) R::count( 'questTarget' ), 3 );
429 * Test aggr without the aliasing.
433 public function testAggrBasic()
436 $book = R::dispense( 'book' );
437 $page1 = R::dispense( 'page' );
438 $page1->name = 'Page 1';
439 $text1 = R::dispense('text');
440 $text1->content = 'Text 1';
441 $page1->text = $text1;
442 $book->xownPageList[] = $page1;
443 $page2 = R::dispense( 'page' );
444 $page2->name = 'Page 2';
445 $text2 = R::dispense( 'text' );
446 $text2->content = 'Text 2';
447 $page2->text = $text2;
448 $book->xownPageList[] = $page2;
450 $book = $book->fresh();
451 $texts = $book->aggr( 'ownPageList', 'text' );
453 asrt( count( $texts ), 2 );
454 foreach( $texts as $text ) {
455 asrt( ( $text instanceof OODBBean ), TRUE );
457 $pages = $book->ownPageList;
458 asrt( count( $pages ), 2 );
459 asrt( R::count( 'page' ), 0 );
460 foreach( $pages as $page ) {
461 asrt( ( $page instanceof OODBBean ), TRUE );
463 asrt( ( $text instanceof OODBBean ), TRUE );
464 $text->content = 'CHANGED';
466 foreach( $texts as $text ) {
467 asrt( $text->content, 'CHANGED' );
472 * Test aggr with basic aliasing.
476 public function testAggrWithOnlyAlias()
479 $book = R::dispense( 'book' );
480 $page1 = R::dispense( 'page' );
481 $page1->name = 'Page 1';
482 $text1 = R::dispense( 'text' );
483 $text1->content = 'Text 1';
484 $page1->content = $text1;
485 $book->xownPageList[] = $page1;
486 $page2 = R::dispense( 'page' );
487 $page2->name = 'Page 2';
488 $text2 = R::dispense( 'text' );
489 $text2->content = 'Text 2';
490 $page2->content = $text2;
491 $book->xownPageList[] = $page2;
493 $book = $book->fresh();
494 $texts = $book->aggr( 'ownPageList', 'content', 'text' );
496 asrt( count( $texts ), 2 );
497 foreach( $texts as $text ) {
498 asrt( ( $text instanceof OODBBean), TRUE );
500 $pages = $book->ownPageList;
501 asrt( count( $pages ), 2 );
502 asrt( R::count( 'page' ), 0 );
503 foreach( $pages as $page ) {
504 asrt( ( $page instanceof OODBBean ), TRUE );
505 $text = $page->content;
506 asrt( ( $text instanceof OODBBean ), TRUE );
507 $text->content = 'CHANGED';
509 foreach( $texts as $text ) {
510 asrt( $text->content, 'CHANGED' );
515 * The aggr method can only be used with own-list.
519 public function testErrorHandlingAggr()
521 $wrongLists = array( 'not-an-own-list', 'OWNlist', 'Ownpage', 'ownbook', 'own book', '!', 'sharedBook' );
522 foreach( $wrongLists as $wrongList ) {
523 $bean = R::dispense( 'bean' );
525 $bean->aggr( $wrongList, 'field' );
527 } catch ( \Exception $exception ) {
529 asrt( ( $exception instanceof RedException ), TRUE );