Yaffs site version 1.1
[yaffs-website] / vendor / gabordemooij / redbean / testing / RedUNIT / Base / Cross.php
1 <?php
2
3 namespace RedUNIT\Base;
4
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;
11
12 /**
13  * Cross
14  *
15  * Tests self referential many-to-many relations, including the
16  * aggr feature.
17  *
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
24  *
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.
28  */
29
30 class Cross extends Base
31 {
32
33         /**
34          * Test how well aggr handles fields with no
35          * values.
36          *
37          * @return void
38          */
39         public function testAggrNullHandling()
40         {
41                 R::nuke();
42                 $book  = R::dispense( 'book' );
43                 $page = R::dispense( 'page' );
44                 $page->name = 'Page 3';
45                 $book->xownPageList[] = $page;
46                 R::store( $book );
47                 $book  = $book->fresh();
48                 $texts = $book->aggr( 'ownPageList', 'text' );
49                 pass();
50                 asrt( count( $texts ), 0 );
51                 asrt( is_array( $texts ), TRUE );
52                 R::nuke();
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;
69                 R::store( $book );
70                 $book  = $book->fresh();
71                 $texts = $book->aggr( 'ownPageList', 'text' );
72                 pass();
73                 asrt( count( $texts ), 2 );
74         }
75
76         /**
77          * Test many different scenarios with self referential
78          * many-to-many relations.
79          *
80          * @return void
81          */
82         public function testSelfReferentialCRUD()
83         {
84                 R::nuke();
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 );
95
96                 //does this yield valid combinations - cross relations / self ref n-m
97                 //need to symmetric....
98
99                 $names = R::gatherLabels( $buddies[0]->sharedBuddyList );
100                 sort( $names );
101                 $names = implode( ',', $names );
102                 asrt( $names, 'B,C' );
103
104                 unset( $buddies[0]->sharedBuddy );
105                 R::storeAll( $buddies );
106                 $buddies[0] = $buddies[0]->fresh();
107                 asrt( count( $buddies[0]->sharedBuddyList ), 2 );
108
109                 $buddies[3] = $buddies[3]->fresh();
110                 asrt( count( $buddies[3]->sharedBuddyList ), 1 );
111
112                 $names = R::gatherLabels( $buddies[3]->sharedBuddyList );
113                 sort( $names );
114                 $names = implode( ',', $names );
115                 asrt( $names, 'C' );
116
117                 $buddies[2] = $buddies[2]->fresh();
118                 asrt( count( $buddies[2]->sharedBuddyList ), 2 );
119
120                 $names = R::gatherLabels( $buddies[2]->sharedBuddyList );
121                 sort( $names );
122                 $names = implode( ',', $names );
123                 asrt( $names, 'A,D' );
124
125                 $buddies[1] = $buddies[1]->fresh();
126                 asrt( count( $buddies[1]->sharedBuddyList ), 1 );
127
128                 $names = R::gatherLabels( $buddies[1]->sharedBuddyList );
129                 sort( $names );
130                 $names = implode( ',', $names );
131                 asrt( $names, 'A' );
132
133                 //Can we add one?
134                 $buddies[1]->sharedBuddyList[] = R::dispense('buddy')->setAttr('name', 'E');
135                 R::store( $buddies[1] );
136
137                 $buddies[0] = $buddies[0]->fresh();
138                 asrt( count( $buddies[0]->sharedBuddyList ), 2 );
139
140                 $names = R::gatherLabels( $buddies[0]->sharedBuddyList );
141                 sort( $names );
142                 $names = implode( ',', $names );
143                 asrt( $names, 'B,C' );
144
145                 $buddies[1] = $buddies[1]->fresh();
146                 asrt( count( $buddies[1]->sharedBuddyList ), 2 );
147
148                 $names = R::gatherLabels( $buddies[1]->sharedBuddyList );
149                 sort( $names );
150                 $names = implode( ',', $names );
151                 asrt( $names, 'A,E' );
152
153                 $all = R::find( 'buddy' );
154                 asrt( count( $all ), 5 );
155
156                 foreach( $buddies[1]->sharedBuddy as $buddy ) {
157                         if ( $buddy->name === 'E' ) {
158                                 $buddyE = $buddy;
159                         }
160                 }
161
162                 asrt( isset( $buddyE ), TRUE );
163                 asrt( $buddyE->name, 'E' );
164
165                 //can we update?
166                 foreach( $buddies[0]->sharedBuddy as $buddy ) {
167                         if ( $buddy->name === 'C' ) {
168                                 $buddy->name = 'C2';
169                         }
170                 }
171
172                 R::store( $buddies[0] );
173
174                 $buddies[0] = $buddies[0]->fresh();
175                 asrt( count( $buddies[0]->sharedBuddyList ), 2 );
176
177                 $names = R::gatherLabels( $buddies[0]->sharedBuddyList );
178                 sort( $names );
179                 $names = implode( ',', $names );
180                 asrt( $names, 'B,C2' );
181
182                 $buddies[2] = $buddies[2]->fresh();
183                 asrt( count( $buddies[2]->sharedBuddyList ), 2 );
184
185                 $names = R::gatherLabels( $buddies[2]->sharedBuddyList );
186                 sort( $names );
187                 $names = implode( ',', $names );
188                 asrt( $names, 'A,D' );
189
190                 //can we delete?
191                 foreach( $buddies[0]->sharedBuddyList as $id => $buddy ) {
192                         if ( $buddy->name === 'B' ) {
193                                 unset( $buddies[0]->sharedBuddyList[$id] );
194                         }
195                 }
196
197                 R::store( $buddies[0] );
198
199                 $buddies[0] = $buddies[0]->fresh();
200                 asrt( count( $buddies[0]->sharedBuddyList ), 1 );
201
202                 $names = R::gatherLabels( $buddies[0]->sharedBuddyList );
203                 sort( $names );
204                 $names = implode( ',', $names );
205                 asrt( $names, 'C2' );
206
207                 $buddies[1] = $buddies[1]->fresh();
208                 asrt( count( $buddies[1]->sharedBuddyList ), 1 );
209
210                 $names = R::gatherLabels( $buddies[1]->sharedBuddyList );
211                 sort( $names );
212                 $names = implode( ',', $names );
213                 asrt( $names, 'E' );
214
215                 asrt( R::count( 'buddy' ), 5 );
216                 asrt( R::count( 'buddyBuddy' ), 3 );
217
218                 $buddies[3] = $buddies[3]->fresh();
219                 asrt( count( $buddies[3]->sharedBuddyList ), 1 );
220
221                 $names = R::gatherLabels( $buddies[3]->sharedBuddyList );
222                 sort( $names );
223                 $names = implode( ',', $names );
224                 asrt( $names, 'C2' );
225
226                 $buddies[2] = $buddies[2]->fresh();
227                 asrt( count( $buddies[2]->sharedBuddyList ), 2 );
228
229                 $names = R::gatherLabels( $buddies[2]->sharedBuddyList );
230                 sort( $names );
231                 $names = implode( ',', $names );
232                 asrt( $names, 'A,D' );
233
234                 $buddyE = $buddyE->fresh();
235                 asrt( count( $buddyE->sharedBuddyList ), 1 );
236
237                 $names = R::gatherLabels( $buddyE->sharedBuddyList );
238                 sort( $names );
239                 $names = implode( ',', $names );
240                 asrt( $names, 'B' );
241
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 );
248                 }
249
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 );
257                 }
258
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 );
263         }
264
265         /**
266          * Test self referential N-M relations (page_page).
267          *
268          * @return void
269          */
270         public function testSelfReferential()
271         {
272                 $page = R::dispense( 'page' )->setAttr( 'title', 'a' );
273                 $page->sharedPage[] = R::dispense( 'page' )->setAttr( 'title', 'b' );
274                 R::store( $page );
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 );
282         }
283
284         /**
285          * Test the unique constraint.
286          * Just want to make sure it is not too limiting and functions
287          * properly for typical RedBeanPHP usage.
288          *
289          * @return void
290          */
291         public function testUnique()
292         {
293                 R::nuke();
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;
301                 R::store( $page1 );
302                 //can we save all combinations with unique?
303                 asrt( R::count( 'pageTag' ), 1);
304                 $page1->sharedTag[] = $tag2;
305                 R::store( $page1 );
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;
318                 R::store( $page1 );
319                 //cant add violates unique
320                 asrt( R::count( 'pageTag' ), 9 );
321         }
322
323         /**
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.
334          *
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
339          * references.
340          *
341          * @return void
342          */
343         public function testAggregationInSelfRefNM()
344         {
345                 R::nuke();
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';
354
355                 $quest1->link( 'questTarget' )->target = $quest2;
356                 $quest1->link( 'questTarget' )->target = $quest3;
357                 $quest3->link( 'questTarget' )->target = $quest4;
358                 $quest3->link( 'questTarget' )->target = $quest1;
359
360                 R::storeAll( array( $quest1, $quest3 ) );
361
362                 //There should be 4 links
363                 asrt( (int) R::count('questTarget'), 4 );
364
365                 $quest1  = $quest1->fresh();
366                 $targets = $quest1->aggr( 'ownQuestTargetList', 'target', 'quest' );
367
368                 //can we aggregate the targets over the link type?
369                 asrt( count( $targets), 2 );
370
371                 //are the all valid beans?
372                 foreach( $targets as $target ) {
373                         //are they beans?
374                         asrt( ( $target instanceof OODBBean ), TRUE );
375                         //are they fetched as quest?
376                         asrt( ( $target->getMeta( 'type' ) ), 'quest' );
377                 }
378
379                 //list target should already have been loaded, nuke has no effect
380                 R::nuke();
381                 $links = $quest1->ownQuestTargetList;
382
383                 //are the links still there, have they been set in the beans as well?
384                 asrt( count( $links ), 2);
385
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' );
390                 }
391
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' );
395                 }
396
397                 //now test the effect on existing shadow...
398                 R::nuke();
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';
407
408                 $quest1->link( 'questTarget' )->target = $quest2;
409                 $quest1->link( 'questTarget' )->target = $quest3;
410
411                 R::store($quest1);
412                 asrt( (int) R::count( 'questTarget' ), 2 );
413
414                 //now lets first build a shadow
415                 $quest1->link( 'questTarget' )->target = $quest4;
416
417                 //$quest1 = $quest1->fresh();
418                 $targets = $quest1->aggr( 'ownQuestTargetList', 'target', 'quest' );
419
420                 //targets should not include the new bean...
421                 asrt( count($targets), 2 );
422
423                 //this should not overwrite the existing beans
424                 R::store($quest1);
425                 asrt( (int) R::count( 'questTarget' ), 3 );
426         }
427
428         /**
429          * Test aggr without the aliasing.
430          *
431          * @return void
432          */
433          public function testAggrBasic()
434          {
435                 R::nuke();
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;
449                 R::store( $book );
450                 $book  = $book->fresh();
451                 $texts = $book->aggr( 'ownPageList', 'text' );
452                 R::nuke();
453                 asrt( count( $texts ), 2 );
454                 foreach( $texts as $text ) {
455                         asrt( ( $text instanceof OODBBean ), TRUE );
456                 }
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 );
462                         $text = $page->text;
463                         asrt( ( $text instanceof OODBBean ), TRUE );
464                         $text->content = 'CHANGED';
465                 }
466                 foreach( $texts as $text ) {
467                         asrt( $text->content, 'CHANGED' );
468                 }
469          }
470
471         /**
472          * Test aggr with basic aliasing.
473          *
474          * @return void
475          */
476          public function testAggrWithOnlyAlias()
477          {
478                 R::nuke();
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;
492                 R::store( $book );
493                 $book = $book->fresh();
494                 $texts = $book->aggr( 'ownPageList', 'content', 'text' );
495                 R::nuke();
496                 asrt( count( $texts ), 2 );
497                 foreach( $texts as $text ) {
498                         asrt( ( $text instanceof OODBBean), TRUE );
499                 }
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';
508                 }
509                 foreach( $texts as $text ) {
510                         asrt( $text->content, 'CHANGED' );
511                 }
512          }
513
514          /**
515           * The aggr method can only be used with own-list.
516           *
517           * @return void
518           */
519          public function testErrorHandlingAggr()
520          {
521                 $wrongLists = array( 'not-an-own-list', 'OWNlist', 'Ownpage', 'ownbook', 'own book', '!', 'sharedBook' );
522                 foreach( $wrongLists as $wrongList ) {
523                         $bean = R::dispense( 'bean' );
524                         try {
525                                 $bean->aggr( $wrongList, 'field' );
526                                 fail();
527                         } catch ( \Exception $exception ) {
528                                 pass();
529                                 asrt( ( $exception instanceof RedException ), TRUE );
530                         }
531                 }
532          }
533 }