Passed
Push — fix-1683 ( 00f5cf )
by Sam
08:14
created

DataObjectTest::testRelObject()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 26
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 14
nc 1
nop 0
dl 0
loc 26
rs 9.7998
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\ORM\Tests;
4
5
use InvalidArgumentException;
6
use LogicException;
7
use SilverStripe\Core\Config\Config;
8
use SilverStripe\Dev\SapphireTest;
9
use SilverStripe\i18n\i18n;
10
use SilverStripe\ORM\Connect\MySQLDatabase;
11
use SilverStripe\ORM\DataObject;
12
use SilverStripe\ORM\DataObjectSchema;
13
use SilverStripe\ORM\DB;
14
use SilverStripe\ORM\FieldType\DBBoolean;
15
use SilverStripe\ORM\FieldType\DBDatetime;
16
use SilverStripe\ORM\FieldType\DBField;
17
use SilverStripe\ORM\FieldType\DBPolymorphicForeignKey;
18
use SilverStripe\ORM\FieldType\DBVarchar;
19
use SilverStripe\ORM\ManyManyList;
20
use SilverStripe\ORM\Tests\DataObjectTest\Player;
21
use SilverStripe\View\ViewableData;
22
use stdClass;
23
24
class DataObjectTest extends SapphireTest
25
{
26
27
    protected static $fixture_file = 'DataObjectTest.yml';
28
29
    /**
30
     * Standard set of dataobject test classes
31
     *
32
     * @var array
33
     */
34
    public static $extra_data_objects = array(
35
        DataObjectTest\Team::class,
36
        DataObjectTest\Fixture::class,
37
        DataObjectTest\SubTeam::class,
38
        DataObjectTest\OtherSubclassWithSameField::class,
39
        DataObjectTest\FieldlessTable::class,
40
        DataObjectTest\FieldlessSubTable::class,
41
        DataObjectTest\ValidatedObject::class,
42
        DataObjectTest\Player::class,
43
        DataObjectTest\TeamComment::class,
44
        DataObjectTest\EquipmentCompany::class,
45
        DataObjectTest\SubEquipmentCompany::class,
46
        DataObjectTest\ExtendedTeamComment::class,
47
        DataObjectTest\Company::class,
48
        DataObjectTest\Staff::class,
49
        DataObjectTest\CEO::class,
50
        DataObjectTest\Fan::class,
51
        DataObjectTest\Play::class,
52
        DataObjectTest\Ploy::class,
53
        DataObjectTest\Bogey::class,
54
        DataObjectTest\Sortable::class,
55
        DataObjectTest\Bracket::class,
56
        DataObjectTest\RelationParent::class,
57
        DataObjectTest\RelationChildFirst::class,
58
        DataObjectTest\RelationChildSecond::class,
59
    );
60
61
    public static function getExtraDataObjects()
62
    {
63
        return array_merge(
64
            DataObjectTest::$extra_data_objects,
65
            ManyManyListTest::$extra_data_objects
66
        );
67
    }
68
69
    /**
70
     * @dataProvider provideSingletons
71
     */
72
    public function testSingleton($inst, $defaultValue, $altDefaultValue)
73
    {
74
        $inst = $inst();
75
        // Test that populateDefaults() isn't called on singletons
76
        // which can lead to SQL errors during build, and endless loops
77
        if ($defaultValue) {
78
            $this->assertEquals($defaultValue, $inst->MyFieldWithDefault);
79
        } else {
80
            $this->assertEmpty($inst->MyFieldWithDefault);
81
        }
82
83
        if ($altDefaultValue) {
84
            $this->assertEquals($altDefaultValue, $inst->MyFieldWithAltDefault);
85
        } else {
86
            $this->assertEmpty($inst->MyFieldWithAltDefault);
87
        }
88
    }
89
90
    public function provideSingletons()
91
    {
92
        // because PHPUnit evalutes test providers *before* setUp methods
93
        // any extensions added in the setUp methods won't be available
94
        // we must return closures to generate the arguments at run time
95
        return array(
96
            'create() static method' => array(function () {
97
                return DataObjectTest\Fixture::create();
98
            }, 'Default Value', 'Default Value'),
99
            'New object creation' => array(function () {
100
                return new DataObjectTest\Fixture();
101
            }, 'Default Value', 'Default Value'),
102
            'singleton() function' => array(function () {
103
                return singleton(DataObjectTest\Fixture::class);
104
            }, null, null),
105
            'singleton() static method' => array(function () {
106
                return DataObjectTest\Fixture::singleton();
107
            }, null, null),
108
            'Manual constructor args' => array(function () {
109
                return new DataObjectTest\Fixture(null, true);
110
            }, null, null),
111
        );
112
    }
113
114
    public function testDb()
115
    {
116
        $schema = DataObject::getSchema();
117
        $dbFields = $schema->fieldSpecs(DataObjectTest\TeamComment::class);
118
119
        // Assert fields are included
120
        $this->assertArrayHasKey('Name', $dbFields);
121
122
        // Assert the base fields are included
123
        $this->assertArrayHasKey('Created', $dbFields);
124
        $this->assertArrayHasKey('LastEdited', $dbFields);
125
        $this->assertArrayHasKey('ClassName', $dbFields);
126
        $this->assertArrayHasKey('ID', $dbFields);
127
128
        // Assert that the correct field type is returned when passing a field
129
        $this->assertEquals('Varchar', $schema->fieldSpec(DataObjectTest\TeamComment::class, 'Name'));
130
        $this->assertEquals('Text', $schema->fieldSpec(DataObjectTest\TeamComment::class, 'Comment'));
131
132
        // Test with table required
133
        $this->assertEquals(
134
            DataObjectTest\TeamComment::class . '.Varchar',
135
            $schema->fieldSpec(DataObjectTest\TeamComment::class, 'Name', DataObjectSchema::INCLUDE_CLASS)
136
        );
137
        $this->assertEquals(
138
            DataObjectTest\TeamComment::class . '.Text',
139
            $schema->fieldSpec(DataObjectTest\TeamComment::class, 'Comment', DataObjectSchema::INCLUDE_CLASS)
140
        );
141
        $dbFields = $schema->fieldSpecs(DataObjectTest\ExtendedTeamComment::class);
142
143
        // fixed fields are still included in extended classes
144
        $this->assertArrayHasKey('Created', $dbFields);
145
        $this->assertArrayHasKey('LastEdited', $dbFields);
146
        $this->assertArrayHasKey('ClassName', $dbFields);
147
        $this->assertArrayHasKey('ID', $dbFields);
148
149
        // Assert overloaded fields have correct data type
150
        $this->assertEquals('HTMLText', $schema->fieldSpec(DataObjectTest\ExtendedTeamComment::class, 'Comment'));
151
        $this->assertEquals(
152
            'HTMLText',
153
            $dbFields['Comment'],
154
            'Calls to DataObject::db without a field specified return correct data types'
155
        );
156
157
        // assertEquals doesn't verify the order of array elements, so access keys manually to check order:
158
        // expected: array('Name' => 'Varchar', 'Comment' => 'HTMLText')
159
        $this->assertEquals(
160
            array(
161
                'Name',
162
                'Comment'
163
            ),
164
            array_slice(array_keys($dbFields), 4, 2),
165
            'DataObject::db returns fields in correct order'
166
        );
167
    }
168
169
    public function testConstructAcceptsValues()
170
    {
171
        // Values can be an array...
172
        $player = new DataObjectTest\Player(
173
            array(
174
                'FirstName' => 'James',
175
                'Surname' => 'Smith'
176
            )
177
        );
178
179
        $this->assertEquals('James', $player->FirstName);
180
        $this->assertEquals('Smith', $player->Surname);
181
182
        // ... or a stdClass inst
183
        $data = new stdClass();
184
        $data->FirstName = 'John';
185
        $data->Surname = 'Doe';
186
        $player = new DataObjectTest\Player($data);
0 ignored issues
show
Bug introduced by
$data of type stdClass is incompatible with the type array|null expected by parameter $record of SilverStripe\ORM\Tests\D...t\Player::__construct(). ( Ignorable by Annotation )

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

186
        $player = new DataObjectTest\Player(/** @scrutinizer ignore-type */ $data);
Loading history...
187
188
        $this->assertEquals('John', $player->FirstName);
189
        $this->assertEquals('Doe', $player->Surname);
190
191
        // IDs should be stored as integers, not strings
192
        $player = new DataObjectTest\Player(array('ID' => '5'));
193
        $this->assertSame(5, $player->ID);
194
    }
195
196
    public function testValidObjectsForBaseFields()
197
    {
198
        $obj = new DataObjectTest\ValidatedObject();
199
200
        foreach (array('Created', 'LastEdited', 'ClassName', 'ID') as $field) {
201
            $helper = $obj->dbObject($field);
202
            $this->assertTrue(
203
                ($helper instanceof DBField),
204
                "for {$field} expected helper to be DBField, but was " . (is_object($helper) ? get_class($helper) : "null")
205
            );
206
        }
207
    }
208
209
    public function testDataIntegrityWhenTwoSubclassesHaveSameField()
210
    {
211
        // Save data into DataObjectTest_SubTeam.SubclassDatabaseField
212
        $obj = new DataObjectTest\SubTeam();
213
        $obj->SubclassDatabaseField = "obj-SubTeam";
0 ignored issues
show
Bug Best Practice introduced by
The property SubclassDatabaseField does not exist on SilverStripe\ORM\Tests\DataObjectTest\SubTeam. Since you implemented __set, consider adding a @property annotation.
Loading history...
214
        $obj->write();
215
216
        // Change the class
217
        $obj->ClassName = DataObjectTest\OtherSubclassWithSameField::class;
218
        $obj->write();
219
        $obj->flushCache();
220
221
        // Re-fetch from the database and confirm that the data is sourced from
222
        // OtherSubclassWithSameField.SubclassDatabaseField
223
        $obj = DataObject::get_by_id(DataObjectTest\Team::class, $obj->ID);
224
        $this->assertNull($obj->SubclassDatabaseField);
0 ignored issues
show
Bug Best Practice introduced by
The property SubclassDatabaseField does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
225
226
        // Confirm that save the object in the other direction.
227
        $obj->SubclassDatabaseField = 'obj-Other';
0 ignored issues
show
Bug Best Practice introduced by
The property SubclassDatabaseField does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
228
        $obj->write();
229
230
        $obj->ClassName = DataObjectTest\SubTeam::class;
231
        $obj->write();
232
        $obj->flushCache();
233
234
        // If we restore the class, the old value has been lying dormant and will be available again.
235
        // NOTE: This behaviour is volatile; we may change this in the future to clear fields that
236
        // are no longer relevant when changing ClassName
237
        $obj = DataObject::get_by_id(DataObjectTest\Team::class, $obj->ID);
238
        $this->assertEquals('obj-SubTeam', $obj->SubclassDatabaseField);
239
    }
240
241
    /**
242
     * Test deletion of DataObjects
243
     *   - Deleting using delete() on the DataObject
244
     *   - Deleting using DataObject::delete_by_id()
245
     */
246
    public function testDelete()
247
    {
248
        // Test deleting using delete() on the DataObject
249
        // Get the first page
250
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
251
        $objID = $obj->ID;
252
        // Check the page exists before deleting
253
        $this->assertTrue(is_object($obj) && $obj->exists());
254
        // Delete the page
255
        $obj->delete();
256
        // Check that page does not exist after deleting
257
        $obj = DataObject::get_by_id(DataObjectTest\Player::class, $objID);
258
        $this->assertTrue(!$obj || !$obj->exists());
259
260
261
        // Test deleting using DataObject::delete_by_id()
262
        // Get the second page
263
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
264
        $objID = $obj->ID;
265
        // Check the page exists before deleting
266
        $this->assertTrue(is_object($obj) && $obj->exists());
267
        // Delete the page
268
        DataObject::delete_by_id(DataObjectTest\Player::class, $obj->ID);
269
        // Check that page does not exist after deleting
270
        $obj = DataObject::get_by_id(DataObjectTest\Player::class, $objID);
271
        $this->assertTrue(!$obj || !$obj->exists());
272
    }
273
274
    /**
275
     * Test methods that get DataObjects
276
     *   - DataObject::get()
277
     *       - All records of a DataObject
278
     *       - Filtering
279
     *       - Sorting
280
     *       - Joins
281
     *       - Limit
282
     *       - Container class
283
     *   - DataObject::get_by_id()
284
     *   - DataObject::get_one()
285
     *        - With and without caching
286
     *        - With and without ordering
287
     */
288
    public function testGet()
289
    {
290
        // Test getting all records of a DataObject
291
        $comments = DataObject::get(DataObjectTest\TeamComment::class);
292
        $this->assertEquals(3, $comments->count());
293
294
        // Test WHERE clause
295
        $comments = DataObject::get(DataObjectTest\TeamComment::class, "\"Name\"='Bob'");
296
        $this->assertEquals(1, $comments->count());
297
        foreach ($comments as $comment) {
298
            $this->assertEquals('Bob', $comment->Name);
299
        }
300
301
        // Test sorting
302
        $comments = DataObject::get(DataObjectTest\TeamComment::class, '', "\"Name\" ASC");
303
        $this->assertEquals(3, $comments->count());
304
        $this->assertEquals('Bob', $comments->first()->Name);
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
305
        $comments = DataObject::get(DataObjectTest\TeamComment::class, '', "\"Name\" DESC");
306
        $this->assertEquals(3, $comments->count());
307
        $this->assertEquals('Phil', $comments->first()->Name);
308
309
        // Test limit
310
        $comments = DataObject::get(DataObjectTest\TeamComment::class, '', "\"Name\" ASC", '', '1,2');
311
        $this->assertEquals(2, $comments->count());
312
        $this->assertEquals('Joe', $comments->first()->Name);
313
        $this->assertEquals('Phil', $comments->last()->Name);
314
315
        // Test get_by_id()
316
        $captain1ID = $this->idFromFixture(DataObjectTest\Player::class, 'captain1');
317
        $captain1 = DataObject::get_by_id(DataObjectTest\Player::class, $captain1ID);
318
        $this->assertEquals('Captain', $captain1->FirstName);
0 ignored issues
show
Bug Best Practice introduced by
The property FirstName does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
319
320
        // Test get_one() without caching
321
        $comment1 = DataObject::get_one(
322
            DataObjectTest\TeamComment::class,
323
            array(
324
                '"DataObjectTest_TeamComment"."Name"' => 'Joe'
325
            ),
326
            false
327
        );
328
        $comment1->Comment = "Something Else";
0 ignored issues
show
Bug Best Practice introduced by
The property Comment does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
329
330
        $comment2 = DataObject::get_one(
331
            DataObjectTest\TeamComment::class,
332
            array(
333
                '"DataObjectTest_TeamComment"."Name"' => 'Joe'
334
            ),
335
            false
336
        );
337
        $this->assertNotEquals($comment1->Comment, $comment2->Comment);
0 ignored issues
show
Bug Best Practice introduced by
The property Comment does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
338
339
        // Test get_one() with caching
340
        $comment1 = DataObject::get_one(
341
            DataObjectTest\TeamComment::class,
342
            array(
343
                '"DataObjectTest_TeamComment"."Name"' => 'Bob'
344
            ),
345
            true
346
        );
347
        $comment1->Comment = "Something Else";
348
349
        $comment2 = DataObject::get_one(
350
            DataObjectTest\TeamComment::class,
351
            array(
352
                '"DataObjectTest_TeamComment"."Name"' => 'Bob'
353
            ),
354
            true
355
        );
356
        $this->assertEquals((string)$comment1->Comment, (string)$comment2->Comment);
357
358
        // Test get_one() with order by without caching
359
        $comment = DataObject::get_one(DataObjectTest\TeamComment::class, '', false, "\"Name\" ASC");
360
        $this->assertEquals('Bob', $comment->Name);
361
362
        $comment = DataObject::get_one(DataObjectTest\TeamComment::class, '', false, "\"Name\" DESC");
363
        $this->assertEquals('Phil', $comment->Name);
364
365
        // Test get_one() with order by with caching
366
        $comment = DataObject::get_one(DataObjectTest\TeamComment::class, '', true, '"Name" ASC');
367
        $this->assertEquals('Bob', $comment->Name);
368
        $comment = DataObject::get_one(DataObjectTest\TeamComment::class, '', true, '"Name" DESC');
369
        $this->assertEquals('Phil', $comment->Name);
370
    }
371
372
    public function testGetByIDCallerClass()
373
    {
374
        $captain1ID = $this->idFromFixture(DataObjectTest\Player::class, 'captain1');
375
        $captain1 = DataObjectTest\Player::get_by_id($captain1ID);
376
        $this->assertInstanceOf(DataObjectTest\Player::class, $captain1);
377
        $this->assertEquals('Captain', $captain1->FirstName);
378
379
        $captain2ID = $this->idFromFixture(DataObjectTest\Player::class, 'captain2');
380
        // make sure we can call from any class but get the one passed as an argument
381
        $captain2 = DataObjectTest\TeamComment::get_by_id(DataObjectTest\Player::class, $captain2ID);
382
        $this->assertInstanceOf(DataObjectTest\Player::class, $captain2);
383
        $this->assertEquals('Captain 2', $captain2->FirstName);
0 ignored issues
show
Bug Best Practice introduced by
The property FirstName does not exist on SilverStripe\ORM\Tests\DataObjectTest\TeamComment. Since you implemented __get, consider adding a @property annotation.
Loading history...
384
    }
385
386
    public function testGetCaseInsensitive()
387
    {
388
        // Test get_one() with bad case on the classname
389
        // Note: This will succeed only if the underlying DB server supports case-insensitive
390
        // table names (e.g. such as MySQL, but not SQLite3)
391
        if (!(DB::get_conn() instanceof MySQLDatabase)) {
392
            $this->markTestSkipped('MySQL only');
393
        }
394
395
        $subteam1 = DataObject::get_one(
396
            strtolower(DataObjectTest\SubTeam::class),
397
            array(
398
                '"DataObjectTest_Team"."Title"' => 'Subteam 1'
399
            ),
400
            true
401
        );
402
        $this->assertNotEmpty($subteam1);
403
        $this->assertEquals($subteam1->Title, "Subteam 1");
404
    }
405
406
    public function testGetSubclassFields()
407
    {
408
        /* Test that fields / has_one relations from the parent table and the subclass tables are extracted */
409
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, "captain1");
410
        // Base field
411
        $this->assertEquals('Captain', $captain1->FirstName);
0 ignored issues
show
Bug Best Practice introduced by
The property FirstName does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
412
        // Subclass field
413
        $this->assertEquals('007', $captain1->ShirtNumber);
0 ignored issues
show
Bug Best Practice introduced by
The property ShirtNumber does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
414
        // Subclass has_one relation
415
        $this->assertEquals($this->idFromFixture(DataObjectTest\Team::class, 'team1'), $captain1->FavouriteTeamID);
0 ignored issues
show
Bug Best Practice introduced by
The property FavouriteTeamID does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
416
    }
