Passed
Pull Request — 4.9 (#10295)
by Guy
06:57
created

testRemoveAllIgnoresLimit()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 0
dl 0
loc 13
rs 10
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
        $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

208
        $first->/** @scrutinizer ignore-call */ 
209
                Items()->add($this->objFromFixture(ManyManyThroughListTest\Item::class, 'child0'));
Loading history...
209
        $second = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent2');
210
211
        $firstItems = $first->Items();
212
        $secondItems = $second->Items();
213
        $initialJoins = ManyManyThroughListTest\JoinObject::get()->count();
214
        $initialItems = ManyManyThroughListTest\Item::get()->count();
215
        $initialRelations = $firstItems->count();
216
        $initialSecondListRelations = $secondItems->count();
217
218
        $firstItems->removeAll();
219
220
        // Validate all items were removed from the first list, but none were removed from the second list
221
        $this->assertEquals(0, count($firstItems));
222
        $this->assertEquals($initialSecondListRelations, count($secondItems));
223
224
        // Validate that the JoinObjects were actually removed from the database
225
        $this->assertEquals($initialJoins - $initialRelations, ManyManyThroughListTest\JoinObject::get()->count());
226
227
        // Confirm Item objects were not removed from the database
228
        $this->assertEquals($initialItems, ManyManyThroughListTest\Item::get()->count());
229
    }
230
231
    public function testRemoveAllIgnoresLimit()
232
    {
233
        $parent = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
234
        $parent->Items()->add($this->objFromFixture(ManyManyThroughListTest\Item::class, 'child0'));
235
        $initialJoins = ManyManyThroughListTest\JoinObject::get()->count();
236
        // Validate there are enough items in the relation for this test
237
        $this->assertTrue($initialJoins > 1);
238
239
        $items = $parent->Items()->Limit(1);
240
        $items->removeAll();
241
242
        // Validate all items were removed from the list - not only one
243
        $this->assertEquals(0, count($items));
244
    }
245
246
    public function testFilteredRemoveAll()
247
    {
248
        $parent = $this->objFromFixture(ManyManyThroughListTest\TestObject::class, 'parent1');
249
        $parent->Items()->add($this->objFromFixture(ManyManyThroughListTest\Item::class, 'child0'));
250
        $items = $parent->Items();
251
        $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...
252
        $initialRelations = $items->count();
0 ignored issues
show
Unused Code introduced by
The assignment to $initialRelations is dead and can be removed.
Loading history...
253
254
        $items->filter('Title:not', 'not filtered')->removeAll();
255
256
        // Validate only the filtered items were removed
257
        $this->assertEquals(1, $items->count());
258
259
        // Validate the list only contains the correct remaining item
260
        $this->assertEquals(['not filtered'], $items->column('Title'));
261
    }
262
263
    /**
264
     * Test validation
265
     *
266
     * @expectedException \InvalidArgumentException
267
     */
268
    public function testValidateModelValidatesJoinType()
269
    {
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