Passed
Pull Request — 4.9 (#10295)
by Guy
14:01
created

ManyManyThroughListTest::testRemoveAll()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 32
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 21
nc 1
nop 0
dl 0
loc 32
rs 9.584
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\ORM\Tests;
4
5
use SilverStripe\Core\Config\Config;
6
use SilverStripe\Dev\SapphireTest;
7
use SilverStripe\ORM\DataObject;
8
use SilverStripe\ORM\ManyManyThroughList;
9
use SilverStripe\ORM\Tests\DataObjectTest\Player;
10
use SilverStripe\ORM\Tests\DataObjectTest\Team;
11
use SilverStripe\ORM\Tests\ManyManyThroughListTest\Item;
12
use SilverStripe\ORM\Tests\ManyManyThroughListTest\PolyItem;
13
use SilverStripe\ORM\Tests\ManyManyThroughListTest\PolyJoinObject;
14
use SilverStripe\ORM\Tests\ManyManyThroughListTest\Locale;
15
use SilverStripe\ORM\Tests\ManyManyThroughListTest\FallbackLocale;
16
use SilverStripe\ORM\Tests\ManyManyThroughListTest\TestObject;
17
18
class ManyManyThroughListTest extends SapphireTest
19
{
20
    protected static $fixture_file = 'ManyManyThroughListTest.yml';
21
22
    protected static $extra_dataobjects = [
23
        ManyManyThroughListTest\Item::class,
24
        ManyManyThroughListTest\JoinObject::class,
25
        ManyManyThroughListTest\TestObject::class,
26
        ManyManyThroughListTest\PolyItem::class,
27
        ManyManyThroughListTest\PolyJoinObject::class,
28
        ManyManyThroughListTest\PolyObjectA::class,
29
        ManyManyThroughListTest\PolyObjectB::class,
30
        ManyManyThroughListTest\Locale::class,
31
        ManyManyThroughListTest\FallbackLocale::class,
32
    ];
33
34
    protected function setUp()
35
    {
36
        parent::setUp();
37
        DataObject::reset();
38
    }
39
40
    protected function tearDown()
41
    {
42
        DataObject::reset();
43
        parent::tearDown();
44
    }
45
46
    public function testSelectJoin()
47
    {
48
        /** @var ManyManyThroughListTest\TestObject $parent */
49
        $parent = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
50
        $this->assertListEquals(
51
            [
52
                ['Title' => 'item 1'],
53
                ['Title' => 'item 2']
54
            ],
55
            $parent->Items()
56
        );
57
        // Check filters on list work
58
        $item1 = $parent->Items()->filter('Title', 'item 1')->first();
59
        $this->assertNotNull($item1);
60
        $this->assertNotNull($item1->getJoin());
61
        $this->assertEquals('join 1', $item1->getJoin()->Title);
62
        $this->assertInstanceOf(
63
            ManyManyThroughListTest\JoinObject::class,
64
            $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...
65
        );
66
        $this->assertEquals('join 1', $item1->ManyManyThroughListTest_JoinObject->Title);
67
68
        // Check filters on list work
69
        $item2 = $parent->Items()->filter('Title', 'item 2')->first();
70
        $this->assertNotNull($item2);
71
        $this->assertNotNull($item2->getJoin());
72
        $this->assertEquals('join 2', $item2->getJoin()->Title);
73
        $this->assertEquals('join 2', $item2->ManyManyThroughListTest_JoinObject->Title);
74
75
        // To filter on join table need to use some raw sql
76
        $item2 = $parent->Items()->where(['"ManyManyThroughListTest_JoinObject"."Title"' => 'join 2'])->first();
77
        $this->assertNotNull($item2);
78
        $this->assertEquals('item 2', $item2->Title);
79
        $this->assertNotNull($item2->getJoin());
80
        $this->assertEquals('join 2', $item2->getJoin()->Title);
81
        $this->assertEquals('join 2', $item2->ManyManyThroughListTest_JoinObject->Title);
82
83
        // Check that the join record is set for new records added
84
        $item3 = new Item;
85
        $this->assertNull($item3->getJoin());
86
        $parent->Items()->add($item3);
87
        $expectedJoinObject = ManyManyThroughListTest\JoinObject::get()->filter(['ParentID' => $parent->ID, 'ChildID' => $item3->ID ])->first();
88
        $this->assertEquals($expectedJoinObject->ID, $item3->getJoin()->ID);
89
        $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

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

210
        /** @scrutinizer ignore-call */ 
211
        $firstItems = $first->Items();
Loading history...
211
        $secondItems = $second->Items();
212
        $initialJoins = ManyManyThroughListTest\JoinObject::get()->count();
213
214
        $firstItems->removeAll();
215
216
        $this->assertEquals(0, count($firstItems));
217
        $this->assertEquals(2, count($secondItems));
218
        $this->assertEquals($initialJoins - 2, ManyManyThroughListTest\JoinObject::get()->count());
219
220
        $firstItems->removeAll();
221
222
        $a = $this->objFromFixture(ManyManyThroughListTest\Item::class, 'child1');
223
        $b = $this->objFromFixture(ManyManyThroughListTest\Item::class, 'child2');
224
        $firstItems->add($a);
225
        $firstItems->add($b);
226
227
        $this->assertEquals(['item 1', 'item 2'], $firstItems->sort('Title')->column('Title'));
228
229
        $firstItems->filter('Title', 'item 2')->removeAll();
230
231
        $this->assertEquals(['item 1'], $firstItems->column('Title'));
232
        $this->assertEquals(['item 1', 'item 2'], $secondItems->sort('Title')->column('Title'));
233
        $this->assertEquals($initialJoins - 1, ManyManyThroughListTest\JoinObject::get()->count());
234
235
        $this->assertNotNull(ManyManyThroughListTest\Item::get()->byID($a->ID));
236
        $this->assertNotNull(ManyManyThroughListTest\Item::get()->byID($b->ID));
237
    }
238
239
    /**
240
     * Test validation
241
     *
242
     * @expectedException \InvalidArgumentException
243
     */
244
    public function testValidateModelValidatesJoinType()
245
    {
246
        DataObject::reset();
247
        ManyManyThroughListTest\Item::config()->update(
248
            'db',
249
            [
250
            ManyManyThroughListTest\JoinObject::class => 'Text'
251
            ]
252
        );
253
254
        DataObject::getSchema()->manyManyComponent(ManyManyThroughListTest\TestObject::class, 'Items');
255
    }
256
257
    public function testRelationParsing()
258
    {
259
        $schema = DataObject::getSchema();
260
261
        // Parent components
262
        $this->assertEquals(
263
            [
264
                'relationClass' => ManyManyThroughList::class,
265
                'parentClass' => ManyManyThroughListTest\TestObject::class,
266
                'childClass' => ManyManyThroughListTest\Item::class,
267
                'parentField' => 'ParentID',
268
                'childField' => 'ChildID',
269
                'join' => ManyManyThroughListTest\JoinObject::class
270
            ],
271
            $schema->manyManyComponent(ManyManyThroughListTest\TestObject::class, 'Items')
272
        );
273
274
        // Belongs_many_many is the same, but with parent/child substituted
275
        $this->assertEquals(
276
            [
277
                'relationClass' => ManyManyThroughList::class,
278
                'parentClass' => ManyManyThroughListTest\Item::class,
279
                'childClass' => ManyManyThroughListTest\TestObject::class,
280
                'parentField' => 'ChildID',
281
                'childField' => 'ParentID',
282
                'join' => ManyManyThroughListTest\JoinObject::class
283
            ],
284
            $schema->manyManyComponent(ManyManyThroughListTest\Item::class, 'Objects')
285
        );
286
    }
287
288
    /**
289
     * Note: polymorphic many_many support is currently experimental
290
     */
291
    public function testPolymorphicManyMany()
292
    {
293
        /** @var ManyManyThroughListTest\PolyObjectA $objA1 */
294
        $objA1 = $this->objFromFixture(ManyManyThroughListTest\PolyObjectA::class, 'obja1');
295
        /** @var ManyManyThroughListTest\PolyObjectB $objB1 */
296
        $objB1 = $this->objFromFixture(ManyManyThroughListTest\PolyObjectB::class, 'objb1');
297
        /** @var ManyManyThroughListTest\PolyObjectB $objB2 */
298
        $objB2 = $this->objFromFixture(ManyManyThroughListTest\PolyObjectB::class, 'objb2');
299
300
        // Test various parent class queries
301
        $this->assertListEquals([
302
            ['Title' => 'item 1'],
303
            ['Title' => 'item 2'],
304
        ], $objA1->Items());
305
        $this->assertListEquals([
306
            ['Title' => 'item 2'],
307
        ], $objB1->Items());
308
        $this->assertListEquals([
309
            ['Title' => 'item 2'],
310
        ], $objB2->Items());
311
312
        // Test adding items
313
        $newItem = new PolyItem();
314
        $newItem->Title = 'New Item';
315
        $objA1->Items()->add($newItem);
316
        $objB2->Items()->add($newItem);
317
        $this->assertListEquals([
318
            ['Title' => 'item 1'],
319
            ['Title' => 'item 2'],
320
            ['Title' => 'New Item'],
321
        ], $objA1->Items());
322
        $this->assertListEquals([
323
            ['Title' => 'item 2'],
324
        ], $objB1->Items());
325
        $this->assertListEquals([
326
            ['Title' => 'item 2'],
327
            ['Title' => 'New Item'],
328
        ], $objB2->Items());
329
330
        // Test removing items
331
        $item2 = $this->objFromFixture(ManyManyThroughListTest\PolyItem::class, 'child2');
332
        $objA1->Items()->remove($item2);
333
        $objB1->Items()->remove($item2);
334
        $this->assertListEquals([
335
            ['Title' => 'item 1'],
336
            ['Title' => 'New Item'],
337
        ], $objA1->Items());
338
        $this->assertListEquals([], $objB1->Items());
339
        $this->assertListEquals([
340
            ['Title' => 'item 2'],
341
            ['Title' => 'New Item'],
342
        ], $objB2->Items());
343
344
        // Test set-by-id-list
345
        $objB2->Items()->setByIDList([
346
            $newItem->ID,
347
            $this->idFromFixture(ManyManyThroughListTest\PolyItem::class, 'child1'),
348
        ]);
349
        $this->assertListEquals([
350
            ['Title' => 'item 1'],
351
            ['Title' => 'New Item'],
352
        ], $objA1->Items());
353
        $this->assertListEquals([], $objB1->Items());
354
        $this->assertListEquals([
355
            ['Title' => 'item 1'],
356
            ['Title' => 'New Item'],
357
        ], $objB2->Items());
358
    }
359
360
    public function testGetJoinTable()
361
    {
362
        $joinTable = DataObject::getSchema()->tableName(PolyJoinObject::class);
363
        /** @var ManyManyThroughListTest\PolyObjectA $objA1 */
364
        $objA1 = $this->objFromFixture(ManyManyThroughListTest\PolyObjectA::class, 'obja1');
365
        /** @var ManyManyThroughListTest\PolyObjectB $objB1 */
366
        $objB1 = $this->objFromFixture(ManyManyThroughListTest\PolyObjectB::class, 'objb1');
367
        /** @var ManyManyThroughListTest\PolyObjectB $objB2 */
368
        $objB2 = $this->objFromFixture(ManyManyThroughListTest\PolyObjectB::class, 'objb2');
369
370
        $this->assertEquals($joinTable, $objA1->Items()->getJoinTable());
371
        $this->assertEquals($joinTable, $objB1->Items()->getJoinTable());
372
        $this->assertEquals($joinTable, $objB2->Items()->getJoinTable());
373
    }
374
375
    /**
376
     * This tests that default sort works when the join table has a default sort set, and the main
377
     * dataobject has a default sort set.
378
     *
379
     * @return void
380
     */
381
    public function testDefaultSortOnJoinAndMain()
382
    {
383
        // We have spanish mexico with two fall back locales; argentina and international sorted in that order.
384
        $mexico = $this->objFromFixture(Locale::class, 'mexico');
385
386
        $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

386
        /** @scrutinizer ignore-call */ 
387
        $fallbacks = $mexico->Fallbacks();
Loading history...
387
        $this->assertCount(2, $fallbacks);
388
389
        // Ensure the default sort is is correct
390
        list($first, $second) = $fallbacks;
391
        $this->assertSame('Argentina', $first->Title);
392
        $this->assertSame('International', $second->Title);
393
394
        // Ensure that we're respecting the default sort by reversing it
395
        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

395
        Config::inst()->/** @scrutinizer ignore-call */ update(FallbackLocale::class, 'default_sort', '"ManyManyThroughTest_FallbackLocale"."Sort" DESC');
Loading history...
396
397
        $reverse = $mexico->Fallbacks();
398
        list($firstReverse, $secondReverse) = $reverse;
399
        $this->assertSame('International', $firstReverse->Title);
400
        $this->assertSame('Argentina', $secondReverse->Title);
401
    }
402
403
    public function testCallbackOnSetById()
404
    {
405
        $addedIds = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $addedIds is dead and can be removed.
Loading history...
406
        $removedIds = [];
407
408
        $base = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
409
        $relation = $base->Items();
410
        $remove = $relation->First();
411
        $add = new Item();
412
        $add->write();
413
414
        $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

414
        $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...
415
            $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...
416
        });
417
418
        $relation->removeCallbacks()->add(function ($list, $ids) use (&$removedIds) {
419
            $removedIds = $ids;
420
        });
421
422
        $relation->setByIDList(array_merge(
423
            $base->Items()->exclude('ID', $remove->ID)->column('ID'),
424
            [$add->ID]
425
        ));
426
        $this->assertEquals([$remove->ID], $removedIds);
427
    }