417
418
    public function testGetRelationClass()
419
    {
420
        $obj = new DataObjectTest\Player();
0 ignored issues
show
Unused Code introduced by
The assignment to $obj is dead and can be removed.
Loading history...
421
        $this->assertEquals(
422
            singleton(DataObjectTest\Player::class)->getRelationClass('FavouriteTeam'),
423
            DataObjectTest\Team::class,
424
            'has_one is properly inspected'
425
        );
426
        $this->assertEquals(
427
            singleton(DataObjectTest\Company::class)->getRelationClass('CurrentStaff'),
428
            DataObjectTest\Staff::class,
429
            'has_many is properly inspected'
430
        );
431
        $this->assertEquals(
432
            singleton(DataObjectTest\Team::class)->getRelationClass('Players'),
433
            DataObjectTest\Player::class,
434
            'many_many is properly inspected'
435
        );
436
        $this->assertEquals(
437
            singleton(DataObjectTest\Player::class)->getRelationClass('Teams'),
438
            DataObjectTest\Team::class,
439
            'belongs_many_many is properly inspected'
440
        );
441
        $this->assertEquals(
442
            singleton(DataObjectTest\CEO::class)->getRelationClass('Company'),
443
            DataObjectTest\Company::class,
444
            'belongs_to is properly inspected'
445
        );
446
        $this->assertEquals(
447
            singleton(DataObjectTest\Fan::class)->getRelationClass('Favourite'),
448
            DataObject::class,
449
            'polymorphic has_one is properly inspected'
450
        );
451
    }
452
453
    /**
454
     * Test that has_one relations can be retrieved
455
     */
456
    public function testGetHasOneRelations()
457
    {
458
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, "captain1");
459
        $team1ID = $this->idFromFixture(DataObjectTest\Team::class, 'team1');
460
461
        // There will be a field called (relname)ID that contains the ID of the
462
        // object linked to via the has_one relation
463
        $this->assertEquals($team1ID, $captain1->FavouriteTeamID);
0 ignored issues
show
Bug Best Practice introduced by
The property FavouriteTeamID does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
464
465
        // There will be a method called $obj->relname() that returns the object itself
466
        $this->assertEquals($team1ID, $captain1->FavouriteTeam()->ID);
0 ignored issues
show
Bug introduced by
The method FavouriteTeam() 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

466
        $this->assertEquals($team1ID, $captain1->/** @scrutinizer ignore-call */ FavouriteTeam()->ID);
Loading history...
467
468
        // Test that getNonReciprocalComponent can find has_one from the has_many end
469
        $this->assertEquals(
470
            $team1ID,
471
            $captain1->inferReciprocalComponent(DataObjectTest\Team::class, 'PlayerFans')->ID
0 ignored issues
show
Bug Best Practice introduced by
The property ID does not exist on SilverStripe\ORM\DataList. Since you implemented __get, consider adding a @property annotation.
Loading history...
472
        );
473
474
        // Check entity with polymorphic has-one
475
        $fan1 = $this->objFromFixture(DataObjectTest\Fan::class, "fan1");
476
        $this->assertTrue((bool)$fan1->hasValue('Favourite'));
477
478
        // There will be fields named (relname)ID and (relname)Class for polymorphic
479
        // entities
480
        $this->assertEquals($team1ID, $fan1->FavouriteID);
0 ignored issues
show
Bug Best Practice introduced by
The property FavouriteID does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
481
        $this->assertEquals(DataObjectTest\Team::class, $fan1->FavouriteClass);
0 ignored issues
show
Bug Best Practice introduced by
The property FavouriteClass does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
482
483
        // There will be a method called $obj->relname() that returns the object itself
484
        $favourite = $fan1->Favourite();
0 ignored issues
show
Bug introduced by
The method Favourite() 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

484
        /** @scrutinizer ignore-call */ 
485
        $favourite = $fan1->Favourite();
Loading history...
485
        $this->assertEquals($team1ID, $favourite->ID);
486
        $this->assertInstanceOf(DataObjectTest\Team::class, $favourite);
487
488
        // check behaviour of dbObject with polymorphic relations
489
        $favouriteDBObject = $fan1->dbObject('Favourite');
490
        $favouriteValue = $favouriteDBObject->getValue();
491
        $this->assertInstanceOf(DBPolymorphicForeignKey::class, $favouriteDBObject);
492
        $this->assertEquals($favourite->ID, $favouriteValue->ID);
493
        $this->assertEquals($favourite->ClassName, $favouriteValue->ClassName);
494
    }
495
496
    public function testLimitAndCount()
497
    {
498
        $players = DataObject::get(DataObjectTest\Player::class);
499
500
        // There's 4 records in total
501
        $this->assertEquals(4, $players->count());
502
503
        // Testing "##, ##" syntax
504
        $this->assertEquals(4, $players->limit(20)->count());
505
        $this->assertEquals(4, $players->limit(20, 0)->count());
506
        $this->assertEquals(0, $players->limit(20, 20)->count());
507
        $this->assertEquals(2, $players->limit(2, 0)->count());
508
        $this->assertEquals(1, $players->limit(5, 3)->count());
509
    }
510
511
    public function testWriteNoChangesDoesntUpdateLastEdited()
512
    {
513
        // set mock now so we can be certain of LastEdited time for our test
514
        DBDatetime::set_mock_now('2017-01-01 00:00:00');
515
        $obj = new Player();
516
        $obj->FirstName = 'Test';
517
        $obj->Surname = 'Plater';
518
        $obj->Email = '[email protected]';
519
        $obj->write();
520
        $this->assertEquals('2017-01-01 00:00:00', $obj->LastEdited);
521
        $writtenObj = Player::get()->byID($obj->ID);
522
        $this->assertEquals('2017-01-01 00:00:00', $writtenObj->LastEdited);
523
524
        // set mock now so we get a new LastEdited if, for some reason, it's updated
525
        DBDatetime::set_mock_now('2017-02-01 00:00:00');
526
        $writtenObj->write();
527
        $this->assertEquals('2017-01-01 00:00:00', $writtenObj->LastEdited);
528
        $this->assertEquals($obj->ID, $writtenObj->ID);
529
530
        $reWrittenObj = Player::get()->byID($writtenObj->ID);
531
        $this->assertEquals('2017-01-01 00:00:00', $reWrittenObj->LastEdited);
532
    }
533
534
    /**
535
     * Test writing of database columns which don't correlate to a DBField,
536
     * e.g. all relation fields on has_one/has_many like "ParentID".
537
     */
538
    public function testWritePropertyWithoutDBField()
539
    {
540
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
541
        $obj->FavouriteTeamID = 99;
0 ignored issues
show
Bug Best Practice introduced by
The property FavouriteTeamID does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
542
        $obj->write();
543
544
        // reload the page from the database
545
        $savedObj = DataObject::get_by_id(DataObjectTest\Player::class, $obj->ID);
546
        $this->assertTrue($savedObj->FavouriteTeamID == 99);
0 ignored issues
show
Bug Best Practice introduced by
The property FavouriteTeamID does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
547
548
        // Test with porymorphic relation
549
        $obj2 = $this->objFromFixture(DataObjectTest\Fan::class, "fan1");
550
        $obj2->FavouriteID = 99;
0 ignored issues
show
Bug Best Practice introduced by
The property FavouriteID does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
551
        $obj2->FavouriteClass = DataObjectTest\Player::class;
0 ignored issues
show
Bug Best Practice introduced by
The property FavouriteClass does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
552
        $obj2->write();
553
554
        $savedObj2 = DataObject::get_by_id(DataObjectTest\Fan::class, $obj2->ID);
555
        $this->assertTrue($savedObj2->FavouriteID == 99);
0 ignored issues
show
Bug Best Practice introduced by
The property FavouriteID does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
556
        $this->assertTrue($savedObj2->FavouriteClass == DataObjectTest\Player::class);
0 ignored issues
show
Bug Best Practice introduced by
The property FavouriteClass does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
557
    }
558
559
    /**
560
     * Test has many relationships
561
     *   - Test getComponents() gets the ComponentSet of the other side of the relation
562
     *   - Test the IDs on the DataObjects are set correctly
563
     */
564
    public function testHasManyRelationships()
565
    {
566
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
567
568
        // Test getComponents() gets the ComponentSet of the other side of the relation
569
        $this->assertTrue($team1->Comments()->count() == 2);
0 ignored issues
show
Bug introduced by
The method Comments() 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

569
        $this->assertTrue($team1->/** @scrutinizer ignore-call */ Comments()->count() == 2);
Loading history...
570
571
        $team1Comments = [
572
            ['Comment' => 'This is a team comment by Joe'],
573
            ['Comment' => 'This is a team comment by Bob'],
574
        ];
575
576
        // Test the IDs on the DataObjects are set correctly
577
        $this->assertListEquals($team1Comments, $team1->Comments());
578
579
        // Test that has_many can be infered from the has_one via getNonReciprocalComponent
580
        $this->assertListEquals(
581
            $team1Comments,
582
            $team1->inferReciprocalComponent(DataObjectTest\TeamComment::class, 'Team')
0 ignored issues
show
Bug introduced by
It seems like $team1->inferReciprocalC...Comment::class, 'Team') can also be of type SilverStripe\ORM\DataObject; however, parameter $list of SilverStripe\Dev\SapphireTest::assertListEquals() does only seem to accept SilverStripe\ORM\SS_List, 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

582
            /** @scrutinizer ignore-type */ $team1->inferReciprocalComponent(DataObjectTest\TeamComment::class, 'Team')
Loading history...
583
        );
