Passed
Push — 4.1 ( cb7f15...ac53f7 )
by Maxime
08:41
created

testWriteManipulationWithNonScalarValuesDisallowed()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 9
nc 1
nop 0
dl 0
loc 13
rs 9.9666
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\ORM\Tests;
4
5
use SilverStripe\ORM\FieldType\DBMoney;
6
use SilverStripe\ORM\ManyManyList;
7
use SilverStripe\Core\Convert;
8
use SilverStripe\Dev\SapphireTest;
9
use SilverStripe\ORM\Tests\DataObjectTest\Player;
10
use SilverStripe\ORM\Tests\DataObjectTest\Team;
11
use SilverStripe\ORM\Tests\ManyManyListTest\ExtraFieldsObject;
12
use SilverStripe\ORM\Tests\ManyManyListTest\Product;
13
use InvalidArgumentException;
14
15
class ManyManyListTest extends SapphireTest
16
{
17
18
    protected static $fixture_file = 'DataObjectTest.yml';
19
20
    public static $extra_data_objects = [
21
        ManyManyListTest\Category::class,
22
        ManyManyListTest\ExtraFieldsObject::class,
23
        ManyManyListTest\Product::class,
24
        DataObjectTest\MockDynamicAssignmentDataObject::class
25
    ];
26
27
    public static function getExtraDataObjects()
28
    {
29
        return array_merge(
30
            DataObjectTest::$extra_data_objects,
31
            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...
32
        );
33
    }
34
35
    public function testAddCompositedExtraFields()
36
    {
37
        $obj = new ManyManyListTest\ExtraFieldsObject();
38
        $obj->write();
39
40
        $money = new DBMoney();
41
        $money->setAmount(100);
42
        $money->setCurrency('USD');
43
44
        // the actual test is that this does not generate an error in the sql.
45
        $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

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

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

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

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

218
        $team1->/** @scrutinizer ignore-call */ 
219
                Players()->add($player, array('Position' => 'Captain'));
Loading history...
219
        $this->assertEquals(
220
            array('Position' => 'Captain'),
221
            $team1->Players()->getExtraData('Teams', $player->ID),
222
            'Writes extrafields'
223
        );
224
225
        $team1->Players()->add($player);
226
        $this->assertEquals(
227
            array('Position' => 'Captain'),
228
            $team1->Players()->getExtraData('Teams', $player->ID),
229
            'Retains extrafields on subsequent adds with NULL fields'
230
        );
231
232
        $team1->Players()->add($player, array('Position' => 'Defense'));
233
        $this->assertEquals(
234
            array('Position' => 'Defense'),
235
            $team1->Players()->getExtraData('Teams', $player->ID),
236
            'Updates extrafields on subsequent adds with fields'
237
        );
238
239
        $team1->Players()->add($player, array('Position' => null));
240
        $this->assertEquals(
241
            array('Position' => null),
242
            $team1->Players()->getExtraData('Teams', $player->ID),
243
            'Allows clearing of extrafields on subsequent adds'
244
        );
245
    }
246
247
    public function testSubtractOnAManyManyList()
248
    {
249
        $allList = ManyManyList::create(
250
            Player::class,
251
            'DataObjectTest_Team_Players',
252
            'DataObjectTest_PlayerID',
253
            'DataObjectTest_TeamID'
254
        );
255
        $this->assertEquals(
256
            3,
257
            $allList->count(),
258
            'Precondition; we have all 3 players connected to a team in the list'
259
        );
260
261
        $teamOneID = $this->idFromFixture(Team::class, 'team1');
262
        $teamTwoID = $this->idFromFixture(Team::class, 'team2');
263
264
        // Captain 1 belongs to one team; team1
265
        $captain1 = $this->objFromFixture(Player::class, 'captain1');
266
        $this->assertEquals(
267
            array($teamOneID),
268
            $captain1->Teams()->column("ID"),
269
            'Precondition; player2 belongs to team1'
270
        );
271
272
        // Player 2 belongs to both teams: team1, team2
273
        $player2 = $this->objFromFixture(Player::class, 'player2');
274
        $this->assertEquals(
275
            array($teamOneID,$teamTwoID),
276
            $player2->Teams()->sort('Title')->column('ID'),
277
            'Precondition; player2 belongs to team1 and team2'
278
        );
279
280
        // We want to find the teams for player2 where the captain does not belong to
281
        $teamsWithoutTheCaptain = $player2->Teams()->subtract($captain1->Teams());
282
283
        // Assertions
284
        $this->assertEquals(
285
            1,
286
            $teamsWithoutTheCaptain->count(),
287
            'The ManyManyList should onlu contain one team'
288
        );
289
        $this->assertEquals(
290
            $teamTwoID,
291
            $teamsWithoutTheCaptain->first()->ID,
292
            'The ManyManyList contains the wrong team'
293
        );
294
    }
295
296
    public function testRemoveAll()
297
    {
298
        $first = new Team();
299
        $first->write();
300
301
        $second = new Team();
302
        $second->write();
303
304
        $firstPlayers = $first->Players();
305
        $secondPlayers = $second->Players();
306
307
        $a = new Player();
308
        $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...
309
        $a->write();
310
311
        $b = new Player();
312
        $b->ShirtNumber = 'b';
313
        $b->write();
314
315
        $firstPlayers->add($a);
316
        $firstPlayers->add($b);
317
318
        $secondPlayers->add($a);
319
        $secondPlayers->add($b);
320
321
        $this->assertEquals(array('a', 'b'), $firstPlayers->sort('ShirtNumber')->column('ShirtNumber'));
322
        $this->assertEquals(array('a', 'b'), $secondPlayers->sort('ShirtNumber')->column('ShirtNumber'));
323
324
        $firstPlayers->removeAll();
325
326
        $this->assertEquals(0, count($firstPlayers));
327
        $this->assertEquals(2, count($secondPlayers));
328
329
        $firstPlayers->removeAll();
330
331
        $firstPlayers->add($a);
332
        $firstPlayers->add($b);
333
334
        $this->assertEquals(array('a', 'b'), $firstPlayers->sort('ShirtNumber')->column('ShirtNumber'));
335
336
        $firstPlayers->filter('ShirtNumber', 'b')->removeAll();
337
338
        $this->assertEquals(array('a'), $firstPlayers->column('ShirtNumber'));
339
        $this->assertEquals(array('a', 'b'), $secondPlayers->sort('ShirtNumber')->column('ShirtNumber'));
340
341
        $this->assertNotNull(Player::get()->byID($a->ID));
342
        $this->assertNotNull(Player::get()->byID($b->ID));
343
    }
344
345
    public function testAppendExtraFieldsToQuery()
346
    {
347
        $list = new ManyManyList(
348
            ManyManyListTest\ExtraFieldsObject::class,
349
            'ManyManyListTest_ExtraFields_Clients',
350
            'ManyManyListTest_ExtraFieldsID',
351
            'ChildID',
352
            array(
353
                'Worth' => 'Money',
354
                'Reference' => 'Varchar'
355
            )
356
        );
357
358
        // ensure that ManyManyListTest_ExtraFields_Clients.ValueCurrency is
359
        // selected.
360
        $expected = 'SELECT DISTINCT "ManyManyListTest_ExtraFields_Clients"."WorthCurrency",'
361
            . ' "ManyManyListTest_ExtraFields_Clients"."WorthAmount", "ManyManyListTest_ExtraFields_Clients"."Reference",'
362
            . ' "ManyManyListTest_ExtraFields"."ClassName", "ManyManyListTest_ExtraFields"."LastEdited",'
363
            . ' "ManyManyListTest_ExtraFields"."Created", "ManyManyListTest_ExtraFields"."ID",'
364
            . ' CASE WHEN "ManyManyListTest_ExtraFields"."ClassName" IS NOT NULL THEN'
365
            . ' "ManyManyListTest_ExtraFields"."ClassName" ELSE ' . Convert::raw2sql(ManyManyListTest\ExtraFieldsObject::class, true)
366
            . ' END AS "RecordClassName" FROM "ManyManyListTest_ExtraFields" INNER JOIN'
367
            . ' "ManyManyListTest_ExtraFields_Clients" ON'
368
            . ' "ManyManyListTest_ExtraFields_Clients"."ManyManyListTest_ExtraFieldsID" ='
369
            . ' "ManyManyListTest_ExtraFields"."ID"';
370
371
        $this->assertSQLEquals($expected, $list->sql($parameters));
372
    }
373
374
    public function testFilteringOnPreviouslyJoinedTable()
375
    {
376
        /** @var ManyManyListTest\Category $category */
377
        $category = $this->objFromFixture(ManyManyListTest\Category::class, 'categorya');
378
379
        /** @var ManyManyList $productsRelatedToProductB */
380
        $productsRelatedToProductB = $category->Products()->filter('RelatedProducts.Title', 'Product A');
381
        $this->assertEquals(1, $productsRelatedToProductB->count());
382
    }
383
384
    public function testWriteManipulationWithNonScalarValuesAllowed()
385
    {
386
        $left = DataObjectTest\MockDynamicAssignmentDataObject::create();
387
        $left->write();
388
        $right = DataObjectTest\MockDynamicAssignmentDataObject::create();
389
        $right->write();
390
391
        $left->MockManyMany()->add($right, [
392
            'ManyManyStaticScalarOnlyField' => true,
393
            'ManyManyDynamicScalarOnlyField' => false,
394
            'ManyManyDynamicField' => true,
395
        ]);
396
397
        $pivot = $left->MockManyMany()->first();
398
399
        $this->assertNotFalse($pivot->ManyManyStaticScalarOnlyField);
400
        $this->assertNotTrue($pivot->ManyManyDynamicScalarOnlyField);
401
        $this->assertNotFalse($pivot->ManyManyDynamicField);
402
    }
403
404
    public function testWriteManipulationWithNonScalarValuesDisallowed()
405
    {
406
        $this->expectException(InvalidArgumentException::class);
407
408
        $left = DataObjectTest\MockDynamicAssignmentDataObject::create();
409
        $left->write();
410
        $right = DataObjectTest\MockDynamicAssignmentDataObject::create();
411
        $right->write();
412
413
        $left->MockManyMany()->add($right, [
414
            'ManyManyStaticScalarOnlyField' => false,
415
            'ManyManyDynamicScalarOnlyField' => true,
416
            'ManyManyDynamicField' => false,
417
        ]);
418
    }
419
}
420