428
429
    public function testAddCallbackWithExtraFields()
430
    {
431
        $added = [];
432
433
        $base = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
434
        $relation = $base->Items();
435
        $add = new Item();
436
        $add->write();
437
438
        $relation->addCallbacks()->add(function ($list, $item, $extraFields) use (&$added) {
439
            $added[] = [$item, $extraFields];
440
        });
441
442
        $relation->add($add, ['Sort' => '99']);
443
        $this->assertEquals([[$add, ['Sort' => '99']]], $added);
444
    }
445
446
    public function testRemoveCallbackOnRemove()
447
    {
448
        $removedIds = [];
449
450
        $base = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
451
        $relation = $base->Items();
452
        $remove = $relation->First();
453
454
        $relation->removeCallbacks()->add(function ($list, $ids) use (&$removedIds) {
455
            $removedIds = $ids;
456
        });
457
458
        $relation->remove($remove);
459
        $this->assertEquals([$remove->ID], $removedIds);
460
    }
461
462
    public function testRemoveCallbackOnRemoveById()
463
    {
464
        $removedIds = [];
465
466
        $base = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
467
        $relation = $base->Items();
468
        $remove = $relation->First();
469
470
        $relation->removeCallbacks()->add(function ($list, $ids) use (&$removedIds) {
471
            $removedIds = $ids;
472
        });
473
474
        $relation->removeByID($remove->ID);
475
        $this->assertEquals([$remove->ID], $removedIds);
476
    }
477
478
    public function testRemoveCallbackOnRemoveAll()
479
    {
480
        $removedIds = [];
481
482
        $base = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
483
        $relation = $base->Items();
484
        $remove = $relation->column('ID');
485
486
        $relation->removeCallbacks()->add(function ($list, $ids) use (&$removedIds) {
487
            $removedIds = $ids;
488
        });
489
490
        $relation->removeAll();
491
        $this->assertEquals(sort($remove), sort($removedIds));
492
    }
493
}
494