584
585
        // Test that we can add and remove items that already exist in the database
586
        $newComment = new DataObjectTest\TeamComment();
587
        $newComment->Name = "Automated commenter";
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\Tests\DataObjectTest\TeamComment. Since you implemented __set, consider adding a @property annotation.
Loading history...
588
        $newComment->Comment = "This is a new comment";
0 ignored issues
show
Bug Best Practice introduced by
The property Comment does not exist on SilverStripe\ORM\Tests\DataObjectTest\TeamComment. Since you implemented __set, consider adding a @property annotation.
Loading history...
589
        $newComment->write();
590
        $team1->Comments()->add($newComment);
591
        $this->assertEquals($team1->ID, $newComment->TeamID);
0 ignored issues
show
Bug Best Practice introduced by
The property TeamID does not exist on SilverStripe\ORM\Tests\DataObjectTest\TeamComment. Since you implemented __get, consider adding a @property annotation.
Loading history...
592
593
        $comment1 = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment1');
594
        $comment2 = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment2');
595
        $team1->Comments()->remove($comment2);
596
597
        $team1CommentIDs = $team1->Comments()->sort('ID')->column('ID');
0 ignored issues
show
Bug introduced by
The method sort() does not exist on SilverStripe\ORM\SS_List. It seems like you code against a sub-type of said class. However, the method does not exist in SilverStripe\ORM\Filterable or SilverStripe\ORM\Limitable. Are you sure you never get one of those? ( Ignorable by Annotation )

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

597
        $team1CommentIDs = $team1->Comments()->/** @scrutinizer ignore-call */ sort('ID')->column('ID');
Loading history...
598
        $this->assertEquals(array($comment1->ID, $newComment->ID), $team1CommentIDs);
599
600
        // Test that removing an item from a list doesn't remove it from the same
601
        // relation belonging to a different object
602
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
603
        $team2 = $this->objFromFixture(DataObjectTest\Team::class, 'team2');
604
        $team2->Comments()->remove($comment1);
605
        $team1CommentIDs = $team1->Comments()->sort('ID')->column('ID');
606
        $this->assertEquals(array($comment1->ID, $newComment->ID), $team1CommentIDs);
607
    }
608
609
610
    /**
611
     * Test has many relationships against polymorphic has_one fields
612
     *   - Test getComponents() gets the ComponentSet of the other side of the relation
613
     *   - Test the IDs on the DataObjects are set correctly
614
     */
615
    public function testHasManyPolymorphicRelationships()
616
    {
617
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
618
619
        // Test getComponents() gets the ComponentSet of the other side of the relation
620
        $this->assertTrue($team1->Fans()->count() == 2);
0 ignored issues
show
Bug introduced by
The method Fans() 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

620
        $this->assertTrue($team1->/** @scrutinizer ignore-call */ Fans()->count() == 2);
Loading history...
621
622
        // Test the IDs/Classes on the DataObjects are set correctly
623
        foreach ($team1->Fans() as $fan) {
624
            $this->assertEquals($team1->ID, $fan->FavouriteID, 'Fan has the correct FavouriteID');
625
            $this->assertEquals(DataObjectTest\Team::class, $fan->FavouriteClass, 'Fan has the correct FavouriteClass');
626
        }
627
628
        // Test that we can add and remove items that already exist in the database
629
        $newFan = new DataObjectTest\Fan();
630
        $newFan->Name = "New fan";
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\Tests\DataObjectTest\Fan. Since you implemented __set, consider adding a @property annotation.
Loading history...
631
        $newFan->write();
632
        $team1->Fans()->add($newFan);
633
        $this->assertEquals($team1->ID, $newFan->FavouriteID, 'Newly created fan has the correct FavouriteID');
0 ignored issues
show
Bug Best Practice introduced by
The property FavouriteID does not exist on SilverStripe\ORM\Tests\DataObjectTest\Fan. Since you implemented __get, consider adding a @property annotation.
Loading history...
634
        $this->assertEquals(
635
            DataObjectTest\Team::class,
636
            $newFan->FavouriteClass,
0 ignored issues
show
Bug Best Practice introduced by
The property FavouriteClass does not exist on SilverStripe\ORM\Tests\DataObjectTest\Fan. Since you implemented __get, consider adding a @property annotation.
Loading history...
637
            'Newly created fan has the correct FavouriteClass'
638
        );
639
640
        $fan1 = $this->objFromFixture(DataObjectTest\Fan::class, 'fan1');
641
        $fan3 = $this->objFromFixture(DataObjectTest\Fan::class, 'fan3');
642
        $team1->Fans()->remove($fan3);
643
644
        $team1FanIDs = $team1->Fans()->sort('ID')->column('ID');
645
        $this->assertEquals(array($fan1->ID, $newFan->ID), $team1FanIDs);
646
647
        // Test that removing an item from a list doesn't remove it from the same
648
        // relation belonging to a different object
649
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
650
        $player1 = $this->objFromFixture(DataObjectTest\Player::class, 'player1');
651
        $player1->Fans()->remove($fan1);
652
        $team1FanIDs = $team1->Fans()->sort('ID')->column('ID');
653
        $this->assertEquals(array($fan1->ID, $newFan->ID), $team1FanIDs);
654
    }
655
656
657
    public function testHasOneRelationship()
658
    {
659
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
660
        $player1 = $this->objFromFixture(DataObjectTest\Player::class, 'player1');
661
        $player2 = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
662
        $fan1 = $this->objFromFixture(DataObjectTest\Fan::class, 'fan1');
663
664
        // Test relation probing
665
        $this->assertFalse((bool)$team1->hasValue('Captain', null, false));
666
        $this->assertFalse((bool)$team1->hasValue('CaptainID', null, false));
667
668
        // Add a captain to team 1
669
        $team1->setField('CaptainID', $player1->ID);
670
        $team1->write();
671
672
        $this->assertTrue((bool)$team1->hasValue('Captain', null, false));
673
        $this->assertTrue((bool)$team1->hasValue('CaptainID', null, false));
674
675
        $this->assertEquals(
676
            $player1->ID,
677
            $team1->Captain()->ID,
0 ignored issues
show
Bug introduced by
The method Captain() 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

677
            $team1->/** @scrutinizer ignore-call */ 
678
                    Captain()->ID,
Loading history...
678
            'The captain exists for team 1'
679
        );
680
        $this->assertEquals(
681
            $player1->ID,
682
            $team1->getComponent('Captain')->ID,
683
            'The captain exists through the component getter'
684
        );
685
686
        $this->assertEquals(
687
            $team1->Captain()->FirstName,
688
            'Player 1',
689
            'Player 1 is the captain'
690
        );
691
        $this->assertEquals(
692
            $team1->getComponent('Captain')->FirstName,
693
            'Player 1',
694
            'Player 1 is the captain'
695
        );
696
697
        $team1->CaptainID = $player2->ID;
0 ignored issues
show
Bug Best Practice introduced by
The property CaptainID does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
698
        $team1->write();
699
700
        $this->assertEquals($player2->ID, $team1->Captain()->ID);
701
        $this->assertEquals($player2->ID, $team1->getComponent('Captain')->ID);
702
        $this->assertEquals('Player 2', $team1->Captain()->FirstName);
703
        $this->assertEquals('Player 2', $team1->getComponent('Captain')->FirstName);
704
705
706
        // Set the favourite team for fan1
707
        $fan1->setField('FavouriteID', $team1->ID);
708
        $fan1->setField('FavouriteClass', get_class($team1));
709
710
        $this->assertEquals($team1->ID, $fan1->Favourite()->ID, 'The team is assigned to fan 1');
711
        $this->assertInstanceOf(get_class($team1), $fan1->Favourite(), 'The team is assigned to fan 1');
712
        $this->assertEquals(
713
            $team1->ID,
714
            $fan1->getComponent('Favourite')->ID,
715
            'The team exists through the component getter'
716
        );
717
        $this->assertInstanceOf(
718
            get_class($team1),
719
            $fan1->getComponent('Favourite'),
720
            'The team exists through the component getter'
721
        );
722
723
        $this->assertEquals(
724
            $fan1->Favourite()->Title,
725
            'Team 1',
726
            'Team 1 is the favourite'
727
        );
728
        $this->assertEquals(
729
            $fan1->getComponent('Favourite')->Title,
730
            'Team 1',
731
            'Team 1 is the favourite'
732
        );
733
    }
734
735
    /**
736
     * Test has_one used as field getter/setter
737
     */
738
    public function testHasOneAsField()
739
    {
740
        /** @var DataObjectTest\Team $team1 */
741
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
742
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
743
        $captain2 = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
744
745
        // Setter: By RelationID
746
        $team1->CaptainID = $captain1->ID;
0 ignored issues
show
Bug Best Practice introduced by
The property CaptainID does not exist on SilverStripe\ORM\Tests\DataObjectTest\Team. Since you implemented __set, consider adding a @property annotation.
Loading history...
747
        $team1->write();
748
        $this->assertEquals($captain1->ID, $team1->Captain->ID);
0 ignored issues
show
Bug Best Practice introduced by
The property Captain does not exist on SilverStripe\ORM\Tests\DataObjectTest\Team. Since you implemented __get, consider adding a @property annotation.
Loading history...
749
750
        // Setter: New object
751
        $team1->Captain = $captain2;
0 ignored issues
show
Bug Best Practice introduced by
The property Captain does not exist on SilverStripe\ORM\Tests\DataObjectTest\Team. Since you implemented __set, consider adding a @property annotation.
Loading history...
752
        $team1->write();
753
        $this->assertEquals($captain2->ID, $team1->Captain->ID);
754
755
        // Setter: Custom data (required by DataDifferencer)
756
        $team1->Captain = DBField::create_field('HTMLFragment', '<p>No captain</p>');
757
        $this->assertEquals('<p>No captain</p>', $team1->Captain);
758
    }
759
760
    /**
761
     * @todo Extend type change tests (e.g. '0'==NULL)
762
     */
763
    public function testChangedFields()
764
    {
765
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
766
        $obj->FirstName = 'Captain-changed';
0 ignored issues
show
Bug Best Practice introduced by
The property FirstName does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
767
        $obj->IsRetired = true;
0 ignored issues
show
Bug Best Practice introduced by
The property IsRetired does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
768
769
        $this->assertEquals(
770
            $obj->getChangedFields(true, DataObject::CHANGE_STRICT),
771
            array(
772
                'FirstName' => array(
773
                    'before' => 'Captain',
774
                    'after' => 'Captain-changed',
775
                    'level' => DataObject::CHANGE_VALUE
776
                ),
777
                'IsRetired' => array(
778
                    'before' => 1,
779
                    'after' => true,
780
                    'level' => DataObject::CHANGE_STRICT
781
                )
782
            ),
783
            'Changed fields are correctly detected with strict type changes (level=1)'
784
        );
785
786
        $this->assertEquals(
787
            $obj->getChangedFields(true, DataObject::CHANGE_VALUE),
788
            array(
789
                'FirstName' => array(
790
                    'before' => 'Captain',
791
                    'after' => 'Captain-changed',
792
                    'level' => DataObject::CHANGE_VALUE
793
                )
794
            ),
795
            'Changed fields are correctly detected while ignoring type changes (level=2)'
796
        );
797
798
        $newObj = new DataObjectTest\Player();
799
        $newObj->FirstName = "New Player";
800
        $this->assertEquals(
801
            array(
802
                'FirstName' => array(
803
                    'before' => null,
804
                    'after' => 'New Player',
805
                    'level' => DataObject::CHANGE_VALUE
806
                )
807
            ),
808
            $newObj->getChangedFields(true, DataObject::CHANGE_VALUE),
809
            'Initialised fields are correctly detected as full changes'
810
        );
811
    }
812
813
    public function testChangedFieldsWhenRestoringData()
814
    {
815
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
816
        $obj->FirstName = 'Captain-changed';
0 ignored issues
show
Bug Best Practice introduced by
The property FirstName does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
817
        $obj->FirstName = 'Captain';
818
819
        $this->assertEquals(
820
            [],
821
            $obj->getChangedFields(true, DataObject::CHANGE_STRICT)
822
        );
823
    }
824
825
    public function testChangedFieldsAfterWrite()
826
    {
827
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
828
        $obj->FirstName = 'Captain-changed';
0 ignored issues
show
Bug Best Practice introduced by
The property FirstName does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
829
        $obj->write();
830
        $obj->FirstName = 'Captain';
831
832
        $this->assertEquals(
833
            array(
834
                'FirstName' => array(
835
                    'before' => 'Captain-changed',
836
                    'after' => 'Captain',
837
                    'level' => DataObject::CHANGE_VALUE,
838
                ),
839
            ),
840
            $obj->getChangedFields(true, DataObject::CHANGE_VALUE)
841
        );
842
    }
843
844
    public function testForceChangeCantBeCancelledUntilWrite()
845
    {
846
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
847
        $this->assertFalse($obj->isChanged('FirstName'));
848
        $this->assertFalse($obj->isChanged('Surname'));
849
850
        // Force change marks the records as changed
851
        $obj->forceChange();
852
        $this->assertTrue($obj->isChanged('FirstName'));
853
        $this->assertTrue($obj->isChanged('Surname'));
854
855
        // ...but not if we explicitly ask if the value has changed
856
        $this->assertFalse($obj->isChanged('FirstName', DataObject::CHANGE_VALUE));
857
        $this->assertFalse($obj->isChanged('Surname', DataObject::CHANGE_VALUE));
858
859
        // Not overwritten by setting the value to is original value
860
        $obj->FirstName = 'Captain';
0 ignored issues
show
Bug Best Practice introduced by
The property FirstName does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
861
        $this->assertTrue($obj->isChanged('FirstName'));
862
        $this->assertTrue($obj->isChanged('Surname'));
863
864
        // Not overwritten by changing it to something else and back again
865
        $obj->FirstName = 'Captain-changed';
866
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_VALUE));
867
868
        $obj->FirstName = 'Captain';
869
        $this->assertFalse($obj->isChanged('FirstName', DataObject::CHANGE_VALUE));
870
        $this->assertTrue($obj->isChanged('FirstName'));
871
        $this->assertTrue($obj->isChanged('Surname'));
872
873
        // Cleared after write
