Passed
Push — 4.11 ( 972a77...dec858 )
by Guy
12:30 queued 06:01
created

ManyManyThroughListTest::testRemoveAll()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 14
nc 1
nop 0
dl 0
loc 24
rs 9.7998
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\ORM\Tests;
4
5
use InvalidArgumentException;
6
use SilverStripe\Core\Config\Config;
7
use SilverStripe\Dev\SapphireTest;
8
use SilverStripe\ORM\DataObject;
9
use SilverStripe\ORM\ManyManyThroughList;
10
use SilverStripe\ORM\Tests\DataObjectTest\Player;
11
use SilverStripe\ORM\Tests\DataObjectTest\Team;
12
use SilverStripe\ORM\Tests\ManyManyThroughListTest\Item;
13
use SilverStripe\ORM\Tests\ManyManyThroughListTest\PolyItem;
14
use SilverStripe\ORM\Tests\ManyManyThroughListTest\PolyJoinObject;
15
use SilverStripe\ORM\Tests\ManyManyThroughListTest\Locale;
16
use SilverStripe\ORM\Tests\ManyManyThroughListTest\FallbackLocale;
17
use SilverStripe\ORM\Tests\ManyManyThroughListTest\TestObject;
18
19
class ManyManyThroughListTest extends SapphireTest
20
{
21
    protected static $fixture_file = 'ManyManyThroughListTest.yml';
22
23
    protected static $extra_dataobjects = [
24
        ManyManyThroughListTest\Item::class,
25
        ManyManyThroughListTest\JoinObject::class,
26
        ManyManyThroughListTest\TestObject::class,
27
        ManyManyThroughListTest\PolyItem::class,
28
        ManyManyThroughListTest\PolyJoinObject::class,
29
        ManyManyThroughListTest\PolyObjectA::class,
30
        ManyManyThroughListTest\PolyObjectB::class,
31
        ManyManyThroughListTest\Locale::class,
32
        ManyManyThroughListTest\FallbackLocale::class,
33
    ];
34
35
    protected function setUp(): void
36
    {
37
        parent::setUp();
38
        DataObject::reset();
39
    }
40
41
    protected function tearDown(): void
42
    {
43
        DataObject::reset();
44
        parent::tearDown();
45
    }
46
47
    public function testSelectJoin()
48
    {
49
        /** @var ManyManyThroughListTest\TestObject $parent */
50
        $parent = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
51
        $this->assertListEquals(
52
            [
53
                ['Title' => 'item 1'],
54
                ['Title' => 'item 2']
55
            ],
56
            $parent->Items()
57
        );
58
        // Check filters on list work
59
        $item1 = $parent->Items()->filter('Title', 'item 1')->first();
60
        $this->assertNotNull($item1);
61
        $this->assertNotNull($item1->getJoin());
62
        $this->assertEquals('join 1', $item1->getJoin()->Title);
63
        $this->assertInstanceOf(
64
            ManyManyThroughListTest\JoinObject::class,
65
            $item1->ManyManyThroughListTest_JoinObject
0 ignored issues
show
Bug Best Practice introduced by
The property ManyManyThroughListTest_JoinObject does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
66
        );
67
        $this->assertEquals('join 1', $item1->ManyManyThroughListTest_JoinObject->Title);
68
69
        // Check filters on list work
70
        $item2 = $parent->Items()->filter('Title', 'item 2')->first();
71
        $this->assertNotNull($item2);
72
        $this->assertNotNull($item2->getJoin());
73
        $this->assertEquals('join 2', $item2->getJoin()->Title);
74
        $this->assertEquals('join 2', $item2->ManyManyThroughListTest_JoinObject->Title);
75
76
        // To filter on join table need to use some raw sql
77
        $item2 = $parent->Items()->where(['"ManyManyThroughListTest_JoinObject"."Title"' => 'join 2'])->first();
78
        $this->assertNotNull($item2);
79
        $this->assertEquals('item 2', $item2->Title);
80
        $this->assertNotNull($item2->getJoin());
81
        $this->assertEquals('join 2', $item2->getJoin()->Title);
82
        $this->assertEquals('join 2', $item2->ManyManyThroughListTest_JoinObject->Title);
83
84
        // Check that the join record is set for new records added
85
        $item3 = new Item;
86
        $this->assertNull($item3->getJoin());
87
        $parent->Items()->add($item3);
88
        $expectedJoinObject = ManyManyThroughListTest\JoinObject::get()->filter(['ParentID' => $parent->ID, 'ChildID' => $item3->ID ])->first();
89
        $this->assertEquals($expectedJoinObject->ID, $item3->getJoin()->ID);
90
        $this->assertEquals(get_class($expectedJoinObject), get_class($item3->getJoin()));
0 ignored issues
show
Bug introduced by
It seems like $expectedJoinObject can also be of type null; however, parameter $object of get_class() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

90
        $this->assertEquals(get_class(/** @scrutinizer ignore-type */ $expectedJoinObject), get_class($item3->getJoin()));
Loading history...
91
    }
92
93
    /**
94
     * @param string $sort
95
     * @param array $expected
96
     * @dataProvider sortingProvider
97
     */
98
    public function testSorting($sort, $expected)
99
    {
100
        /** @var ManyManyThroughListTest\TestObject $parent */
101
        $parent = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
102
103
        $items = $parent->Items();
104
        if ($sort) {
105
            $items = $items->sort($sort);
106
        }
107
        $this->assertSame($expected, $items->column('Title'));
108
    }
109
110
    /**
111
     * @return array[]
112
     */
113
    public function sortingProvider()
114
    {
115
        return [
116
            'nothing passed (default)' => [
117
                null,
118
                ['item 2', 'item 1'],
119
            ],
120
            'table with default column' => [
121
                '"ManyManyThroughListTest_JoinObject"."Sort"',
122
                ['item 2', 'item 1'],
123
            ],
124
            'table with default column ascending' => [
125
                '"ManyManyThroughListTest_JoinObject"."Sort" ASC',
126
                ['item 2', 'item 1'],
127
            ],
128
            'table with default column descending' => [
129
                '"ManyManyThroughListTest_JoinObject"."Sort" DESC',
130
                ['item 1', 'item 2'],
131
            ],
132
            'table with column descending' => [
133
                '"ManyManyThroughListTest_JoinObject"."Title" DESC',
134
                ['item 2', 'item 1'],
135
            ],
136
            'table with column ascending' => [
137
                '"ManyManyThroughListTest_JoinObject"."Title" ASC',
138
                ['item 1', 'item 2'],
139
            ],
140
            'default column' => [
141
                '"Sort"',
142
                ['item 2', 'item 1'],
143
            ],
144
            'default column ascending' => [
145
                '"Sort" ASC',
146
                ['item 2', 'item 1'],
147
            ],
148
            'default column descending' => [
149
                '"Sort" DESC',
150
                ['item 1', 'item 2'],
151
            ],
152
            'column descending' => [
153
                '"Title" DESC',
154
                ['item 2', 'item 1'],
155
            ],
156
            'column ascending' => [
157
                '"Title" ASC',
158
                ['item 1', 'item 2'],
159
            ],
160
        ];
161
    }
162
163
    public function testAdd()
164
    {
165
        /** @var ManyManyThroughListTest\TestObject $parent */
166
        $parent = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
167
        $newItem = new ManyManyThroughListTest\Item();
168
        $newItem->Title = 'my new item';
169
        $newItem->write();
170
        $parent->Items()->add($newItem, ['Title' => 'new join record']);
171
172
        // Check select
173
        $newItem = $parent->Items()->filter(['Title' => 'my new item'])->first();
174
        $this->assertNotNull($newItem);
175
        $this->assertEquals('my new item', $newItem->Title);
176
        $this->assertInstanceOf(
177
            ManyManyThroughListTest\JoinObject::class,
178
            $newItem->getJoin()
179
        );
180
        $this->assertInstanceOf(
181
            ManyManyThroughListTest\JoinObject::class,
182
            $newItem->ManyManyThroughListTest_JoinObject
0 ignored issues
show
Bug Best Practice introduced by
The property ManyManyThroughListTest_JoinObject does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
183
        );
184
        $this->assertEquals('new join record', $newItem->ManyManyThroughListTest_JoinObject->Title);
185
    }
186
187
    public function testRemove()
188
    {
189
        /** @var ManyManyThroughListTest\TestObject $parent */
190
        $parent = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
191
        $this->assertListEquals(
192
            [
193
                ['Title' => 'item 1'],
194
                ['Title' => 'item 2']
195
            ],
196
            $parent->Items()
197
        );
198
        $item1 = $parent->Items()->filter(['Title' => 'item 1'])->first();
199
        $parent->Items()->remove($item1);
200
        $this->assertListEquals(
201
            [['Title' => 'item 2']],
202
            $parent->Items()
203
        );
204
    }
205
206
    public function testRemoveAll()
207
    {
208
        $first = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
209
        $first->Items()->add($this->objFromFixture(ManyManyThroughListTest\Item::class, 'child0'));
0 ignored issues
show
Bug introduced by
The method Items() does not exist on SilverStripe\ORM\DataObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

209
        $first->/** @scrutinizer ignore-call */ 
210
                Items()->add($this->objFromFixture(ManyManyThroughListTest\Item::class, 'child0'));
Loading history...
210
        $second = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent2');
211
212
        $firstItems = $first->Items();
213
        $secondItems = $second->Items();
214
        $initialJoins = ManyManyThroughListTest\JoinObject::get()->count();
215
        $initialItems = ManyManyThroughListTest\Item::get()->count();
216
        $initialRelations = $firstItems->count();
217
        $initialSecondListRelations = $secondItems->count();
218
219
        $firstItems->removeAll();
220
221
        // Validate all items were removed from the first list, but none were removed from the second list
222
        $this->assertEquals(0, count($firstItems));
223
        $this->assertEquals($initialSecondListRelations, count($secondItems));
224
225
        // Validate that the JoinObjects were actually removed from the database
226
        $this->assertEquals($initialJoins - $initialRelations, ManyManyThroughListTest\JoinObject::get()->count());
227
228
        // Confirm Item objects were not removed from the database
229
        $this->assertEquals($initialItems, ManyManyThroughListTest\Item::get()->count());
230
    }
231
232
    public function testRemoveAllIgnoresLimit()
233
    {
234
        $parent = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
235
        $parent->Items()->add($this->objFromFixture(ManyManyThroughListTest\Item::class, 'child0'));
236
        $initialJoins = ManyManyThroughListTest\JoinObject::get()->count();
237
        // Validate there are enough items in the relation for this test
238
        $this->assertTrue($initialJoins > 1);
239
240
        $items = $parent->Items()->Limit(1);
241
        $items->removeAll();
242
243
        // Validate all items were removed from the list - not only one
244
        $this->assertEquals(0, count($items));
245
    }
246
247
    public function testFilteredRemoveAll()
248
    {
249
        $parent = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
250
        $parent->Items()->add($this->objFromFixture(ManyManyThroughListTest\Item::class, 'child0'));
251
        $items = $parent->Items();
252
        $initialJoins = ManyManyThroughListTest\JoinObject::get()->count();
0 ignored issues
show
Unused Code introduced by
The assignment to $initialJoins is dead and can be removed.
Loading history...
253
        $initialRelations = $items->count();
0 ignored issues
show
Unused Code introduced by
The assignment to $initialRelations is dead and can be removed.
Loading history...
254
255
        $items->filter('Title:not', 'not filtered')->removeAll();
256
257
        // Validate only the filtered items were removed
258
        $this->assertEquals(1, $items->count());
259
260
        // Validate the list only contains the correct remaining item
261
        $this->assertEquals(['not filtered'], $items->column('Title'));
262
    }
263
264
    /**
265
     * Test validation
266
     */
267
    public function testValidateModelValidatesJoinType()
268
    {
269
        $this->expectException(\InvalidArgumentException::class);
270
        DataObject::reset();
271
        ManyManyThroughListTest\Item::config()->update(
272
            'db',
273
            [
274
            ManyManyThroughListTest\JoinObject::class => 'Text'
275
            ]
276
        );
277
278
        DataObject::getSchema()->manyManyComponent(ManyManyThroughListTest\TestObject::class, 'Items');
279
    }
280
281
    public function testRelationParsing()
282
    {
283
        $schema = DataObject::getSchema();
284
285
        // Parent components
286
        $this->assertEquals(
287
            [
288
                'relationClass' => ManyManyThroughList::class,
289
                'parentClass' => ManyManyThroughListTest\TestObject::class,
290
                'childClass' => ManyManyThroughListTest\Item::class,
291
                'parentField' => 'ParentID',
292
                'childField' => 'ChildID',
293
                'join' => ManyManyThroughListTest\JoinObject::class
294
            ],
295
            $schema->manyManyComponent(ManyManyThroughListTest\TestObject::class, 'Items')
296
        );
297
298
        // Belongs_many_many is the same, but with parent/child substituted
299
        $this->assertEquals(
300
            [
301
                'relationClass' => ManyManyThroughList::class,
302
                'parentClass' => ManyManyThroughListTest\Item::class,
303
                'childClass' => ManyManyThroughListTest\TestObject::class,
304
                'parentField' => 'ChildID',
305
                'childField' => 'ParentID',
306
                'join' => ManyManyThroughListTest\JoinObject::class
307
            ],
308
            $schema->manyManyComponent(ManyManyThroughListTest\Item::class, 'Objects')
309
        );
310
    }
311
312
    /**
313
     * Note: polymorphic many_many support is currently experimental
314
     */
315
    public function testPolymorphicManyMany()
316
    {
317
        /** @var ManyManyThroughListTest\PolyObjectA $objA1 */
318
        $objA1 = $this->objFromFixture(ManyManyThroughListTest\PolyObjectA::class, 'obja1');
319
        /** @var ManyManyThroughListTest\PolyObjectB $objB1 */
320
        $objB1 = $this->objFromFixture(ManyManyThroughListTest\PolyObjectB::class, 'objb1');
321
        /** @var ManyManyThroughListTest\PolyObjectB $objB2 */
322
        $objB2 = $this->objFromFixture(ManyManyThroughListTest\PolyObjectB::class, 'objb2');
323
324
        // Test various parent class queries
325
        $this->assertListEquals([
326
            ['Title' => 'item 1'],
327
            ['Title' => 'item 2'],
328
        ], $objA1->Items());
329
        $this->assertListEquals([
330
            ['Title' => 'item 2'],
331
        ], $objB1->Items());
332
        $this->assertListEquals([
333
            ['Title' => 'item 2'],
334
        ], $objB2->Items());
335
336
        // Test adding items
337
        $newItem = new PolyItem();
338
        $newItem->Title = 'New Item';
339
        $objA1->Items()->add($newItem);
340
        $objB2->Items()->add($newItem);
341
        $this->assertListEquals([
342
            ['Title' => 'item 1'],
343
            ['Title' => 'item 2'],
344
            ['Title' => 'New Item'],
345
        ], $objA1->Items());
346
        $this->assertListEquals([
347
            ['Title' => 'item 2'],
348
        ], $objB1->Items());
349
        $this->assertListEquals([
350
            ['Title' => 'item 2'],
351
            ['Title' => 'New Item'],
352
        ], $objB2->Items());
353
354
        // Test removing items
355
        $item2 = $this->objFromFixture(ManyManyThroughListTest\PolyItem::class, 'child2');
356
        $objA1->Items()->remove($item2);
357
        $objB1->Items()->remove($item2);
358
        $this->assertListEquals([
359
            ['Title' => 'item 1'],
360
            ['Title' => 'New Item'],
361
        ], $objA1->Items());
362
        $this->assertListEquals([], $objB1->Items());
363
        $this->assertListEquals([
364
            ['Title' => 'item 2'],
365
            ['Title' => 'New Item'],
366
        ], $objB2->Items());
367
368
        // Test set-by-id-list
369
        $objB2->Items()->setByIDList([
370
            $newItem->ID,
371
            $this->idFromFixture(ManyManyThroughListTest\PolyItem::class, 'child1'),
372
        ]);
373
        $this->assertListEquals([
374
            ['Title' => 'item 1'],
375
            ['Title' => 'New Item'],
376
        ], $objA1->Items());
377
        $this->assertListEquals([], $objB1->Items());
378
        $this->assertListEquals([
379
            ['Title' => 'item 1'],
380
            ['Title' => 'New Item'],
381
        ], $objB2->Items());
382
    }
383
384
    public function testGetJoinTable()
385
    {
386
        $joinTable = DataObject::getSchema()->tableName(PolyJoinObject::class);
387
        /** @var ManyManyThroughListTest\PolyObjectA $objA1 */
388
        $objA1 = $this->objFromFixture(ManyManyThroughListTest\PolyObjectA::class, 'obja1');
389
        /** @var ManyManyThroughListTest\PolyObjectB $objB1 */
390
        $objB1 = $this->objFromFixture(ManyManyThroughListTest\PolyObjectB::class, 'objb1');
391
        /** @var ManyManyThroughListTest\PolyObjectB $objB2 */
392
        $objB2 = $this->objFromFixture(ManyManyThroughListTest\PolyObjectB::class, 'objb2');
393
394
        $this->assertEquals($joinTable, $objA1->Items()->getJoinTable());
395
        $this->assertEquals($joinTable, $objB1->Items()->getJoinTable());
396
        $this->assertEquals($joinTable, $objB2->Items()->getJoinTable());
397
    }
398
399
    /**
400
     * This tests that default sort works when the join table has a default sort set, and the main
401
     * dataobject has a default sort set.
402
     *
403
     * @return void
404
     */
405
    public function testDefaultSortOnJoinAndMain()
406
    {
407
        // We have spanish mexico with two fall back locales; argentina and international sorted in that order.
408
        $mexico = $this->objFromFixture(Locale::class, 'mexico');
409
410
        $fallbacks = $mexico->Fallbacks();
0 ignored issues
show
Bug introduced by
The method Fallbacks() does not exist on SilverStripe\ORM\DataObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

410
        /** @scrutinizer ignore-call */ 
411
        $fallbacks = $mexico->Fallbacks();
Loading history...
411
        $this->assertCount(2, $fallbacks);
412
413
        // Ensure the default sort is is correct
414
        list($first, $second) = $fallbacks;
415
        $this->assertSame('Argentina', $first->Title);
416
        $this->assertSame('International', $second->Title);
417
418
        // Ensure that we're respecting the default sort by reversing it
419
        Config::inst()->update(FallbackLocale::class, 'default_sort', '"ManyManyThroughTest_FallbackLocale"."Sort" DESC');
0 ignored issues
show
Bug introduced by
The method update() does not exist on SilverStripe\Config\Coll...nfigCollectionInterface. It seems like you code against a sub-type of SilverStripe\Config\Coll...nfigCollectionInterface such as SilverStripe\Config\Coll...\MemoryConfigCollection. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

419
        Config::inst()->/** @scrutinizer ignore-call */ update(FallbackLocale::class, 'default_sort', '"ManyManyThroughTest_FallbackLocale"."Sort" DESC');
Loading history...
420
421
        $reverse = $mexico->Fallbacks();
422
        list($firstReverse, $secondReverse) = $reverse;
423
        $this->assertSame('International', $firstReverse->Title);
424
        $this->assertSame('Argentina', $secondReverse->Title);
425
    }
426
427
    public function testCallbackOnSetById()
428
    {
429
        $addedIds = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $addedIds is dead and can be removed.
Loading history...
430
        $removedIds = [];
431
432
        $base = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
433
        $relation = $base->Items();
434
        $remove = $relation->First();
435
        $add = new Item();
436
        $add->write();
437
438
        $relation->addCallbacks()->add(function ($list, $item, $extraFields) use (&$removedIds) {
0 ignored issues
show
Unused Code introduced by
The parameter $extraFields is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

438
        $relation->addCallbacks()->add(function ($list, $item, /** @scrutinizer ignore-unused */ $extraFields) use (&$removedIds) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The import $removedIds is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
439
            $addedIds[] = $item;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$addedIds was never initialized. Although not strictly required by PHP, it is generally a good practice to add $addedIds = array(); before regardless.
Loading history...
440
        });
441
442
        $relation->removeCallbacks()->add(function ($list, $ids) use (&$removedIds) {
443
            $removedIds = $ids;
444
        });
445
446
        $relation->setByIDList(array_merge(
447
            $base->Items()->exclude('ID', $remove->ID)->column('ID'),
448
            [$add->ID]
449
        ));
450
        $this->assertEquals([$remove->ID], $removedIds);
451
    }
452
453
    public function testAddCallbackWithExtraFields()
454
    {
455
        $added = [];
456
457
        $base = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
458
        $relation = $base->Items();
459
        $add = new Item();
460
        $add->write();
461
462
        $relation->addCallbacks()->add(function ($list, $item, $extraFields) use (&$added) {
463
            $added[] = [$item, $extraFields];
464
        });
465
466
        $relation->add($add, ['Sort' => '99']);
467
        $this->assertEquals([[$add, ['Sort' => '99']]], $added);
468
    }
469
470
    public function testRemoveCallbackOnRemove()
471
    {
472
        $removedIds = [];
473
474
        $base = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
475
        $relation = $base->Items();
476
        $remove = $relation->First();
477
478
        $relation->removeCallbacks()->add(function ($list, $ids) use (&$removedIds) {
479
            $removedIds = $ids;
480
        });
481
482
        $relation->remove($remove);
483
        $this->assertEquals([$remove->ID], $removedIds);
484
    }
485
486
    public function testRemoveCallbackOnRemoveById()
487
    {
488
        $removedIds = [];
489
490
        $base = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
491
        $relation = $base->Items();
492
        $remove = $relation->First();
493
494
        $relation->removeCallbacks()->add(function ($list, $ids) use (&$removedIds) {
495
            $removedIds = $ids;
496
        });
497
498
        $relation->removeByID($remove->ID);
499
        $this->assertEquals([$remove->ID], $removedIds);
500
    }
501
502
    public function testRemoveCallbackOnRemoveAll()
503
    {
504
        $removedIds = [];
505
506
        $base = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
507
        $relation = $base->Items();
508
        $remove = $relation->column('ID');
509
510
        $relation->removeCallbacks()->add(function ($list, $ids) use (&$removedIds) {
511
            $removedIds = $ids;
512
        });
513
514
        $relation->removeAll();
515
        $this->assertEquals(sort($remove), sort($removedIds));
516
    }
517
}
518