Passed
Push — 4.3 ( ed7454...651d53 )
by Maxime
24:06 queued 15:47
created

ManyManyListTest::testAddingSingleDataObjectByID()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 14
nc 1
nop 0
dl 0
loc 17
rs 9.7998
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\ORM\Tests;
4
5
use SilverStripe\Core\Config\Config;
6
use SilverStripe\ORM\FieldType\DBMoney;
7
use SilverStripe\ORM\ManyManyList;
8
use SilverStripe\Core\Convert;
9
use SilverStripe\Dev\SapphireTest;
10
use SilverStripe\ORM\Tests\DataObjectTest\Player;
11
use SilverStripe\ORM\Tests\DataObjectTest\Team;
12
use SilverStripe\ORM\Tests\ManyManyListTest\ExtraFieldsObject;
13
use SilverStripe\ORM\Tests\ManyManyListTest\Product;
14
use InvalidArgumentException;
15
16
class ManyManyListTest extends SapphireTest
17
{
18
19
    protected static $fixture_file = 'DataObjectTest.yml';
20
21
    public static $extra_data_objects = [
22
        ManyManyListTest\Category::class,
23
        ManyManyListTest\ExtraFieldsObject::class,
24
        ManyManyListTest\Product::class,
25
        DataObjectTest\MockDynamicAssignmentDataObject::class
26
    ];
27
28
    public static function getExtraDataObjects()
29
    {
30
        return array_merge(
31
            DataObjectTest::$extra_data_objects,
32
            ManyManyListTest::$extra_data_objects
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
33
        );
34
    }
35
36
    public function testAddCompositedExtraFields()
37
    {
38
        $obj = new ManyManyListTest\ExtraFieldsObject();
39
        $obj->write();
40
41
        $money = new DBMoney();
42
        $money->setAmount(100);
43
        $money->setCurrency('USD');
44
45
        // the actual test is that this does not generate an error in the sql.
46
        $obj->Clients()->add(
0 ignored issues
show
Bug introduced by
The method Clients() does not exist on SilverStripe\ORM\Tests\M...tTest\ExtraFieldsObject. 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

46
        $obj->/** @scrutinizer ignore-call */ 
47
              Clients()->add(
Loading history...
47
            $obj,
48
            array(
49
            'Worth' => $money,
50
            'Reference' => 'Foo'
51
            )
52
        );
53
54
        $check = $obj->Clients()->First();
55
56
        $this->assertEquals('Foo', $check->Reference, 'Basic scalar fields should exist');
57
        $this->assertInstanceOf(DBMoney::class, $check->Worth, 'Composite fields should exist on the record');
58
        $this->assertEquals(100, $check->Worth->getAmount());
59
    }
60
61
    /**
62
     * This test targets a bug where appending many_many_extraFields to a query would
63
     * result in erroneous queries for sort orders that rely on _SortColumn0
64
     */
65
    public function testAddCompositedExtraFieldsWithSortColumn0()
66
    {
67
        $obj = new ExtraFieldsObject();
68
        $obj->write();
69
70
        $product = new Product();
71
        $product->Title = 'Test Product';
72
        $product->write();
73
74
        // the actual test is that this does not generate an error in the sql.
75
        $obj->Products()->add($product, array(
0 ignored issues
show
Bug introduced by
The method Products() does not exist on SilverStripe\ORM\Tests\M...tTest\ExtraFieldsObject. 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

75
        $obj->/** @scrutinizer ignore-call */ 
76
              Products()->add($product, array(
Loading history...
76
            'Reference' => 'Foo'
77
        ));
78
79
        $result = $obj->Products()->First();
80
        $this->assertEquals('Foo', $result->Reference, 'Basic scalar fields should exist');
81
        $this->assertEquals('Test Product', $result->Title);
82
    }
83
84
    public function testCreateList()
85
    {
86
        $list = ManyManyList::create(
87
            Team::class,
88
            'DataObjectTest_Team_Players',
89
            'DataObjectTest_TeamID',
90
            'DataObjectTest_PlayerID'
91
        );
92
        $this->assertEquals(2, $list->count());
93
    }
94
95
96
    public function testRelationshipEmptyOnNewRecords()
97
    {
98
        // Relies on the fact that (unrelated) teams exist in the fixture file already
99
        $newPlayer = new Player(); // many_many Teams
100
        $this->assertEquals(array(), $newPlayer->Teams()->column('ID'));
0 ignored issues
show
Bug introduced by
The method Teams() does not exist on SilverStripe\ORM\Tests\DataObjectTest\Player. 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

100
        $this->assertEquals(array(), $newPlayer->/** @scrutinizer ignore-call */ Teams()->column('ID'));
Loading history...
101
    }
102
103
    public function testAddingSingleDataObjectByReference()
104
    {
105
        $player1 = $this->objFromFixture(Player::class, 'player1');
106
        $team1 = $this->objFromFixture(Team::class, 'team1');
107
        $player1->Teams()->add($team1);
0 ignored issues
show
Bug introduced by
The method Teams() 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

107
        $player1->/** @scrutinizer ignore-call */ 
108
                  Teams()->add($team1);
Loading history...
108
        $player1->flushCache();
109
110
        $compareTeams = new ManyManyList(
111
            Team::class,
112
            'DataObjectTest_Team_Players',
113
            'DataObjectTest_TeamID',
114
            'DataObjectTest_PlayerID'
115
        );
116
        $compareTeams = $compareTeams->forForeignID($player1->ID);
117
        $this->assertEquals(
118
            $player1->Teams()->column('ID'),
119
            $compareTeams->column('ID'),
120
            "Adding single record as DataObject to many_many"
121
        );
122
    }
123
124
    public function testRemovingSingleDataObjectByReference()
125
    {
126
        $player1 = $this->objFromFixture(Player::class, 'player1');
127
        $team1 = $this->objFromFixture(Team::class, 'team1');
128
        $player1->Teams()->remove($team1);
129
        $player1->flushCache();
130
        $compareTeams = new ManyManyList(
131
            Team::class,
132
            'DataObjectTest_Team_Players',
133
            'DataObjectTest_TeamID',
134
            'DataObjectTest_PlayerID'
135
        );
136
        $compareTeams = $compareTeams->forForeignID($player1->ID);
137
        $this->assertEquals(
138
            $player1->Teams()->column('ID'),
139
            $compareTeams->column('ID'),
140
            "Removing single record as DataObject from many_many"
141
        );
142
    }
143
144
    public function testAddingSingleDataObjectByID()
145
    {
146
        $player1 = $this->objFromFixture(Player::class, 'player1');
147
        $team1 = $this->objFromFixture(Team::class, 'team1');
148
        $player1->Teams()->add($team1->ID);
149
        $player1->flushCache();
150
        $compareTeams = new ManyManyList(
151
            Team::class,
152
            'DataObjectTest_Team_Players',
153
            'DataObjectTest_TeamID',
154
            'DataObjectTest_PlayerID'
155
        );
156
        $compareTeams = $compareTeams->forForeignID($player1->ID);
157
        $this->assertEquals(
158
            $player1->Teams()->column('ID'),
159
            $compareTeams->column('ID'),
160
            "Adding single record as ID to many_many"
161
        );
162
    }
163
164
    public function testRemoveByID()
165
    {
166
        $player1 = $this->objFromFixture(Player::class, 'player1');
167
        $team1 = $this->objFromFixture(Team::class, 'team1');
168
        $player1->Teams()->removeByID($team1->ID);
169
        $player1->flushCache();
170
        $compareTeams = new ManyManyList(
171
            Team::class,
172
            'DataObjectTest_Team_Players',
173
            'DataObjectTest_TeamID',
174
            'DataObjectTest_PlayerID'
175
        );
176
        $compareTeams = $compareTeams->forForeignID($player1->ID);
177
        $this->assertEquals(
178
            $player1->Teams()->column('ID'),
179
            $compareTeams->column('ID'),
180
            "Removing single record as ID from many_many"
181
        );
182
    }
183
184
    public function testSetByIdList()
185
    {
186
        $player1 = $this->objFromFixture(Player::class, 'player1');
187
        $team1 = $this->objFromFixture(Team::class, 'team1');
188
        $team2 = $this->objFromFixture(Team::class, 'team2');
189
        $player1->Teams()->setByIdList(array($team1->ID, $team2->ID));
190
        $this->assertEquals(array($team1->ID, $team2->ID), $player1->Teams()->sort('Title')->column());
191
        $player1->Teams()->setByIdList(array($team1->ID));
192
        $this->assertEquals(array($team1->ID), $player1->Teams()->sort('Title')->column());
193
        $player1->Teams()->setByIdList(array($team2->ID));
194
        $this->assertEquals(array($team2->ID), $player1->Teams()->sort('Title')->column());
195
    }
196
197
    public function testAddingWithMultipleForeignKeys()
198
    {
199
        $newPlayer = new Player();
200
        $newPlayer->write();
201
        $team1 = $this->objFromFixture(Team::class, 'team1');
202
        $team2 = $this->objFromFixture(Team::class, 'team2');
203
204
        $playersTeam1Team2 = Team::get()->relation('Players')
205
            ->forForeignID(array($team1->ID, $team2->ID));
206
        $playersTeam1Team2->add($newPlayer);
207
        $this->assertEquals(
208
            array($team1->ID, $team2->ID),
209
            $newPlayer->Teams()->sort('Title')->column('ID')
210
        );
211
    }
212
213
    public function testAddingExistingDoesntRemoveExtraFields()
214
    {
215
        $player = new Player();
216
        $player->write();
217
        $team1 = $this->objFromFixture(Team::class, 'team1');
218
219
        $team1->Players()->add($player, array('Position' => 'Captain'));
0 ignored issues
show
Bug introduced by
The method Players() 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

219
        $team1->/** @scrutinizer ignore-call */ 
220
                Players()->add($player, array('Position' => 'Captain'));
Loading history...
220
        $this->assertEquals(
221
            array('Position' => 'Captain'),
222
            $team1->Players()->getExtraData('Teams', $player->ID),
223
            'Writes extrafields'
224
        );
225
226
        $team1->Players()->add($player);
227
        $this->assertEquals(
228
            array('Position' => 'Captain'),
229
            $team1->Players()->getExtraData('Teams', $player->ID),
230
            'Retains extrafields on subsequent adds with NULL fields'
231
        );
232
233
        $team1->Players()->add($player, array('Position' => 'Defense'));
234
        $this->assertEquals(
235
            array('Position' => 'Defense'),
236
            $team1->Players()->getExtraData('Teams', $player->ID),
237
            'Updates extrafields on subsequent adds with fields'
238
        );
239
240
        $team1->Players()->add($player, array('Position' => null));
241
        $this->assertEquals(
242
            array('Position' => null),
243
            $team1->Players()->getExtraData('Teams', $player->ID),
244
            'Allows clearing of extrafields on subsequent adds'
245
        );
246
    }
247
248
    public function testSubtractOnAManyManyList()
249
    {
250
        $allList = ManyManyList::create(
251
            Player::class,
252
            'DataObjectTest_Team_Players',
253
            'DataObjectTest_PlayerID',
254
            'DataObjectTest_TeamID'
255
        );
256
        $this->assertEquals(
257
            3,
258
            $allList->count(),
259
            'Precondition; we have all 3 players connected to a team in the list'
260
        );
261
262
        $teamOneID = $this->idFromFixture(Team::class, 'team1');
263
        $teamTwoID = $this->idFromFixture(Team::class, 'team2');
264
265
        // Captain 1 belongs to one team; team1
266
        $captain1 = $this->objFromFixture(Player::class, 'captain1');
267
        $this->assertEquals(
268
            array($teamOneID),
269
            $captain1->Teams()->column("ID"),
270
            'Precondition; player2 belongs to team1'
271
        );
272
273
        // Player 2 belongs to both teams: team1, team2
274
        $player2 = $this->objFromFixture(Player::class, 'player2');
275
        $this->assertEquals(
276
            array($teamOneID,$teamTwoID),
277
            $player2->Teams()->sort('Title')->column('ID'),
278
            'Precondition; player2 belongs to team1 and team2'
279
        );
280
281
        // We want to find the teams for player2 where the captain does not belong to
282
        $teamsWithoutTheCaptain = $player2->Teams()->subtract($captain1->Teams());
283
284
        // Assertions
285
        $this->assertEquals(
286
            1,
287
            $teamsWithoutTheCaptain->count(),
288
            'The ManyManyList should onlu contain one team'
289
        );
290
        $this->assertEquals(
291
            $teamTwoID,
292
            $teamsWithoutTheCaptain->first()->ID,
293
            'The ManyManyList contains the wrong team'
294
        );
295
    }
296
297
    public function testRemoveAll()
298
    {
299
        $first = new Team();
300
        $first->write();
301
302
        $second = new Team();
303
        $second->write();
304
305
        $firstPlayers = $first->Players();
306
        $secondPlayers = $second->Players();
307
308
        $a = new Player();
309
        $a->ShirtNumber = 'a';
0 ignored issues
show
Bug Best Practice introduced by
The property ShirtNumber does not exist on SilverStripe\ORM\Tests\DataObjectTest\Player. Since you implemented __set, consider adding a @property annotation.
Loading history...
310
        $a->write();
311
312
        $b = new Player();
313
        $b->ShirtNumber = 'b';
314
        $b->write();
315
316
        $firstPlayers->add($a);
317
        $firstPlayers->add($b);
318
319
        $secondPlayers->add($a);
320
        $secondPlayers->add($b);
321
322
        $this->assertEquals(array('a', 'b'), $firstPlayers->sort('ShirtNumber')->column('ShirtNumber'));
323
        $this->assertEquals(array('a', 'b'), $secondPlayers->sort('ShirtNumber')->column('ShirtNumber'));
324
325
        $firstPlayers->removeAll();
326
327
        $this->assertEquals(0, count($firstPlayers));
328
        $this->assertEquals(2, count($secondPlayers));
329
330
        $firstPlayers->removeAll();
331
332
        $firstPlayers->add($a);
333
        $firstPlayers->add($b);
334
335
        $this->assertEquals(array('a', 'b'), $firstPlayers->sort('ShirtNumber')->column('ShirtNumber'));
336
337
        $firstPlayers->filter('ShirtNumber', 'b')->removeAll();
338
339
        $this->assertEquals(array('a'), $firstPlayers->column('ShirtNumber'));
340
        $this->assertEquals(array('a', 'b'), $secondPlayers->sort('ShirtNumber')->column('ShirtNumber'));
341
342
        $this->assertNotNull(Player::get()->byID($a->ID));
343
        $this->assertNotNull(Player::get()->byID($b->ID));
344
    }
345
346
    public function testAppendExtraFieldsToQuery()
347
    {
348
        $list = new ManyManyList(
349
            ManyManyListTest\ExtraFieldsObject::class,
350
            'ManyManyListTest_ExtraFields_Clients',
351
            'ManyManyListTest_ExtraFieldsID',
352
            'ChildID',
353
            array(
354
                'Worth' => 'Money',
355
                'Reference' => 'Varchar'
356
            )
357
        );
358
359
        // ensure that ManyManyListTest_ExtraFields_Clients.ValueCurrency is
360
        // selected.
361
        $expected = 'SELECT DISTINCT "ManyManyListTest_ExtraFields_Clients"."WorthCurrency",'
362
            . ' "ManyManyListTest_ExtraFields_Clients"."WorthAmount", "ManyManyListTest_ExtraFields_Clients"."Reference",'
363
            . ' "ManyManyListTest_ExtraFields"."ClassName", "ManyManyListTest_ExtraFields"."LastEdited",'
364
            . ' "ManyManyListTest_ExtraFields"."Created", "ManyManyListTest_ExtraFields"."ID",'
365
            . ' CASE WHEN "ManyManyListTest_ExtraFields"."ClassName" IS NOT NULL THEN'
366
            . ' "ManyManyListTest_ExtraFields"."ClassName" ELSE ' . Convert::raw2sql(ManyManyListTest\ExtraFieldsObject::class, true)
367
            . ' END AS "RecordClassName" FROM "ManyManyListTest_ExtraFields" INNER JOIN'
368
            . ' "ManyManyListTest_ExtraFields_Clients" ON'
369
            . ' "ManyManyListTest_ExtraFields_Clients"."ManyManyListTest_ExtraFieldsID" ='
370
            . ' "ManyManyListTest_ExtraFields"."ID"';
371
372
        $this->assertSQLEquals($expected, $list->sql($parameters));
373
    }
374
375
    /**
376
     * This tests that we can set a default sort on a join table, even though the class doesn't exist.
377
     *
378
     * @return void
379
     */
380
    public function testSortByExtraFieldsDefaultSort()
381
    {
382
        $obj = new ManyManyListTest\ExtraFieldsObject();
383
        $obj->write();
384
385
        $obj2 = new ManyManyListTest\ExtraFieldsObject();
386
        $obj2->write();
387
388
        $money = new DBMoney();
389
        $money->setAmount(100);
390
        $money->setCurrency('USD');
391
392
        // Add two objects as relations (first is linking back to itself)
393
        $obj->Clients()->add($obj, ['Worth' => $money, 'Reference' => 'A']);
394
        $obj->Clients()->add($obj2, ['Worth' => $money, 'Reference' => 'B']);
395
396
        // Set the default sort for this relation
397
        Config::inst()->update('ManyManyListTest_ExtraFields_Clients', 'default_sort', 'Reference ASC');
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

397
        Config::inst()->/** @scrutinizer ignore-call */ update('ManyManyListTest_ExtraFields_Clients', 'default_sort', 'Reference ASC');
Loading history...
398
        $clients = $obj->Clients();
399
        $this->assertCount(2, $clients);
400
401
        list($first, $second) = $obj->Clients();
402
        $this->assertEquals('A', $first->Reference);
403
        $this->assertEquals('B', $second->Reference);
404
405
        // Now we ensure the default sort is being respected by reversing its order
406
        Config::inst()->update('ManyManyListTest_ExtraFields_Clients', 'default_sort', 'Reference DESC');
407
        $reverseClients = $obj->Clients();
408
        $this->assertCount(2, $reverseClients);
409
410
        list($reverseFirst, $reverseSecond) = $obj->Clients();
411
        $this->assertEquals('B', $reverseFirst->Reference);
412
        $this->assertEquals('A', $reverseSecond->Reference);
413
    }
414
415
    public function testFilteringOnPreviouslyJoinedTable()
416
    {
417
        /** @var ManyManyListTest\Category $category */
418
        $category = $this->objFromFixture(ManyManyListTest\Category::class, 'categorya');
419
420
        /** @var ManyManyList $productsRelatedToProductB */
421
        $productsRelatedToProductB = $category->Products()->filter('RelatedProducts.Title', 'Product A');
422
        $this->assertEquals(1, $productsRelatedToProductB->count());
423
    }
424
425
    public function testWriteManipulationWithNonScalarValuesAllowed()
426
    {
427
        $left = DataObjectTest\MockDynamicAssignmentDataObject::create();
428
        $left->write();
429
        $right = DataObjectTest\MockDynamicAssignmentDataObject::create();
430
        $right->write();
431
432
        $left->MockManyMany()->add($right, [
433
            'ManyManyStaticScalarOnlyField' => true,
434
            'ManyManyDynamicScalarOnlyField' => false,
435
            'ManyManyDynamicField' => true,
436
        ]);
437
438
        $pivot = $left->MockManyMany()->first();
439
440
        $this->assertNotFalse($pivot->ManyManyStaticScalarOnlyField);
441
        $this->assertNotTrue($pivot->ManyManyDynamicScalarOnlyField);
442
        $this->assertNotFalse($pivot->ManyManyDynamicField);
443
    }
444
445
    public function testWriteManipulationWithNonScalarValuesDisallowed()
446
    {
447
        $this->expectException(InvalidArgumentException::class);
448
449
        $left = DataObjectTest\MockDynamicAssignmentDataObject::create();
450
        $left->write();
451
        $right = DataObjectTest\MockDynamicAssignmentDataObject::create();
452
        $right->write();
453
454
        $left->MockManyMany()->add($right, [
455
            'ManyManyStaticScalarOnlyField' => false,
456
            'ManyManyDynamicScalarOnlyField' => true,
457
            'ManyManyDynamicField' => false,
458
        ]);
459
    }
460
}
461