874
        $obj->write();
875
        $this->assertFalse($obj->isChanged('FirstName'));
876
        $this->assertFalse($obj->isChanged('Surname'));
877
878
        $obj->FirstName = 'Captain';
879
        $this->assertFalse($obj->isChanged('FirstName'));
880
    }
881
882
    /**
883
     * @skipUpgrade
884
     */
885
    public function testIsChanged()
886
    {
887
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
888
        $obj->NonDBField = 'bob';
0 ignored issues
show
Bug Best Practice introduced by
The property NonDBField does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
889
        $obj->FirstName = 'Captain-changed';
0 ignored issues
show
Bug Best Practice introduced by
The property FirstName does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
890
        $obj->IsRetired = true; // type change only, database stores "1"
0 ignored issues
show
Bug Best Practice introduced by
The property IsRetired does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
891
892
        // Now that DB fields are changed, isChanged is true
893
        $this->assertTrue($obj->isChanged('NonDBField'));
894
        $this->assertFalse($obj->isChanged('NonField'));
895
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_STRICT));
896
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_VALUE));
897
        $this->assertTrue($obj->isChanged('IsRetired', DataObject::CHANGE_STRICT));
898
        $this->assertFalse($obj->isChanged('IsRetired', DataObject::CHANGE_VALUE));
899
        $this->assertFalse($obj->isChanged('Email', 1), 'Doesnt change mark unchanged property');
900
        $this->assertFalse($obj->isChanged('Email', 2), 'Doesnt change mark unchanged property');
901
902
        $newObj = new DataObjectTest\Player();
903
        $newObj->FirstName = "New Player";
904
        $this->assertTrue($newObj->isChanged('FirstName', DataObject::CHANGE_STRICT));
905
        $this->assertTrue($newObj->isChanged('FirstName', DataObject::CHANGE_VALUE));
906
        $this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_STRICT));
907
        $this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_VALUE));
908
909
        $newObj->write();
910
        $this->assertFalse($newObj->ischanged());
911
        $this->assertFalse($newObj->isChanged('FirstName', DataObject::CHANGE_STRICT));
912
        $this->assertFalse($newObj->isChanged('FirstName', DataObject::CHANGE_VALUE));
913
        $this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_STRICT));
914
        $this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_VALUE));
915
916
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
917
        $obj->FirstName = null;
918
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_STRICT));
919
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_VALUE));
920
921
        /* Test when there's not field provided */
922
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
923
        $this->assertFalse($obj->isChanged());
924
        $obj->NonDBField = 'new value';
925
        $this->assertFalse($obj->isChanged());
926
        $obj->FirstName = "New Player";
927
        $this->assertTrue($obj->isChanged());
928
929
        $obj->write();
930
        $this->assertFalse($obj->isChanged());
931
    }
932
933
    public function testRandomSort()
934
    {
935
        /* If we perform the same regularly sorted query twice, it should return the same results */
936
        $itemsA = DataObject::get(DataObjectTest\TeamComment::class, "", "ID");
937
        foreach ($itemsA as $item) {
938
            $keysA[] = $item->ID;
939
        }
940
941
        $itemsB = DataObject::get(DataObjectTest\TeamComment::class, "", "ID");
942
        foreach ($itemsB as $item) {
943
            $keysB[] = $item->ID;
944
        }
945
946
        /* Test when there's not field provided */
947
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
948
        $obj->FirstName = "New Player";
0 ignored issues
show
Bug Best Practice introduced by
The property FirstName does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
949
        $this->assertTrue($obj->isChanged());
950
951
        $obj->write();
952
        $this->assertFalse($obj->isChanged());
953
954
        /* If we perform the same random query twice, it shouldn't return the same results */
955
        $itemsA = DataObject::get(DataObjectTest\TeamComment::class, "", DB::get_conn()->random());
956
        $itemsB = DataObject::get(DataObjectTest\TeamComment::class, "", DB::get_conn()->random());
957
        $itemsC = DataObject::get(DataObjectTest\TeamComment::class, "", DB::get_conn()->random());
958
        $itemsD = DataObject::get(DataObjectTest\TeamComment::class, "", DB::get_conn()->random());
959
        foreach ($itemsA as $item) {
960
            $keysA[] = $item->ID;
961
        }
962
        foreach ($itemsB as $item) {
963
            $keysB[] = $item->ID;
964
        }
965
        foreach ($itemsC as $item) {
966
            $keysC[] = $item->ID;
967
        }
968
        foreach ($itemsD as $item) {
969
            $keysD[] = $item->ID;
970
        }
971
972
        // These shouldn't all be the same (run it 4 times to minimise chance of an accidental collision)
973
        // There's about a 1 in a billion chance of an accidental collision
974
        $this->assertTrue($keysA != $keysB || $keysB != $keysC || $keysC != $keysD);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $keysA seems to be defined by a foreach iteration on line 937. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
Comprehensibility Best Practice introduced by
The variable $keysC seems to be defined by a foreach iteration on line 965. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
Comprehensibility Best Practice introduced by
The variable $keysB seems to be defined by a foreach iteration on line 942. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
Comprehensibility Best Practice introduced by
The variable $keysD seems to be defined by a foreach iteration on line 968. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
975
    }
976
977
    public function testWriteSavesToHasOneRelations()
978
    {
979
        /* DataObject::write() should save to a has_one relationship if you set a field called (relname)ID */
980
        $team = new DataObjectTest\Team();
981
        $captainID = $this->idFromFixture(DataObjectTest\Player::class, 'player1');
982
        $team->CaptainID = $captainID;
0 ignored issues
show
Bug Best Practice introduced by
The property CaptainID does not exist on SilverStripe\ORM\Tests\DataObjectTest\Team. Since you implemented __set, consider adding a @property annotation.
Loading history...
983
        $team->write();
984
        $this->assertEquals(
985
            $captainID,
986
            DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $team->ID")->value()
987
        );
988
989
        /* After giving it a value, you should also be able to set it back to null */
990
        $team->CaptainID = '';
991
        $team->write();
992
        $this->assertEquals(
993
            0,
994
            DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $team->ID")->value()
995
        );
996
997
        /* You should also be able to save a blank to it when it's first created */
998
        $team = new DataObjectTest\Team();
999
        $team->CaptainID = '';
1000
        $team->write();
1001
        $this->assertEquals(
1002
            0,
1003
            DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $team->ID")->value()
1004
        );
1005
1006
        /* Ditto for existing records without a value */
1007
        $existingTeam = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1008
        $existingTeam->CaptainID = '';
0 ignored issues
show
Bug Best Practice introduced by
The property CaptainID does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
1009
        $existingTeam->write();
1010
        $this->assertEquals(
1011
            0,
1012
            DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $existingTeam->ID")->value()
1013
        );
1014
    }
1015
1016
    public function testCanAccessHasOneObjectsAsMethods()
1017
    {
1018
        /* If you have a has_one relation 'Captain' on $obj, and you set the $obj->CaptainID = (ID), then the
1019
        * object itself should be accessible as $obj->Captain() */
1020
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1021
        $captainID = $this->idFromFixture(DataObjectTest\Player::class, 'captain1');
1022
1023
        $team->CaptainID = $captainID;
0 ignored issues
show
Bug Best Practice introduced by
The property CaptainID does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
1024
        $this->assertNotNull($team->Captain());
1025
        $this->assertEquals($captainID, $team->Captain()->ID);
1026
1027
        // Test for polymorphic has_one relations
1028
        $fan = $this->objFromFixture(DataObjectTest\Fan::class, 'fan1');
1029
        $fan->FavouriteID = $team->ID;
0 ignored issues
show
Bug Best Practice introduced by
The property FavouriteID does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
1030
        $fan->FavouriteClass = DataObjectTest\Team::class;
0 ignored issues
show
Bug Best Practice introduced by
The property FavouriteClass does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
1031
        $this->assertNotNull($fan->Favourite());
1032
        $this->assertEquals($team->ID, $fan->Favourite()->ID);
1033
        $this->assertInstanceOf(DataObjectTest\Team::class, $fan->Favourite());
1034
    }
1035
1036
    public function testFieldNamesThatMatchMethodNamesWork()
1037
    {
1038
        /* Check that a field name that corresponds to a method on DataObject will still work */
1039
        $obj = new DataObjectTest\Fixture();
1040
        $obj->Data = "value1";
0 ignored issues
show
Bug Best Practice introduced by
The property Data does not exist on SilverStripe\ORM\Tests\DataObjectTest\Fixture. Since you implemented __set, consider adding a @property annotation.
Loading history...
1041
        $obj->DbObject = "value2";
0 ignored issues
show
Bug Best Practice introduced by
The property DbObject does not exist on SilverStripe\ORM\Tests\DataObjectTest\Fixture. Since you implemented __set, consider adding a @property annotation.
Loading history...
1042
        $obj->Duplicate = "value3";
0 ignored issues
show
Bug Best Practice introduced by
The property Duplicate does not exist on SilverStripe\ORM\Tests\DataObjectTest\Fixture. Since you implemented __set, consider adding a @property annotation.
Loading history...
1043
        $obj->write();
1044
1045
        $this->assertNotNull($obj->ID);
1046
        $this->assertEquals(
1047
            'value1',
1048
            DB::query("SELECT \"Data\" FROM \"DataObjectTest_Fixture\" WHERE \"ID\" = $obj->ID")->value()
1049
        );
1050
        $this->assertEquals(
1051
            'value2',
1052
            DB::query("SELECT \"DbObject\" FROM \"DataObjectTest_Fixture\" WHERE \"ID\" = $obj->ID")->value()
1053
        );
1054
        $this->assertEquals(
1055
            'value3',
1056
            DB::query("SELECT \"Duplicate\" FROM \"DataObjectTest_Fixture\" WHERE \"ID\" = $obj->ID")->value()
1057
        );
1058
    }
1059
1060
    /**
1061
     * @todo Re-enable all test cases for field existence after behaviour has been fixed
1062
     */
1063
    public function testFieldExistence()
1064
    {
1065
        $teamInstance = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1066
        $teamSingleton = singleton(DataObjectTest\Team::class);
1067
1068
        $subteamInstance = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
1069
        $schema = DataObject::getSchema();
1070
1071
        /* hasField() singleton checks */
1072
        $this->assertTrue(
1073
            $teamSingleton->hasField('ID'),
1074
            'hasField() finds built-in fields in singletons'
1075
        );
1076
        $this->assertTrue(
1077
            $teamSingleton->hasField('Title'),
1078
            'hasField() finds custom fields in singletons'
1079
        );
1080
1081
        /* hasField() instance checks */
1082
        $this->assertFalse(
1083
            $teamInstance->hasField('NonExistingField'),
1084
            'hasField() doesnt find non-existing fields in instances'
1085
        );
1086
        $this->assertTrue(
1087
            $teamInstance->hasField('ID'),
1088
            'hasField() finds built-in fields in instances'
1089
        );
1090
        $this->assertTrue(
1091
            $teamInstance->hasField('Created'),
1092
            'hasField() finds built-in fields in instances'
1093
        );
1094
        $this->assertTrue(
1095
            $teamInstance->hasField('DatabaseField'),
1096
            'hasField() finds custom fields in instances'
1097
        );
1098
        //$this->assertFalse($teamInstance->hasField('SubclassDatabaseField'),
1099
        //'hasField() doesnt find subclass fields in parentclass instances');
1100
        $this->assertTrue(
1101
            $teamInstance->hasField('DynamicField'),
1102
            'hasField() finds dynamic getters in instances'
1103
        );
1104
        $this->assertTrue(
1105
            $teamInstance->hasField('HasOneRelationshipID'),
1106
            'hasField() finds foreign keys in instances'
1107
        );
1108
        $this->assertTrue(
1109
            $teamInstance->hasField('ExtendedDatabaseField'),
1110
            'hasField() finds extended fields in instances'
1111
        );
1112
        $this->assertTrue(
1113
            $teamInstance->hasField('ExtendedHasOneRelationshipID'),
1114
            'hasField() finds extended foreign keys in instances'
1115
        );
1116
        //$this->assertTrue($teamInstance->hasField('ExtendedDynamicField'),
1117
        //'hasField() includes extended dynamic getters in instances');
1118
1119
        /* hasField() subclass checks */
1120
        $this->assertTrue(
1121
            $subteamInstance->hasField('ID'),
1122
            'hasField() finds built-in fields in subclass instances'
1123
        );
1124
        $this->assertTrue(
1125
            $subteamInstance->hasField('Created'),
1126
            'hasField() finds built-in fields in subclass instances'
1127
        );
1128
        $this->assertTrue(
1129
            $subteamInstance->hasField('DatabaseField'),
1130
            'hasField() finds custom fields in subclass instances'
1131
        );
1132
        $this->assertTrue(
1133
            $subteamInstance->hasField('SubclassDatabaseField'),
1134
            'hasField() finds custom fields in subclass instances'
1135
        );
1136
        $this->assertTrue(
1137
            $subteamInstance->hasField('DynamicField'),
1138
            'hasField() finds dynamic getters in subclass instances'
1139
        );
1140
        $this->assertTrue(
1141
            $subteamInstance->hasField('HasOneRelationshipID'),
1142
            'hasField() finds foreign keys in subclass instances'
1143
        );
1144
        $this->assertTrue(
1145
            $subteamInstance->hasField('ExtendedDatabaseField'),
1146
            'hasField() finds extended fields in subclass instances'
1147
        );
1148
        $this->assertTrue(
1149
            $subteamInstance->hasField('ExtendedHasOneRelationshipID'),
1150
            'hasField() finds extended foreign keys in subclass instances'
1151
        );
1152
1153
        /* hasDatabaseField() singleton checks */
1154
        //$this->assertTrue($teamSingleton->hasDatabaseField('ID'),
1155
        //'hasDatabaseField() finds built-in fields in singletons');
1156
        $this->assertNotEmpty(
1157
            $schema->fieldSpec(DataObjectTest\Team::class, 'Title'),
1158
            'hasDatabaseField() finds custom fields in singletons'
1159
        );
1160
1161
        /* hasDatabaseField() instance checks */
1162
        $this->assertNull(
1163
            $schema->fieldSpec(DataObjectTest\Team::class, 'NonExistingField'),
1164
            'hasDatabaseField() doesnt find non-existing fields in instances'
1165
        );
1166
        //$this->assertNotEmpty($schema->fieldSpec(DataObjectTest_Team::class, 'ID'),
1167
        //'hasDatabaseField() finds built-in fields in instances');
1168
        $this->assertNotEmpty(
1169
            $schema->fieldSpec(DataObjectTest\Team::class, 'Created'),
1170
            'hasDatabaseField() finds built-in fields in instances'
1171
        );
1172
        $this->assertNotEmpty(
1173
            $schema->fieldSpec(DataObjectTest\Team::class, 'DatabaseField'),
1174
            'hasDatabaseField() finds custom fields in instances'
1175
        );
1176
        $this->assertNull(
1177
            $schema->fieldSpec(DataObjectTest\Team::class, 'SubclassDatabaseField'),
1178
            'hasDatabaseField() doesnt find subclass fields in parentclass instances'
1179
        );
1180
        //$this->assertNull($schema->fieldSpec(DataObjectTest_Team::class, 'DynamicField'),
1181
        //'hasDatabaseField() doesnt dynamic getters in instances');
1182
        $this->assertNotEmpty(
1183
            $schema->fieldSpec(DataObjectTest\Team::class, 'HasOneRelationshipID'),
1184
            'hasDatabaseField() finds foreign keys in instances'
1185
        );
1186
        $this->assertNotEmpty(
1187
            $schema->fieldSpec(DataObjectTest\Team::class, 'ExtendedDatabaseField'),
1188
            'hasDatabaseField() finds extended fields in instances'
1189
        );
1190
        $this->assertNotEmpty(
1191
            $schema->fieldSpec(DataObjectTest\Team::class, 'ExtendedHasOneRelationshipID'),
1192
            'hasDatabaseField() finds extended foreign keys in instances'
1193
        );
1194
        $this->assertNull(
1195
            $schema->fieldSpec(DataObjectTest\Team::class, 'ExtendedDynamicField'),
1196
            'hasDatabaseField() doesnt include extended dynamic getters in instances'
1197
        );
1198
1199
        /* hasDatabaseField() subclass checks */
1200
        $this->assertNotEmpty(
1201
            $schema->fieldSpec(DataObjectTest\SubTeam::class, 'DatabaseField'),
1202
            'hasField() finds custom fields in subclass instances'
1203
        );
1204
        $this->assertNotEmpty(
1205
            $schema->fieldSpec(DataObjectTest\SubTeam::class, 'SubclassDatabaseField'),
1206
            'hasField() finds custom fields in subclass instances'
1207
        );
1208
    }
1209
1210
    /**
1211
     * @todo Re-enable all test cases for field inheritance aggregation after behaviour has been fixed
1212
     */
1213
    public function testFieldInheritance()
1214
    {
1215
        $schema = DataObject::getSchema();
1216
1217
        // Test logical fields (including composite)
1218
        $teamSpecifications = $schema->fieldSpecs(DataObjectTest\Team::class);
1219
        $expected = array(
1220
            'ID',
1221
            'ClassName',
1222
            'LastEdited',
1223
            'Created',
1224
            'Title',
1225
            'DatabaseField',
1226
            'ExtendedDatabaseField',
1227
            'CaptainID',
1228
            'FounderID',
1229
            'HasOneRelationshipID',
1230
            'ExtendedHasOneRelationshipID'
1231
        );
1232
        $actual = array_keys($teamSpecifications);
1233
        sort($expected);
1234
        sort($actual);
1235
        $this->assertEquals(
1236
            $expected,
1237
            $actual,
1238
            'fieldSpecifications() contains all fields defined on instance: base, extended and foreign keys'
1239
        );
1240
1241
        $teamFields = $schema->databaseFields(DataObjectTest\Team::class, false);
1242
        $expected = array(
1243
            'ID',
1244
            'ClassName',
1245
            'LastEdited',
1246
            'Created',
1247
            'Title',
1248
            'DatabaseField',
1249
            'ExtendedDatabaseField',
1250
            'CaptainID',
1251
            'FounderID',
1252
            'HasOneRelationshipID',
1253
            'ExtendedHasOneRelationshipID'
1254
        );
1255
        $actual = array_keys($teamFields);
1256
        sort($expected);
1257
        sort($actual);
1258
        $this->assertEquals(
1259
            $expected,
1260
            $actual,
1261
            'databaseFields() contains only fields defined on instance, including base, extended and foreign keys'
1262
        );
1263
1264
        $subteamSpecifications = $schema->fieldSpecs(DataObjectTest\SubTeam::class);
1265
        $expected = array(
1266
            'ID',
1267
            'ClassName',
1268
            'LastEdited',
1269
            'Created',
1270
            'Title',
1271
            'DatabaseField',
1272
            'ExtendedDatabaseField',
1273
            'CaptainID',
1274
            'FounderID',
1275
            'HasOneRelationshipID',
1276
            'ExtendedHasOneRelationshipID',
1277
            'SubclassDatabaseField',
1278
            'ParentTeamID',
1279
        );
1280
        $actual = array_keys($subteamSpecifications);
1281
        sort($expected);
1282
        sort($actual);
1283
        $this->assertEquals(
1284
            $expected,
1285
            $actual,
1286
            'fieldSpecifications() on subclass contains all fields, including base, extended  and foreign keys'
1287
        );
1288
1289
        $subteamFields = $schema->databaseFields(DataObjectTest\SubTeam::class, false);
1290
        $expected = array(
1291
            'ID',
1292
            'SubclassDatabaseField',
1293
            'ParentTeamID',
1294
        );
1295
        $actual = array_keys($subteamFields);
1296
        sort($expected);
1297
        sort($actual);
1298
        $this->assertEquals(
1299
            $expected,
1300
            $actual,
1301
            'databaseFields() on subclass contains only fields defined on instance'
1302
        );
1303
    }
1304
1305
    public function testSearchableFields()
1306
    {
1307
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
1308
        $fields = $player->searchableFields();
1309
        $this->assertArrayHasKey(
1310
            'IsRetired',
1311
            $fields,
1312
            'Fields defined by $searchable_fields static are correctly detected'
1313
        );
1314
        $this->assertArrayHasKey(
1315
            'ShirtNumber',
1316
            $fields,
1317
            'Fields defined by $searchable_fields static are correctly detected'
1318
        );
1319
1320
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1321
        $fields = $team->searchableFields();
1322
        $this->assertArrayHasKey(
1323
            'Title',
1324
            $fields,
1325
            'Fields can be inherited from the $summary_fields static, including methods called on fields'
1326
        );
1327
        $this->assertArrayHasKey(
1328
            'Captain.ShirtNumber',
1329
            $fields,
1330
            'Fields on related objects can be inherited from the $summary_fields static'
1331
        );
1332
        $this->assertArrayHasKey(
1333
            'Captain.FavouriteTeam.Title',
1334
            $fields,
1335
            'Fields on related objects can be inherited from the $summary_fields static'
1336
        );
1337
1338
        $testObj = new DataObjectTest\Fixture();
1339
        $fields = $testObj->searchableFields();
1340
        $this->assertEmpty($fields);
1341
    }
1342
1343
    public function testCastingHelper()
1344
    {
1345
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1346
1347
        $this->assertEquals('Varchar', $team->castingHelper('Title'), 'db field wasn\'t casted correctly');
1348
        $this->assertEquals('HTMLVarchar', $team->castingHelper('DatabaseField'), 'db field wasn\'t casted correctly');
1349
1350
        $sponsor = $team->Sponsors()->first();
0 ignored issues
show
Bug introduced by
The method Sponsors() 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

1350
        $sponsor = $team->/** @scrutinizer ignore-call */ Sponsors()->first();
Loading history...
1351
        $this->assertEquals('Int', $sponsor->castingHelper('SponsorFee'), 'many_many_extraFields not casted correctly');
1352
    }
1353
1354
    public function testSummaryFieldsCustomLabels()
1355
    {
1356
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1357
        $summaryFields = $team->summaryFields();
1358
1359
        $this->assertEquals(
1360
            [
1361
                'Title' => 'Custom Title',
1362
                'Title.UpperCase' => 'Title',
1363
                'Captain.ShirtNumber' => 'Captain\'s shirt number',
1364
                'Captain.FavouriteTeam.Title' => 'Captain\'s favourite team',
1365
            ],
1366
            $summaryFields
1367
        );
1368
    }
1369
1370
    public function testDataObjectUpdate()
1371
    {
1372
        /* update() calls can use the dot syntax to reference has_one relations and other methods that return
1373
        * objects */
1374
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1375
        $team1->CaptainID = $this->idFromFixture(DataObjectTest\Player::class, 'captain1');
0 ignored issues
show
Bug Best Practice introduced by
The property CaptainID does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
1376
1377
        $team1->update(
1378
            array(
1379
                'DatabaseField' => 'Something',
1380
                'Captain.FirstName' => 'Jim',
1381
                'Captain.Email' => '[email protected]',
1382
                'Captain.FavouriteTeam.Title' => 'New and improved team 1',
1383
            )
1384
        );
1385
1386
        /* Test the simple case of updating fields on the object itself */
1387
        $this->assertEquals('Something', $team1->DatabaseField);
0 ignored issues
show
Bug Best Practice introduced by
The property DatabaseField does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1388
1389
        /* Setting Captain.Email and Captain.FirstName will have updated DataObjectTest_Captain.captain1 in
1390
        * the database.  Although update() doesn't usually write, it does write related records automatically. */
1391
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
1392
        $this->assertEquals('Jim', $captain1->FirstName);
0 ignored issues
show
Bug Best Practice introduced by
The property FirstName does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1393
        $this->assertEquals('[email protected]', $captain1->Email);
0 ignored issues
show
Bug Best Practice introduced by
The property Email does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1394
1395
        /* Jim's favourite team is team 1; we need to reload the object to the the change that setting Captain.
1396
        * FavouriteTeam.Title made */
1397
        $reloadedTeam1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1398
        $this->assertEquals('New and improved team 1', $reloadedTeam1->Title);
1399
    }
1400
1401
    public function testDataObjectUpdateNew()
1402
    {
1403
        /* update() calls can use the dot syntax to reference has_one relations and other methods that return
1404
        * objects */
1405
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1406
        $team1->CaptainID = 0;
0 ignored issues
show
Bug Best Practice introduced by
The property CaptainID does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
1407
1408
        $team1->update(
1409
            array(
1410
                'Captain.FirstName' => 'Jim',
1411
                'Captain.FavouriteTeam.Title' => 'New and improved team 1',
1412
            )
1413
        );
1414
        /* Test that the captain ID has been updated */
1415
        $this->assertGreaterThan(0, $team1->CaptainID);
0 ignored issues
show
Bug Best Practice introduced by
The property CaptainID does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1416
1417
        /* Fetch the newly created captain */
1418
        $captain1 = DataObjectTest\Player::get()->byID($team1->CaptainID);
1419
        $this->assertEquals('Jim', $captain1->FirstName);
0 ignored issues
show
Bug Best Practice introduced by
The property FirstName does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1420
1421
        /* Grab the favourite team and make sure it has the correct values */
1422
        $reloadedTeam1 = $captain1->FavouriteTeam();
1423
        $this->assertEquals($reloadedTeam1->ID, $captain1->FavouriteTeamID);
0 ignored issues
show
Bug Best Practice introduced by
The property FavouriteTeamID does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1424
        $this->assertEquals('New and improved team 1', $reloadedTeam1->Title);
1425
    }
1426
1427
1428
    /**
1429
     * @expectedException \SilverStripe\ORM\ValidationException
1430
     */
1431
    public function testWritingInvalidDataObjectThrowsException()
1432
    {
1433
        $validatedObject = new DataObjectTest\ValidatedObject();
1434
        $validatedObject->write();
1435
    }
1436
1437
    public function testWritingValidDataObjectDoesntThrowException()
1438
    {
1439
        $validatedObject = new DataObjectTest\ValidatedObject();
1440
        $validatedObject->Name = "Mr. Jones";
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\Tests\D...ectTest\ValidatedObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
1441
1442
        $validatedObject->write();
1443
        $this->assertTrue($validatedObject->isInDB(), "Validated object was not saved to database");
1444
    }
1445
1446
    public function testSubclassCreation()
1447
    {
1448
        /* Creating a new object of a subclass should set the ClassName field correctly */
1449
        $obj = new DataObjectTest\SubTeam();
1450
        $obj->write();
1451
        $this->assertEquals(
1452
            DataObjectTest\SubTeam::class,
1453
            DB::query("SELECT \"ClassName\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $obj->ID")->value()
1454
        );
1455
    }
1456
1457
    public function testForceInsert()
1458
    {
1459
        /* If you set an ID on an object and pass forceInsert = true, then the object should be correctly created */
1460
        $conn = DB::get_conn();
1461
        if (method_exists($conn, 'allowPrimaryKeyEditing')) {
1462
            $conn->allowPrimaryKeyEditing(DataObjectTest\Team::class, true);
1463
        }
1464
        $obj = new DataObjectTest\SubTeam();
1465
        $obj->ID = 1001;
1466
        $obj->Title = 'asdfasdf';
1467
        $obj->SubclassDatabaseField = 'asdfasdf';
0 ignored issues
show
Bug Best Practice introduced by
The property SubclassDatabaseField does not exist on SilverStripe\ORM\Tests\DataObjectTest\SubTeam. Since you implemented __set, consider adding a @property annotation.
Loading history...
1468
        $obj->write(false, true);
1469
        if (method_exists($conn, 'allowPrimaryKeyEditing')) {
1470
            $conn->allowPrimaryKeyEditing(DataObjectTest\Team::class, false);
1471
        }
1472
1473
        $this->assertEquals(
1474
            DataObjectTest\SubTeam::class,
1475
            DB::query("SELECT \"ClassName\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $obj->ID")->value()
1476
        );
1477
1478
        /* Check that it actually saves to the database with the correct ID */
1479
        $this->assertEquals(
1480
            "1001",
1481
            DB::query(
1482
                "SELECT \"ID\" FROM \"DataObjectTest_SubTeam\" WHERE \"SubclassDatabaseField\" = 'asdfasdf'"
1483
            )->value()
1484
        );
1485
        $this->assertEquals(
1486
            "1001",
1487
            DB::query("SELECT \"ID\" FROM \"DataObjectTest_Team\" WHERE \"Title\" = 'asdfasdf'")->value()
1488
        );
1489
    }
1490
1491
    public function testHasOwnTable()
1492
    {
1493
        $schema = DataObject::getSchema();
1494
        /* Test DataObject::has_own_table() returns true if the object has $has_one or $db values */
1495
        $this->assertTrue($schema->classHasTable(DataObjectTest\Player::class));
1496
        $this->assertTrue($schema->classHasTable(DataObjectTest\Team::class));
1497
        $this->assertTrue($schema->classHasTable(DataObjectTest\Fixture::class));
1498
1499
        /* Root DataObject that always have a table, even if they lack both $db and $has_one */
1500
        $this->assertTrue($schema->classHasTable(DataObjectTest\FieldlessTable::class));
1501
1502
        /* Subclasses without $db or $has_one don't have a table */
1503
        $this->assertFalse($schema->classHasTable(DataObjectTest\FieldlessSubTable::class));
1504
1505
        /* Return false if you don't pass it a subclass of DataObject */
1506
        $this->assertFalse($schema->classHasTable(DataObject::class));
1507
        $this->assertFalse($schema->classHasTable(ViewableData::class));
1508
1509
        /* Invalid class name */
1510
        $this->assertFalse($schema->classHasTable("ThisIsntADataObject"));
1511
    }
1512
1513
    public function testMerge()
1514
    {
1515
        // test right merge of subclasses
1516
        $left = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
1517
        $right = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam2_with_player_relation');
1518
        $leftOrigID = $left->ID;
1519
        $left->merge($right, 'right', false, false);
1520
        $this->assertEquals(
1521
            $left->Title,
1522
            'Subteam 2',
1523
            'merge() with "right" priority overwrites fields with existing values on subclasses'
1524
        );
1525
        $this->assertEquals(
1526
            $left->ID,
1527
            $leftOrigID,
1528
            'merge() with "right" priority doesnt overwrite database ID'
1529
        );
1530
1531
        // test overwriteWithEmpty flag on existing left values
1532
        $left = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam2_with_player_relation');
1533
        $right = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam3_with_empty_fields');
1534
        $left->merge($right, 'right', false, true);
1535
        $this->assertEquals(
1536
            $left->Title,
1537
            'Subteam 3',
1538
            'merge() with $overwriteWithEmpty overwrites non-empty fields on left object'
1539
        );
1540
1541
        // test overwriteWithEmpty flag on empty left values
1542
        $left = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
1543
        // $SubclassDatabaseField is empty on here
1544
        $right = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam2_with_player_relation');
1545
        $left->merge($right, 'right', false, true);
1546
        $this->assertEquals(
1547
            $left->SubclassDatabaseField,
0 ignored issues
show
Bug Best Practice introduced by
The property SubclassDatabaseField does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1548
            null,
1549
            'merge() with $overwriteWithEmpty overwrites empty fields on left object'
1550
        );
1551
1552
        // @todo test "left" priority flag
1553
        // @todo test includeRelations flag
1554
        // @todo test includeRelations in combination with overwriteWithEmpty
1555
        // @todo test has_one relations
1556
        // @todo test has_many and many_many relations
1557
    }
1558
1559
    public function testPopulateDefaults()
1560
    {
1561
        $obj = new DataObjectTest\Fixture();
1562
        $this->assertEquals(
1563
            $obj->MyFieldWithDefault,
0 ignored issues
show
Bug Best Practice introduced by
The property MyFieldWithDefault does not exist on SilverStripe\ORM\Tests\DataObjectTest\Fixture. Since you implemented __get, consider adding a @property annotation.
Loading history...
1564
            'Default Value',
1565
            'Defaults are populated for in-memory object from $defaults array'
1566
        );
1567
1568
        $this->assertEquals(
1569
            $obj->MyFieldWithAltDefault,
1570
            'Default Value',
1571
            'Defaults are populated from overloaded populateDefaults() method'
1572
        );
1573
1574
        // Test populate defaults on subclasses
1575
        $staffObj = new DataObjectTest\Staff();
1576
        $this->assertEquals('Staff', $staffObj->EmploymentType);
0 ignored issues
show
Bug Best Practice introduced by
The property EmploymentType does not exist on SilverStripe\ORM\Tests\DataObjectTest\Staff. Since you implemented __get, consider adding a @property annotation.
Loading history...
1577
1578
        $ceoObj = new DataObjectTest\CEO();
1579
        $this->assertEquals('Staff', $ceoObj->EmploymentType);
0 ignored issues
show
Bug Best Practice introduced by
The property EmploymentType does not exist on SilverStripe\ORM\Tests\DataObjectTest\CEO. Since you implemented __get, consider adding a @property annotation.
Loading history...
1580
    }
1581
1582
    /**
1583
     * @expectedException \InvalidArgumentException
1584
     */
1585
    public function testValidateModelDefinitionsFailsWithArray()
1586
    {
1587
        Config::modify()->merge(DataObjectTest\Team::class, 'has_one', array('NotValid' => array('NoArraysAllowed')));
1588
        DataObject::getSchema()->hasOneComponent(DataObjectTest\Team::class, 'NotValid');
1589
    }
1590
1591
    /**
1592
     * @expectedException \InvalidArgumentException
1593
     */
1594
    public function testValidateModelDefinitionsFailsWithIntKey()
1595
    {
1596
        Config::modify()->set(DataObjectTest\Team::class, 'has_many', array(0 => DataObjectTest\Player::class));
1597
        DataObject::getSchema()->hasManyComponent(DataObjectTest\Team::class, 0);
1598
    }
1599
1600
    /**
1601
     * @expectedException \InvalidArgumentException
1602
     */
1603
    public function testValidateModelDefinitionsFailsWithIntValue()
1604
    {
1605
        Config::modify()->merge(DataObjectTest\Team::class, 'many_many', array('Players' => 12));
1606
        DataObject::getSchema()->manyManyComponent(DataObjectTest\Team::class, 'Players');
1607
    }
1608
1609
    public function testNewClassInstance()
1610
    {
1611
        $dataObject = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1612
        $changedDO = $dataObject->newClassInstance(DataObjectTest\SubTeam::class);
1613
        $changedFields = $changedDO->getChangedFields();
1614
1615
        // Don't write the record, it will reset changed fields
1616
        $this->assertInstanceOf(DataObjectTest\SubTeam::class, $changedDO);
1617
        $this->assertEquals($changedDO->ClassName, DataObjectTest\SubTeam::class);
1618
        $this->assertEquals($changedDO->RecordClassName, DataObjectTest\SubTeam::class);
0 ignored issues
show
Bug Best Practice introduced by
The property RecordClassName does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1619
        $this->assertContains('ClassName', array_keys($changedFields));
1620
        $this->assertEquals($changedFields['ClassName']['before'], DataObjectTest\Team::class);
1621
        $this->assertEquals($changedFields['ClassName']['after'], DataObjectTest\SubTeam::class);
1622
        $this->assertEquals($changedFields['RecordClassName']['before'], DataObjectTest\Team::class);
1623
        $this->assertEquals($changedFields['RecordClassName']['after'], DataObjectTest\SubTeam::class);
1624
1625
        $changedDO->write();
1626
1627
        $this->assertInstanceOf(DataObjectTest\SubTeam::class, $changedDO);
1628
        $this->assertEquals($changedDO->ClassName, DataObjectTest\SubTeam::class);
1629
1630
        // Test invalid classes fail
1631
        $this->expectException(InvalidArgumentException::class);
1632
        $this->expectExceptionMessage('Controller is not a valid subclass of DataObject');
1633
        /**
1634
         * @skipUpgrade
1635
         */
1636
        $dataObject->newClassInstance('Controller');
1637
    }
1638
1639
    public function testMultipleManyManyWithSameClass()
1640
    {
1641
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1642
        $company2 = $this->objFromFixture(DataObjectTest\EquipmentCompany::class, 'equipmentcompany2');
1643
        $sponsors = $team->Sponsors();
1644
        $equipmentSuppliers = $team->EquipmentSuppliers();
0 ignored issues
show
Bug introduced by
The method EquipmentSuppliers() 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

1644
        /** @scrutinizer ignore-call */ 
1645
        $equipmentSuppliers = $team->EquipmentSuppliers();
Loading history...
1645
1646
        // Check that DataObject::many_many() works as expected
1647
        $manyManyComponent = DataObject::getSchema()->manyManyComponent(DataObjectTest\Team::class, 'Sponsors');
1648
        $this->assertEquals(ManyManyList::class, $manyManyComponent['relationClass']);
1649
        $this->assertEquals(
1650
            DataObjectTest\Team::class,
1651
            $manyManyComponent['parentClass'],
1652
            'DataObject::many_many() didn\'t find the correct base class'
1653
        );
1654
        $this->assertEquals(
1655
            DataObjectTest\EquipmentCompany::class,
1656
            $manyManyComponent['childClass'],
1657
            'DataObject::many_many() didn\'t find the correct target class for the relation'
1658
        );
1659
        $this->assertEquals(
1660
            'DataObjectTest_EquipmentCompany_SponsoredTeams',
1661
            $manyManyComponent['join'],
1662
            'DataObject::many_many() didn\'t find the correct relation table'
1663
        );
1664
        $this->assertEquals('DataObjectTest_TeamID', $manyManyComponent['parentField']);
1665
        $this->assertEquals('DataObjectTest_EquipmentCompanyID', $manyManyComponent['childField']);
1666
1667
        // Check that ManyManyList still works
1668
        $this->assertEquals(2, $sponsors->count(), 'Rows are missing from relation');
1669
        $this->assertEquals(1, $equipmentSuppliers->count(), 'Rows are missing from relation');
1670
1671
        // Check everything works when no relation is present
1672
        $teamWithoutSponsor = $this->objFromFixture(DataObjectTest\Team::class, 'team3');
1673
        $this->assertInstanceOf(ManyManyList::class, $teamWithoutSponsor->Sponsors());
1674
        $this->assertEquals(0, $teamWithoutSponsor->Sponsors()->count());
1675
1676
        // Test that belongs_many_many can be infered from with getNonReciprocalComponent
1677
        $this->assertListEquals(
1678
            [
1679
                ['Name' => 'Company corp'],
1680
                ['Name' => 'Team co.'],
1681
            ],
1682
            $team->inferReciprocalComponent(DataObjectTest\EquipmentCompany::class, 'SponsoredTeams')
0 ignored issues
show
Bug introduced by
It seems like $team->inferReciprocalCo...lass, 'SponsoredTeams') can also be of type SilverStripe\ORM\DataObject; however, parameter $list of SilverStripe\Dev\SapphireTest::assertListEquals() does only seem to accept SilverStripe\ORM\SS_List, 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

1682
            /** @scrutinizer ignore-type */ $team->inferReciprocalComponent(DataObjectTest\EquipmentCompany::class, 'SponsoredTeams')
Loading history...
1683
        );
1684
1685
        // Test that many_many can be infered from getNonReciprocalComponent
1686
        $this->assertListEquals(
1687
            [
1688
                ['Title' => 'Team 1'],
1689
                ['Title' => 'Team 2'],
1690
                ['Title' => 'Subteam 1'],
1691
            ],
1692
            $company2->inferReciprocalComponent(DataObjectTest\Team::class, 'Sponsors')
1693
        );
1694
1695
        // Check many_many_extraFields still works
1696
        $equipmentCompany = $this->objFromFixture(DataObjectTest\EquipmentCompany::class, 'equipmentcompany1');
1697
        $equipmentCompany->SponsoredTeams()->add($teamWithoutSponsor, array('SponsorFee' => 1000));
0 ignored issues
show
Bug introduced by
The method SponsoredTeams() 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

1697
        $equipmentCompany->/** @scrutinizer ignore-call */ 
1698
                           SponsoredTeams()->add($teamWithoutSponsor, array('SponsorFee' => 1000));
Loading history...
1698
        $sponsoredTeams = $equipmentCompany->SponsoredTeams();
1699
        $this->assertEquals(
1700
            1000,
1701
            $sponsoredTeams->byID($teamWithoutSponsor->ID)->SponsorFee,
1702
            'Data from many_many_extraFields was not stored/extracted correctly'
1703
        );
1704
1705
        // Check subclasses correctly inherit multiple many_manys
1706
        $subTeam = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
1707
        $this->assertEquals(
1708
            2,
1709
            $subTeam->Sponsors()->count(),
1710
            'Child class did not inherit multiple many_manys'
1711
        );
1712
        $this->assertEquals(
1713
            1,
1714
            $subTeam->EquipmentSuppliers()->count(),
1715
            'Child class did not inherit multiple many_manys'
1716
        );
1717
        // Team 2 has one EquipmentCompany sponsor and one SubEquipmentCompany
1718
        $team2 = $this->objFromFixture(DataObjectTest\Team::class, 'team2');
1719
        $this->assertEquals(
1720
            2,
1721
            $team2->Sponsors()->count(),
1722
            'Child class did not inherit multiple belongs_many_manys'
1723
        );
1724
1725
        // Check many_many_extraFields also works from the belongs_many_many side
1726
        $sponsors = $team2->Sponsors();
1727
        $sponsors->add($equipmentCompany, array('SponsorFee' => 750));
1728
        $this->assertEquals(
1729
            750,
1730
            $sponsors->byID($equipmentCompany->ID)->SponsorFee,
1731
            'Data from many_many_extraFields was not stored/extracted correctly'
1732
        );
1733
1734
        $subEquipmentCompany = $this->objFromFixture(DataObjectTest\SubEquipmentCompany::class, 'subequipmentcompany1');
1735
        $subTeam->Sponsors()->add($subEquipmentCompany, array('SponsorFee' => 1200));
1736
        $this->assertEquals(
1737
            1200,
1738
            $subTeam->Sponsors()->byID($subEquipmentCompany->ID)->SponsorFee,
1739
            'Data from inherited many_many_extraFields was not stored/extracted correctly'
1740
        );
1741
    }
1742
1743
    public function testManyManyExtraFields()
1744
    {
1745
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1746
        $schema = DataObject::getSchema();
1747
1748
        // Get all extra fields
1749
        $teamExtraFields = $team->manyManyExtraFields();
1750
        $this->assertEquals(
1751
            array(
1752
                'Players' => array('Position' => 'Varchar(100)')
1753
            ),
1754
            $teamExtraFields
1755
        );
1756
1757
        // Ensure fields from parent classes are included
1758
        $subTeam = singleton(DataObjectTest\SubTeam::class);
1759
        $teamExtraFields = $subTeam->manyManyExtraFields();
1760
        $this->assertEquals(
1761
            array(
1762
                'Players' => array('Position' => 'Varchar(100)'),
1763
                'FormerPlayers' => array('Position' => 'Varchar(100)')
1764
            ),
1765
            $teamExtraFields
1766
        );
1767
1768
        // Extra fields are immediately available on the Team class (defined in $many_many_extraFields)
1769
        $teamExtraFields = $schema->manyManyExtraFieldsForComponent(DataObjectTest\Team::class, 'Players');
1770
        $this->assertEquals(
1771
            $teamExtraFields,
1772
            array(
1773
                'Position' => 'Varchar(100)'
1774
            )
1775
        );
1776
1777
        // We'll have to go through the relation to get the extra fields on Player
1778
        $playerExtraFields = $schema->manyManyExtraFieldsForComponent(DataObjectTest\Player::class, 'Teams');
1779
        $this->assertEquals(
1780
            $playerExtraFields,
1781
            array(
1782
                'Position' => 'Varchar(100)'
1783
            )
1784
        );
1785
1786
        // Iterate through a many-many relationship and confirm that extra fields are included
1787
        $newTeam = new DataObjectTest\Team();
1788
        $newTeam->Title = "New team";
1789
        $newTeam->write();
1790
        $newTeamID = $newTeam->ID;
1791
1792
        $newPlayer = new DataObjectTest\Player();
1793
        $newPlayer->FirstName = "Sam";
1794
        $newPlayer->Surname = "Minnee";
1795
        $newPlayer->write();
1796
1797
        // The idea of Sam as a prop is essentially humourous.
1798
        $newTeam->Players()->add($newPlayer, array("Position" => "Prop"));
1799
1800
        // Requery and uncache everything
1801
        $newTeam->flushCache();
1802
        $newTeam = DataObject::get_by_id(DataObjectTest\Team::class, $newTeamID);
1803
1804
        // Check that the Position many_many_extraField is extracted.
1805
        $player = $newTeam->Players()->first();
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

1805
        $player = $newTeam->/** @scrutinizer ignore-call */ Players()->first();
Loading history...
1806
        $this->assertEquals('Sam', $player->FirstName);
1807
        $this->assertEquals("Prop", $player->Position);
1808
1809
        // Check that ordering a many-many relation by an aggregate column doesn't fail
1810
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
1811
        $player->Teams()->sort("count(DISTINCT \"DataObjectTest_Team_Players\".\"DataObjectTest_PlayerID\") DESC");
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

1811
        $player->/** @scrutinizer ignore-call */ 
1812
                 Teams()->sort("count(DISTINCT \"DataObjectTest_Team_Players\".\"DataObjectTest_PlayerID\") DESC");
Loading history...
1812
    }
1813
1814
    /**
1815
     * Check that the queries generated for many-many relation queries can have unlimitedRowCount
1816
     * called on them.
1817
     */
1818
    public function testManyManyUnlimitedRowCount()
1819
    {
1820
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
1821
        // TODO: What's going on here?
1822
        $this->assertEquals(2, $player->Teams()->dataQuery()->query()->unlimitedRowCount());
1823
    }
1824
1825
    /**
1826
     * Tests that singular_name() generates sensible defaults.
1827
     */
1828
    public function testSingularName()
1829
    {
1830
        $assertions = array(
1831
            DataObjectTest\Player::class => 'Player',
1832
            DataObjectTest\Team::class => 'Team',
1833
            DataObjectTest\Fixture::class => 'Fixture',
1834
        );
1835
1836
        foreach ($assertions as $class => $expectedSingularName) {
1837
            $this->assertEquals(
1838
                $expectedSingularName,
1839
                singleton($class)->singular_name(),
1840
                "Assert that the singular_name for '$class' is correct."
1841
            );
1842
        }
1843
    }
1844
1845
    /**
1846
     * Tests that plural_name() generates sensible defaults.
1847
     */
1848
    public function testPluralName()
1849
    {
1850
        $assertions = array(
1851
            DataObjectTest\Player::class => 'Players',
1852
            DataObjectTest\Team::class => 'Teams',
1853
            DataObjectTest\Fixture::class => 'Fixtures',
1854
            DataObjectTest\Play::class => 'Plays',
1855
            DataObjectTest\Bogey::class => 'Bogeys',
1856
            DataObjectTest\Ploy::class => 'Ploys',
1857
        );
1858
        i18n::set_locale('en_NZ');
1859
        foreach ($assertions as $class => $expectedPluralName) {
1860
            $this->assertEquals(
1861
                $expectedPluralName,
1862
                DataObject::singleton($class)->plural_name(),
1863
                "Assert that the plural_name for '$class' is correct."
1864
            );
1865
            $this->assertEquals(
1866
                $expectedPluralName,
1867
                DataObject::singleton($class)->i18n_plural_name(),
1868
                "Assert that the i18n_plural_name for '$class' is correct."
1869
            );
1870
        }
1871
    }
1872
1873
    public function testHasDatabaseField()
1874
    {
1875
        $team = singleton(DataObjectTest\Team::class);
1876
        $subteam = singleton(DataObjectTest\SubTeam::class);
1877
1878
        $this->assertTrue(
1879
            $team->hasDatabaseField('Title'),
1880
            "hasOwnDatabaseField() works with \$db fields"
1881
        );
1882
        $this->assertTrue(
1883
            $team->hasDatabaseField('CaptainID'),
1884
            "hasOwnDatabaseField() works with \$has_one fields"
1885
        );
1886
        $this->assertFalse(
1887
            $team->hasDatabaseField('NonExistentField'),
1888
            "hasOwnDatabaseField() doesn't detect non-existend fields"
1889
        );
1890
        $this->assertTrue(
1891
            $team->hasDatabaseField('ExtendedDatabaseField'),
1892
            "hasOwnDatabaseField() works with extended fields"
1893
        );
1894
        $this->assertFalse(
1895
            $team->hasDatabaseField('SubclassDatabaseField'),
1896
            "hasOwnDatabaseField() doesn't pick up fields in subclasses on parent class"
1897
        );
1898
1899
        $this->assertTrue(
1900
            $subteam->hasDatabaseField('SubclassDatabaseField'),
1901
            "hasOwnDatabaseField() picks up fields in subclasses"
1902
        );
1903
    }
1904
1905
    public function testFieldTypes()
1906
    {
1907
        $obj = new DataObjectTest\Fixture();
1908
        $obj->DateField = '1988-01-02';
0 ignored issues
show
Bug Best Practice introduced by
The property DateField does not exist on SilverStripe\ORM\Tests\DataObjectTest\Fixture. Since you implemented __set, consider adding a @property annotation.
Loading history...
1909
        $obj->DatetimeField = '1988-03-04 06:30';
0 ignored issues
show
Bug Best Practice introduced by
The property DatetimeField does not exist on SilverStripe\ORM\Tests\DataObjectTest\Fixture. Since you implemented __set, consider adding a @property annotation.
Loading history...
1910
        $obj->write();
1911
        $obj->flushCache();
1912
1913
        $obj = DataObject::get_by_id(DataObjectTest\Fixture::class, $obj->ID);
1914
        $this->assertEquals('1988-01-02', $obj->DateField);
0 ignored issues
show
Bug Best Practice introduced by
The property DateField does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1915
        $this->assertEquals('1988-03-04 06:30:00', $obj->DatetimeField);
0 ignored issues
show
Bug Best Practice introduced by
The property DatetimeField does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1916
    }
1917
1918
    public function testTwoSubclassesWithTheSameFieldNameWork()
1919
    {
1920
        // Create two objects of different subclasses, setting the values of fields that are
1921
        // defined separately in each subclass
1922
        $obj1 = new DataObjectTest\SubTeam();
1923
        $obj1->SubclassDatabaseField = "obj1";
0 ignored issues
show
Bug Best Practice introduced by
The property SubclassDatabaseField does not exist on SilverStripe\ORM\Tests\DataObjectTest\SubTeam. Since you implemented __set, consider adding a @property annotation.
Loading history...
1924
        $obj2 = new DataObjectTest\OtherSubclassWithSameField();
1925
        $obj2->SubclassDatabaseField = "obj2";
0 ignored issues
show
Bug Best Practice introduced by
The property SubclassDatabaseField does not exist on SilverStripe\ORM\Tests\D...erSubclassWithSameField. Since you implemented __set, consider adding a @property annotation.
Loading history...
1926
1927
        // Write them to the database
1928
        $obj1->write();
1929
        $obj2->write();
1930
1931
        // Check that the values of those fields are properly read from the database
1932
        $values = DataObject::get(
1933
            DataObjectTest\Team::class,
1934
            "\"DataObjectTest_Team\".\"ID\" IN
1935
			($obj1->ID, $obj2->ID)"
1936
        )->column("SubclassDatabaseField");
1937
        $this->assertEquals(array_intersect($values, array('obj1', 'obj2')), $values);
1938
    }
1939
1940
    public function testClassNameSetForNewObjects()
1941
    {
1942
        $d = new DataObjectTest\Player();
1943
        $this->assertEquals(DataObjectTest\Player::class, $d->ClassName);
1944
    }
1945
1946
    public function testHasValue()
1947
    {
1948
        $team = new DataObjectTest\Team();
1949
        $this->assertFalse($team->hasValue('Title', null, false));
1950
        $this->assertFalse($team->hasValue('DatabaseField', null, false));
1951
1952
        $team->Title = 'hasValue';
1953
        $this->assertTrue($team->hasValue('Title', null, false));
1954
        $this->assertFalse($team->hasValue('DatabaseField', null, false));
1955
1956
        $team->Title = '<p></p>';
1957
        $this->assertTrue(
1958
            $team->hasValue('Title', null, false),
1959
            'Test that an empty paragraph is a value for non-HTML fields.'
1960
        );
1961
1962
        $team->DatabaseField = 'hasValue';
1963
        $this->assertTrue($team->hasValue('Title', null, false));
1964
        $this->assertTrue($team->hasValue('DatabaseField', null, false));
1965
    }
1966
1967
    public function testHasMany()
1968
    {
1969
        $company = new DataObjectTest\Company();
1970
1971
        $this->assertEquals(
1972
            array(
1973
                'CurrentStaff' => DataObjectTest\Staff::class,
1974
                'PreviousStaff' => DataObjectTest\Staff::class
1975
            ),
1976
            $company->hasMany(),
1977
            'has_many strips field name data by default.'
1978
        );
1979
1980
        $this->assertEquals(
1981
            DataObjectTest\Staff::class,
1982
            DataObject::getSchema()->hasManyComponent(DataObjectTest\Company::class, 'CurrentStaff'),
1983
            'has_many strips field name data by default on single relationships.'
1984
        );
1985
1986
        $this->assertEquals(
1987
            array(
1988
                'CurrentStaff' => DataObjectTest\Staff::class . '.CurrentCompany',
1989
                'PreviousStaff' => DataObjectTest\Staff::class . '.PreviousCompany'
1990
            ),
1991
            $company->hasMany(false),
1992
            'has_many returns field name data when $classOnly is false.'
1993
        );
1994
1995
        $this->assertEquals(
1996
            DataObjectTest\Staff::class . '.CurrentCompany',
1997
            DataObject::getSchema()->hasManyComponent(DataObjectTest\Company::class, 'CurrentStaff', false),
1998
            'has_many returns field name data on single records when $classOnly is false.'
1999
        );
2000
    }
2001
2002
    public function testGetRemoteJoinField()
2003
    {
2004
        $schema = DataObject::getSchema();
2005
2006
        // Company schema
2007
        $staffJoinField = $schema->getRemoteJoinField(
2008
            DataObjectTest\Company::class,
2009
            'CurrentStaff',
2010
            'has_many',
2011
            $polymorphic
2012
        );
2013
        $this->assertEquals('CurrentCompanyID', $staffJoinField);
2014
        $this->assertFalse($polymorphic, 'DataObjectTest_Company->CurrentStaff is not polymorphic');
2015
        $previousStaffJoinField = $schema->getRemoteJoinField(
2016
            DataObjectTest\Company::class,
2017
            'PreviousStaff',
2018
            'has_many',
2019
            $polymorphic
2020
        );
2021
        $this->assertEquals('PreviousCompanyID', $previousStaffJoinField);
2022
        $this->assertFalse($polymorphic, 'DataObjectTest_Company->PreviousStaff is not polymorphic');
2023
2024
        // CEO Schema
2025
        $this->assertEquals(
2026
            'CEOID',
2027
            $schema->getRemoteJoinField(
2028
                DataObjectTest\CEO::class,
2029
                'Company',
2030
                'belongs_to',
2031
                $polymorphic
2032
            )
2033
        );
2034
        $this->assertFalse($polymorphic, 'DataObjectTest_CEO->Company is not polymorphic');
2035
        $this->assertEquals(
2036
            'PreviousCEOID',
2037
            $schema->getRemoteJoinField(
2038
                DataObjectTest\CEO::class,
2039
                'PreviousCompany',
2040
                'belongs_to',
2041
                $polymorphic
2042
            )
2043
        );
2044
        $this->assertFalse($polymorphic, 'DataObjectTest_CEO->PreviousCompany is not polymorphic');
2045
2046
        // Team schema
2047
        $this->assertEquals(
2048
            'Favourite',
2049
            $schema->getRemoteJoinField(
2050
                DataObjectTest\Team::class,
2051
                'Fans',
2052
                'has_many',
2053
                $polymorphic
2054
            )
2055
        );
2056
        $this->assertTrue($polymorphic, 'DataObjectTest_Team->Fans is polymorphic');
2057
        $this->assertEquals(
2058
            'TeamID',
2059
            $schema->getRemoteJoinField(
2060
                DataObjectTest\Team::class,
2061
                'Comments',
2062
                'has_many',
2063
                $polymorphic
2064
            )
2065
        );
2066
        $this->assertFalse($polymorphic, 'DataObjectTest_Team->Comments is not polymorphic');
2067
    }
2068
2069
    public function testBelongsTo()
2070
    {
2071
        $company = new DataObjectTest\Company();
2072
        $ceo = new DataObjectTest\CEO();
2073
2074
        $company->Name = 'New Company';
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\Tests\DataObjectTest\Company. Since you implemented __set, consider adding a @property annotation.
Loading history...
2075
        $company->write();
2076
        $ceo->write();
2077
2078
        // Test belongs_to assignment
2079
        $company->CEOID = $ceo->ID;
0 ignored issues
show
Bug Best Practice introduced by
The property CEOID does not exist on SilverStripe\ORM\Tests\DataObjectTest\Company. Since you implemented __set, consider adding a @property annotation.
Loading history...
2080
        $company->write();
2081
2082
        $this->assertEquals($company->ID, $ceo->Company()->ID, 'belongs_to returns the right results.');
0 ignored issues
show
Bug introduced by
The method Company() does not exist on SilverStripe\ORM\Tests\DataObjectTest\CEO. 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

2082
        $this->assertEquals($company->ID, $ceo->/** @scrutinizer ignore-call */ Company()->ID, 'belongs_to returns the right results.');
Loading history...
2083
2084
        // Test belongs_to can be infered via getNonReciprocalComponent
2085
        // Note: Will be returned as has_many since the belongs_to is ignored.
2086
        $this->assertListEquals(
2087
            [['Name' => 'New Company']],
2088
            $ceo->inferReciprocalComponent(DataObjectTest\Company::class, 'CEO')
0 ignored issues
show
Bug introduced by
It seems like $ceo->inferReciprocalCom...\Company::class, 'CEO') can also be of type SilverStripe\ORM\DataObject; however, parameter $list of SilverStripe\Dev\SapphireTest::assertListEquals() does only seem to accept SilverStripe\ORM\SS_List, 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

2088
            /** @scrutinizer ignore-type */ $ceo->inferReciprocalComponent(DataObjectTest\Company::class, 'CEO')
Loading history...
2089
        );
2090
2091
        // Test has_one to a belongs_to can be infered via getNonReciprocalComponent
2092
        $this->assertEquals(
2093
            $ceo->ID,
2094
            $company->inferReciprocalComponent(DataObjectTest\CEO::class, 'Company')->ID
0 ignored issues
show
Bug Best Practice introduced by
The property ID does not exist on SilverStripe\ORM\DataList. Since you implemented __get, consider adding a @property annotation.
Loading history...
2095
        );
2096
2097
        // Test automatic creation of class where no assigment exists
2098
        $ceo = new DataObjectTest\CEO();
2099
        $ceo->write();
2100
2101
        $this->assertTrue(
2102
            $ceo->Company() instanceof DataObjectTest\Company,
2103
            'DataObjects across belongs_to relations are automatically created.'
2104
        );
2105
        $this->assertEquals($ceo->ID, $ceo->Company()->CEOID, 'Remote IDs are automatically set.');
2106
2107
        // Write object with components
2108
        $ceo->Name = 'Edward Scissorhands';
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\Tests\DataObjectTest\CEO. Since you implemented __set, consider adding a @property annotation.
Loading history...
2109
        $ceo->write(false, false, false, true);
2110
        $this->assertTrue($ceo->Company()->isInDB(), 'write() writes belongs_to components to the database.');
2111
2112
        $newCEO = DataObject::get_by_id(DataObjectTest\CEO::class, $ceo->ID);
2113
        $this->assertEquals(
2114
            $ceo->Company()->ID,
2115
            $newCEO->Company()->ID,
0 ignored issues
show
Bug introduced by
The method Company() 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

2115
            $newCEO->/** @scrutinizer ignore-call */ 
2116
                     Company()->ID,
Loading history...
2116
            'belongs_to can be retrieved from the database.'
2117
        );
2118
    }
2119
2120
    public function testBelongsToPolymorphic()
2121
    {
2122
        $company = new DataObjectTest\Company();
2123
        $ceo = new DataObjectTest\CEO();
2124
2125
        $company->write();
2126
        $ceo->write();
2127
2128
        // Test belongs_to assignment
2129
        $company->OwnerID = $ceo->ID;
0 ignored issues
show
Bug Best Practice introduced by
The property OwnerID does not exist on SilverStripe\ORM\Tests\DataObjectTest\Company. Since you implemented __set, consider adding a @property annotation.
Loading history...
2130
        $company->OwnerClass = DataObjectTest\CEO::class;
0 ignored issues
show
Bug Best Practice introduced by
The property OwnerClass does not exist on SilverStripe\ORM\Tests\DataObjectTest\Company. Since you implemented __set, consider adding a @property annotation.
Loading history...
2131
        $company->write();
2132
2133
        $this->assertEquals($company->ID, $ceo->CompanyOwned()->ID, 'belongs_to returns the right results.');
0 ignored issues
show
Bug introduced by
The method CompanyOwned() does not exist on SilverStripe\ORM\Tests\DataObjectTest\CEO. 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

2133
        $this->assertEquals($company->ID, $ceo->/** @scrutinizer ignore-call */ CompanyOwned()->ID, 'belongs_to returns the right results.');
Loading history...
2134
        $this->assertInstanceOf(
2135
            DataObjectTest\Company::class,
2136
            $ceo->CompanyOwned(),
2137
            'belongs_to returns the right results.'
2138
        );
2139
2140
        // Test automatic creation of class where no assigment exists
2141
        $ceo = new DataObjectTest\CEO();
2142
        $ceo->write();
2143
2144
        $this->assertTrue(
2145
            $ceo->CompanyOwned() instanceof DataObjectTest\Company,
2146
            'DataObjects across polymorphic belongs_to relations are automatically created.'
2147
        );
2148
        $this->assertEquals($ceo->ID, $ceo->CompanyOwned()->OwnerID, 'Remote IDs are automatically set.');
2149
        $this->assertInstanceOf($ceo->CompanyOwned()->OwnerClass, $ceo, 'Remote class is automatically  set');
2150
2151
        // Write object with components
2152
        $ceo->write(false, false, false, true);
2153
        $this->assertTrue($ceo->CompanyOwned()->isInDB(), 'write() writes belongs_to components to the database.');
2154
2155
        $newCEO = DataObject::get_by_id(DataObjectTest\CEO::class, $ceo->ID);
2156
        $this->assertEquals(
2157
            $ceo->CompanyOwned()->ID,
2158
            $newCEO->CompanyOwned()->ID,
0 ignored issues
show
Bug introduced by
The method CompanyOwned() 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

2158
            $newCEO->/** @scrutinizer ignore-call */ 
2159
                     CompanyOwned()->ID,
Loading history...
2159
            'polymorphic belongs_to can be retrieved from the database.'
2160
        );
2161
    }
2162
2163
    /**
2164
     * @expectedException LogicException
2165
     */
2166
    public function testInvalidate()
2167
    {
2168
        $do = new DataObjectTest\Fixture();
2169
        $do->write();
2170
2171
        $do->delete();
2172
2173
        $do->delete(); // Prohibit invalid object manipulation
2174
        $do->write();
2175
        $do->duplicate();
2176
    }
2177
2178
    public function testToMap()
2179
    {
2180
        $obj = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
2181
2182
        $map = $obj->toMap();
2183
2184
        $this->assertArrayHasKey('ID', $map, 'Contains base fields');
2185
        $this->assertArrayHasKey('Title', $map, 'Contains fields from parent class');
2186
        $this->assertArrayHasKey('SubclassDatabaseField', $map, 'Contains fields from concrete class');
2187
2188
        $this->assertEquals(
2189
            $obj->ID,
2190
            $map['ID'],
2191
            'Contains values from base fields'
2192
        );
2193
        $this->assertEquals(
2194
            $obj->Title,
2195
            $map['Title'],
2196
            'Contains values from parent class fields'
2197
        );
2198
        $this->assertEquals(
2199
            $obj->SubclassDatabaseField,
0 ignored issues
show
Bug Best Practice introduced by
The property SubclassDatabaseField does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
2200
            $map['SubclassDatabaseField'],
2201
            'Contains values from concrete class fields'
2202
        );
2203
2204
        $newObj = new DataObjectTest\SubTeam();
0 ignored issues
show
Unused Code introduced by
The assignment to $newObj is dead and can be removed.
Loading history...
2205
        $this->assertArrayHasKey('Title', $map, 'Contains null fields');
2206
    }
2207
2208
    public function testIsEmpty()
2209
    {
2210
        $objEmpty = new DataObjectTest\Team();
2211
        $this->assertTrue($objEmpty->isEmpty(), 'New instance without populated defaults is empty');
2212
2213
        $objEmpty->Title = '0'; //
2214
        $this->assertFalse($objEmpty->isEmpty(), 'Zero value in attribute considered non-empty');
2215
    }
2216
2217
    public function testRelField()
2218
    {
2219
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
2220
        // Test traversal of a single has_one
2221
        $this->assertEquals("Team 1", $captain1->relField('FavouriteTeam.Title'));
2222
        // Test direct field access
2223
        $this->assertEquals("Captain", $captain1->relField('FirstName'));
2224
2225
        // Test empty link
2226
        $captain2 = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
2227
        $this->assertEmpty($captain2->relField('FavouriteTeam.Title'));
2228
        $this->assertNull($captain2->relField('FavouriteTeam.ReturnsNull'));
2229
        $this->assertNull($captain2->relField('FavouriteTeam.ReturnsNull.Title'));
2230
2231
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
2232
        // Test that we can traverse more than once, and that arbitrary methods are okay
2233
        $this->assertEquals("Team 1", $player->relField('Teams.First.Title'));
2234
2235
        $newPlayer = new DataObjectTest\Player();
2236
        $this->assertNull($newPlayer->relField('Teams.First.Title'));
2237
2238
        // Test that relField works on db field manipulations
2239
        $comment = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment3');
2240
        $this->assertEquals("PHIL IS A UNIQUE GUY, AND COMMENTS ON TEAM2", $comment->relField('Comment.UpperCase'));
2241
2242
        // relField throws exception on invalid properties
2243
        $this->expectException(LogicException::class);
2244
        $this->expectExceptionMessage("Not is not a relation/field on " . DataObjectTest\TeamComment::class);
2245
        $comment->relField('Not.A.Field');
2246
    }
2247
2248
    public function testRelObject()
2249
    {
2250
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
2251
2252
        // Test traversal of a single has_one
2253
        $this->assertInstanceOf(DBVarchar::class, $captain1->relObject('FavouriteTeam.Title'));
2254
        $this->assertEquals("Team 1", $captain1->relObject('FavouriteTeam.Title')->getValue());
2255
2256
        // Test empty link
2257
        $captain2 = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
2258
        $this->assertEmpty($captain2->relObject('FavouriteTeam.Title')->getValue());
2259
        $this->assertNull($captain2->relObject('FavouriteTeam.ReturnsNull.Title'));
2260
2261
        // Test direct field access
2262
        $this->assertInstanceOf(DBBoolean::class, $captain1->relObject('IsRetired'));
2263
        $this->assertEquals(1, $captain1->relObject('IsRetired')->getValue());
2264
2265
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
2266
        // Test that we can traverse more than once, and that arbitrary methods are okay
2267
        $this->assertInstanceOf(DBVarchar::class, $player->relObject('Teams.First.Title'));
2268
        $this->assertEquals("Team 1", $player->relObject('Teams.First.Title')->getValue());
2269
2270
        // relObject throws exception on invalid properties
2271
        $this->expectException(LogicException::class);
2272
        $this->expectExceptionMessage("Not is not a relation/field on " . DataObjectTest\Player::class);
2273
        $player->relObject('Not.A.Field');
2274
    }
2275
2276
    public function testLateStaticBindingStyle()
2277
    {
2278
        // Confirm that DataObjectTest_Player::get() operates as excepted
2279
        $this->assertEquals(4, DataObjectTest\Player::get()->count());
2280
        $this->assertInstanceOf(DataObjectTest\Player::class, DataObjectTest\Player::get()->first());
2281
2282
        // You can't pass arguments to LSB syntax - use the DataList methods instead.
2283
        $this->expectException(InvalidArgumentException::class);
2284
2285
        DataObjectTest\Player::get(null, "\"ID\" = 1");
2286
    }
2287
2288
    /**
2289
     * @expectedException \InvalidArgumentException
2290
     */
2291
    public function testBrokenLateStaticBindingStyle()
2292
    {
2293
        // If you call DataObject::get() you have to pass a first argument
2294
        DataObject::get();
2295
    }
2296
2297
    public function testBigIntField()
2298
    {
2299
        $staff = new DataObjectTest\Staff();
2300
        $staff->Salary = PHP_INT_MAX;
0 ignored issues
show
Bug Best Practice introduced by
The property Salary does not exist on SilverStripe\ORM\Tests\DataObjectTest\Staff. Since you implemented __set, consider adding a @property annotation.
Loading history...
2301
        $staff->write();
2302
        $this->assertEquals(PHP_INT_MAX, DataObjectTest\Staff::get()->byID($staff->ID)->Salary);
0 ignored issues
show
Bug Best Practice introduced by
The property Salary does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
2303
    }
2304
2305
    public function testGetOneMissingValueReturnsNull()
2306
    {
2307
2308
        // Test that missing values return null
2309
        $this->assertEquals(null, DataObject::get_one(
2310
            DataObjectTest\TeamComment::class,
2311
            ['"DataObjectTest_TeamComment"."Name"' => 'does not exists']
2312
        ));
2313
    }
2314
2315
    /**
2316
     * Test that fields of a subclass can be used to filter a base class query.
2317
     * Most commonly appears in CMS, when filtering SiteTree queries by Page columns
2318
     * https://github.com/silverstripe/silverstripe-framework/issues/1683
2319
     */
2320
    public function testFilterBySubclassFields()
2321
    {
2322
        $subteams = DataObjectTest\Team::get()->filter('SubclassDatabaseField', 'Subclassed 1');
2323
        echo $subteams->dataQuery()->sql();
2324
        $this->assertEquals(1, $subteams->count());
2325
        $this->assertEquals(
2326
            $this->idFromFixture(DataObjectTest\SubTeam::class, 'subteam1'),
2327
            $subteams->First()->ID
2328
        );
2329
    }
2330
}
2331