Passed
Pull Request — 4.3 (#9221)
by Maxime
06:54
created

DataObjectTest::testShallowRecursiveWrite()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 41
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

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

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

472
        $this->assertEquals($team1ID, $captain1->/** @scrutinizer ignore-call */ FavouriteTeam()->ID);
Loading history...
473
474
        // Test that getNonReciprocalComponent can find has_one from the has_many end
475
        $this->assertEquals(
476
            $team1ID,
477
            $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...
478
        );
479
480
        // Check entity with polymorphic has-one
481
        $fan1 = $this->objFromFixture(DataObjectTest\Fan::class, "fan1");
482
        $this->assertTrue((bool)$fan1->hasValue('Favourite'));
483
484
        // There will be fields named (relname)ID and (relname)Class for polymorphic
485
        // entities
486
        $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...
487
        $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...
488
489
        // There will be a method called $obj->relname() that returns the object itself
490
        $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

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

575
        $this->assertTrue($team1->/** @scrutinizer ignore-call */ Comments()->count() == 2);
Loading history...
576
577
        $team1Comments = [
578
            ['Comment' => 'This is a team comment by Joe'],
579
            ['Comment' => 'This is a team comment by Bob'],
580
        ];
581
582
        // Test the IDs on the DataObjects are set correctly
583
        $this->assertListEquals($team1Comments, $team1->Comments());
584
585
        // Test that has_many can be infered from the has_one via getNonReciprocalComponent
586
        $this->assertListEquals(
587
            $team1Comments,
588
            $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

588
            /** @scrutinizer ignore-type */ $team1->inferReciprocalComponent(DataObjectTest\TeamComment::class, 'Team')
Loading history...
589
        );
590
591
        // Test that we can add and remove items that already exist in the database
592
        $newComment = new DataObjectTest\TeamComment();
593
        $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...
594
        $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...
595
        $newComment->write();
596
        $team1->Comments()->add($newComment);
597
        $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...
598
599
        $comment1 = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment1');
600
        $comment2 = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment2');
601
        $team1->Comments()->remove($comment2);
602
603
        $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

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

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

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

1287
        $sponsor = $team->/** @scrutinizer ignore-call */ Sponsors()->first();
Loading history...
1288
        $this->assertEquals('Int', $sponsor->castingHelper('SponsorFee'), 'many_many_extraFields not casted correctly');
1289
    }
1290
1291
    public function testSummaryFieldsCustomLabels()
1292
    {
1293
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1294
        $summaryFields = $team->summaryFields();
1295
1296
        $this->assertEquals(
1297
            [
1298
                'Title' => 'Custom Title',
1299
                'Title.UpperCase' => 'Title',
1300
                'Captain.ShirtNumber' => 'Captain\'s shirt number',
1301
                'Captain.FavouriteTeam.Title' => 'Captain\'s favourite team',
1302
            ],
1303
            $summaryFields
1304
        );
1305
    }
1306
1307
    public function testDataObjectUpdate()
1308
    {
1309
        /* update() calls can use the dot syntax to reference has_one relations and other methods that return
1310
        * objects */
1311
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1312
        $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...
1313
1314
        $team1->update(
1315
            array(
1316
                'DatabaseField' => 'Something',
1317
                'Captain.FirstName' => 'Jim',
1318
                'Captain.Email' => '[email protected]',
1319
                'Captain.FavouriteTeam.Title' => 'New and improved team 1',
1320
            )
1321
        );
1322
1323
        /* Test the simple case of updating fields on the object itself */
1324
        $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...
1325
1326
        /* Setting Captain.Email and Captain.FirstName will have updated DataObjectTest_Captain.captain1 in
1327
        * the database.  Although update() doesn't usually write, it does write related records automatically. */
1328
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
1329
        $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...
1330
        $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...
1331
1332
        /* Jim's favourite team is team 1; we need to reload the object to the the change that setting Captain.
1333
        * FavouriteTeam.Title made */
1334
        $reloadedTeam1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1335
        $this->assertEquals('New and improved team 1', $reloadedTeam1->Title);
1336
    }
1337
1338
    public function testDataObjectUpdateNew()
1339
    {
1340
        /* update() calls can use the dot syntax to reference has_one relations and other methods that return
1341
        * objects */
1342
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1343
        $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...
1344
1345
        $team1->update(
1346
            array(
1347
                'Captain.FirstName' => 'Jim',
1348
                'Captain.FavouriteTeam.Title' => 'New and improved team 1',
1349
            )
1350
        );
1351
        /* Test that the captain ID has been updated */
1352
        $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...
1353
1354
        /* Fetch the newly created captain */
1355
        $captain1 = DataObjectTest\Player::get()->byID($team1->CaptainID);
1356
        $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...
1357
1358
        /* Grab the favourite team and make sure it has the correct values */
1359
        $reloadedTeam1 = $captain1->FavouriteTeam();
1360
        $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...
1361
        $this->assertEquals('New and improved team 1', $reloadedTeam1->Title);
1362
    }
1363
1364
1365
    /**
1366
     * @expectedException \SilverStripe\ORM\ValidationException
1367
     */
1368
    public function testWritingInvalidDataObjectThrowsException()
1369
    {
1370
        $validatedObject = new DataObjectTest\ValidatedObject();
1371
        $validatedObject->write();
1372
    }
1373
1374
    public function testWritingValidDataObjectDoesntThrowException()
1375
    {
1376
        $validatedObject = new DataObjectTest\ValidatedObject();
1377
        $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...
1378
1379
        $validatedObject->write();
1380
        $this->assertTrue($validatedObject->isInDB(), "Validated object was not saved to database");
1381
    }
1382
1383
    public function testSubclassCreation()
1384
    {
1385
        /* Creating a new object of a subclass should set the ClassName field correctly */
1386
        $obj = new DataObjectTest\SubTeam();
1387
        $obj->write();
1388
        $this->assertEquals(
1389
            DataObjectTest\SubTeam::class,
1390
            DB::query("SELECT \"ClassName\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $obj->ID")->value()
1391
        );
1392
    }
1393
1394
    public function testForceInsert()
1395
    {
1396
        /* If you set an ID on an object and pass forceInsert = true, then the object should be correctly created */
1397
        $conn = DB::get_conn();
1398
        if (method_exists($conn, 'allowPrimaryKeyEditing')) {
1399
            $conn->allowPrimaryKeyEditing(DataObjectTest\Team::class, true);
1400
        }
1401
        $obj = new DataObjectTest\SubTeam();
1402
        $obj->ID = 1001;
1403
        $obj->Title = 'asdfasdf';
1404
        $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...
1405
        $obj->write(false, true);
1406
        if (method_exists($conn, 'allowPrimaryKeyEditing')) {
1407
            $conn->allowPrimaryKeyEditing(DataObjectTest\Team::class, false);
1408
        }
1409
1410
        $this->assertEquals(
1411
            DataObjectTest\SubTeam::class,
1412
            DB::query("SELECT \"ClassName\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $obj->ID")->value()
1413
        );
1414
1415
        /* Check that it actually saves to the database with the correct ID */
1416
        $this->assertEquals(
1417
            "1001",
1418
            DB::query(
1419
                "SELECT \"ID\" FROM \"DataObjectTest_SubTeam\" WHERE \"SubclassDatabaseField\" = 'asdfasdf'"
1420
            )->value()
1421
        );
1422
        $this->assertEquals(
1423
            "1001",
1424
            DB::query("SELECT \"ID\" FROM \"DataObjectTest_Team\" WHERE \"Title\" = 'asdfasdf'")->value()
1425
        );
1426
    }
1427
1428
    public function testHasOwnTable()
1429
    {
1430
        $schema = DataObject::getSchema();
1431
        /* Test DataObject::has_own_table() returns true if the object has $has_one or $db values */
1432
        $this->assertTrue($schema->classHasTable(DataObjectTest\Player::class));
1433
        $this->assertTrue($schema->classHasTable(DataObjectTest\Team::class));
1434
        $this->assertTrue($schema->classHasTable(DataObjectTest\Fixture::class));
1435
1436
        /* Root DataObject that always have a table, even if they lack both $db and $has_one */
1437
        $this->assertTrue($schema->classHasTable(DataObjectTest\FieldlessTable::class));
1438
1439
        /* Subclasses without $db or $has_one don't have a table */
1440
        $this->assertFalse($schema->classHasTable(DataObjectTest\FieldlessSubTable::class));
1441
1442
        /* Return false if you don't pass it a subclass of DataObject */
1443
        $this->assertFalse($schema->classHasTable(DataObject::class));
1444
        $this->assertFalse($schema->classHasTable(ViewableData::class));
1445
1446
        /* Invalid class name */
1447
        $this->assertFalse($schema->classHasTable("ThisIsntADataObject"));
1448
    }
1449
1450
    public function testMerge()
1451
    {
1452
        // test right merge of subclasses
1453
        $left = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
1454
        $right = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam2_with_player_relation');
1455
        $leftOrigID = $left->ID;
1456
        $left->merge($right, 'right', false, false);
1457
        $this->assertEquals(
1458
            $left->Title,
1459
            'Subteam 2',
1460
            'merge() with "right" priority overwrites fields with existing values on subclasses'
1461
        );
1462
        $this->assertEquals(
1463
            $left->ID,
1464
            $leftOrigID,
1465
            'merge() with "right" priority doesnt overwrite database ID'
1466
        );
1467
1468
        // test overwriteWithEmpty flag on existing left values
1469
        $left = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam2_with_player_relation');
1470
        $right = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam3_with_empty_fields');
1471
        $left->merge($right, 'right', false, true);
1472
        $this->assertEquals(
1473
            $left->Title,
1474
            'Subteam 3',
1475
            'merge() with $overwriteWithEmpty overwrites non-empty fields on left object'
1476
        );
1477
1478
        // test overwriteWithEmpty flag on empty left values
1479
        $left = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
1480
        // $SubclassDatabaseField is empty on here
1481
        $right = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam2_with_player_relation');
1482
        $left->merge($right, 'right', false, true);
1483
        $this->assertEquals(
1484
            $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...
1485
            null,
1486
            'merge() with $overwriteWithEmpty overwrites empty fields on left object'
1487
        );
1488
1489
        // @todo test "left" priority flag
1490
        // @todo test includeRelations flag
1491
        // @todo test includeRelations in combination with overwriteWithEmpty
1492
        // @todo test has_one relations
1493
        // @todo test has_many and many_many relations
1494
    }
1495
1496
    public function testPopulateDefaults()
1497
    {
1498
        $obj = new DataObjectTest\Fixture();
1499
        $this->assertEquals(
1500
            $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...
1501
            'Default Value',
1502
            'Defaults are populated for in-memory object from $defaults array'
1503
        );
1504
1505
        $this->assertEquals(
1506
            $obj->MyFieldWithAltDefault,
1507
            'Default Value',
1508
            'Defaults are populated from overloaded populateDefaults() method'
1509
        );
1510
    }
1511
1512
    /**
1513
     * @expectedException \InvalidArgumentException
1514
     */
1515
    public function testValidateModelDefinitionsFailsWithArray()
1516
    {
1517
        Config::modify()->merge(DataObjectTest\Team::class, 'has_one', array('NotValid' => array('NoArraysAllowed')));
1518
        DataObject::getSchema()->hasOneComponent(DataObjectTest\Team::class, 'NotValid');
1519
    }
1520
1521
    /**
1522
     * @expectedException \InvalidArgumentException
1523
     */
1524
    public function testValidateModelDefinitionsFailsWithIntKey()
1525
    {
1526
        Config::modify()->set(DataObjectTest\Team::class, 'has_many', array(0 => DataObjectTest\Player::class));
1527
        DataObject::getSchema()->hasManyComponent(DataObjectTest\Team::class, 0);
1528
    }
1529
1530
    /**
1531
     * @expectedException \InvalidArgumentException
1532
     */
1533
    public function testValidateModelDefinitionsFailsWithIntValue()
1534
    {
1535
        Config::modify()->merge(DataObjectTest\Team::class, 'many_many', array('Players' => 12));
1536
        DataObject::getSchema()->manyManyComponent(DataObjectTest\Team::class, 'Players');
1537
    }
1538
1539
    public function testNewClassInstance()
1540
    {
1541
        $dataObject = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1542
        $changedDO = $dataObject->newClassInstance(DataObjectTest\SubTeam::class);
1543
        $changedFields = $changedDO->getChangedFields();
1544
1545
        // Don't write the record, it will reset changed fields
1546
        $this->assertInstanceOf(DataObjectTest\SubTeam::class, $changedDO);
1547
        $this->assertEquals($changedDO->ClassName, DataObjectTest\SubTeam::class);
1548
        $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...
1549
        $this->assertContains('ClassName', array_keys($changedFields));
1550
        $this->assertEquals($changedFields['ClassName']['before'], DataObjectTest\Team::class);
1551
        $this->assertEquals($changedFields['ClassName']['after'], DataObjectTest\SubTeam::class);
1552
        $this->assertEquals($changedFields['RecordClassName']['before'], DataObjectTest\Team::class);
1553
        $this->assertEquals($changedFields['RecordClassName']['after'], DataObjectTest\SubTeam::class);
1554
1555
        $changedDO->write();
1556
1557
        $this->assertInstanceOf(DataObjectTest\SubTeam::class, $changedDO);
1558
        $this->assertEquals($changedDO->ClassName, DataObjectTest\SubTeam::class);
1559
1560
        // Test invalid classes fail
1561
        $this->expectException(InvalidArgumentException::class);
1562
        $this->expectExceptionMessage('Controller is not a valid subclass of DataObject');
1563
        /**
1564
         * @skipUpgrade
1565
         */
1566
        $dataObject->newClassInstance('Controller');
1567
    }
1568
1569
    public function testMultipleManyManyWithSameClass()
1570
    {
1571
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1572
        $company2 = $this->objFromFixture(DataObjectTest\EquipmentCompany::class, 'equipmentcompany2');
1573
        $sponsors = $team->Sponsors();
1574
        $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

1574
        /** @scrutinizer ignore-call */ 
1575
        $equipmentSuppliers = $team->EquipmentSuppliers();
Loading history...
1575
1576
        // Check that DataObject::many_many() works as expected
1577
        $manyManyComponent = DataObject::getSchema()->manyManyComponent(DataObjectTest\Team::class, 'Sponsors');
1578
        $this->assertEquals(ManyManyList::class, $manyManyComponent['relationClass']);
1579
        $this->assertEquals(
1580
            DataObjectTest\Team::class,
1581
            $manyManyComponent['parentClass'],
1582
            'DataObject::many_many() didn\'t find the correct base class'
1583
        );
1584
        $this->assertEquals(
1585
            DataObjectTest\EquipmentCompany::class,
1586
            $manyManyComponent['childClass'],
1587
            'DataObject::many_many() didn\'t find the correct target class for the relation'
1588
        );
1589
        $this->assertEquals(
1590
            'DataObjectTest_EquipmentCompany_SponsoredTeams',
1591
            $manyManyComponent['join'],
1592
            'DataObject::many_many() didn\'t find the correct relation table'
1593
        );
1594
        $this->assertEquals('DataObjectTest_TeamID', $manyManyComponent['parentField']);
1595
        $this->assertEquals('DataObjectTest_EquipmentCompanyID', $manyManyComponent['childField']);
1596
1597
        // Check that ManyManyList still works
1598
        $this->assertEquals(2, $sponsors->count(), 'Rows are missing from relation');
1599
        $this->assertEquals(1, $equipmentSuppliers->count(), 'Rows are missing from relation');
1600
1601
        // Check everything works when no relation is present
1602
        $teamWithoutSponsor = $this->objFromFixture(DataObjectTest\Team::class, 'team3');
1603
        $this->assertInstanceOf(ManyManyList::class, $teamWithoutSponsor->Sponsors());
1604
        $this->assertEquals(0, $teamWithoutSponsor->Sponsors()->count());
1605
1606
        // Test that belongs_many_many can be infered from with getNonReciprocalComponent
1607
        $this->assertListEquals(
1608
            [
1609
                ['Name' => 'Company corp'],
1610
                ['Name' => 'Team co.'],
1611
            ],
1612
            $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

1612
            /** @scrutinizer ignore-type */ $team->inferReciprocalComponent(DataObjectTest\EquipmentCompany::class, 'SponsoredTeams')
Loading history...
1613
        );
1614
1615
        // Test that many_many can be infered from getNonReciprocalComponent
1616
        $this->assertListEquals(
1617
            [
1618
                ['Title' => 'Team 1'],
1619
                ['Title' => 'Team 2'],
1620
                ['Title' => 'Subteam 1'],
1621
            ],
1622
            $company2->inferReciprocalComponent(DataObjectTest\Team::class, 'Sponsors')
1623
        );
1624
1625
        // Check many_many_extraFields still works
1626
        $equipmentCompany = $this->objFromFixture(DataObjectTest\EquipmentCompany::class, 'equipmentcompany1');
1627
        $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

1627
        $equipmentCompany->/** @scrutinizer ignore-call */ 
1628
                           SponsoredTeams()->add($teamWithoutSponsor, array('SponsorFee' => 1000));
Loading history...
1628
        $sponsoredTeams = $equipmentCompany->SponsoredTeams();
1629
        $this->assertEquals(
1630
            1000,
1631
            $sponsoredTeams->byID($teamWithoutSponsor->ID)->SponsorFee,
1632
            'Data from many_many_extraFields was not stored/extracted correctly'
1633
        );
1634
1635
        // Check subclasses correctly inherit multiple many_manys
1636
        $subTeam = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
1637
        $this->assertEquals(
1638
            2,
1639
            $subTeam->Sponsors()->count(),
1640
            'Child class did not inherit multiple many_manys'
1641
        );
1642
        $this->assertEquals(
1643
            1,
1644
            $subTeam->EquipmentSuppliers()->count(),
1645
            'Child class did not inherit multiple many_manys'
1646
        );
1647
        // Team 2 has one EquipmentCompany sponsor and one SubEquipmentCompany
1648
        $team2 = $this->objFromFixture(DataObjectTest\Team::class, 'team2');
1649
        $this->assertEquals(
1650
            2,
1651
            $team2->Sponsors()->count(),
1652
            'Child class did not inherit multiple belongs_many_manys'
1653
        );
1654
1655
        // Check many_many_extraFields also works from the belongs_many_many side
1656
        $sponsors = $team2->Sponsors();
1657
        $sponsors->add($equipmentCompany, array('SponsorFee' => 750));
1658
        $this->assertEquals(
1659
            750,
1660
            $sponsors->byID($equipmentCompany->ID)->SponsorFee,
1661
            'Data from many_many_extraFields was not stored/extracted correctly'
1662
        );
1663
1664
        $subEquipmentCompany = $this->objFromFixture(DataObjectTest\SubEquipmentCompany::class, 'subequipmentcompany1');
1665
        $subTeam->Sponsors()->add($subEquipmentCompany, array('SponsorFee' => 1200));
1666
        $this->assertEquals(
1667
            1200,
1668
            $subTeam->Sponsors()->byID($subEquipmentCompany->ID)->SponsorFee,
1669
            'Data from inherited many_many_extraFields was not stored/extracted correctly'
1670
        );
1671
    }
1672
1673
    public function testManyManyExtraFields()
1674
    {
1675
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1676
        $schema = DataObject::getSchema();
1677
1678
        // Get all extra fields
1679
        $teamExtraFields = $team->manyManyExtraFields();
1680
        $this->assertEquals(
1681
            array(
1682
                'Players' => array('Position' => 'Varchar(100)')
1683
            ),
1684
            $teamExtraFields
1685
        );
1686
1687
        // Ensure fields from parent classes are included
1688
        $subTeam = singleton(DataObjectTest\SubTeam::class);
1689
        $teamExtraFields = $subTeam->manyManyExtraFields();
1690
        $this->assertEquals(
1691
            array(
1692
                'Players' => array('Position' => 'Varchar(100)'),
1693
                'FormerPlayers' => array('Position' => 'Varchar(100)')
1694
            ),
1695
            $teamExtraFields
1696
        );
1697
1698
        // Extra fields are immediately available on the Team class (defined in $many_many_extraFields)
1699
        $teamExtraFields = $schema->manyManyExtraFieldsForComponent(DataObjectTest\Team::class, 'Players');
1700
        $this->assertEquals(
1701
            $teamExtraFields,
1702
            array(
1703
                'Position' => 'Varchar(100)'
1704
            )
1705
        );
1706
1707
        // We'll have to go through the relation to get the extra fields on Player
1708
        $playerExtraFields = $schema->manyManyExtraFieldsForComponent(DataObjectTest\Player::class, 'Teams');
1709
        $this->assertEquals(
1710
            $playerExtraFields,
1711
            array(
1712
                'Position' => 'Varchar(100)'
1713
            )
1714
        );
1715
1716
        // Iterate through a many-many relationship and confirm that extra fields are included
1717
        $newTeam = new DataObjectTest\Team();
1718
        $newTeam->Title = "New team";
1719
        $newTeam->write();
1720
        $newTeamID = $newTeam->ID;
1721
1722
        $newPlayer = new DataObjectTest\Player();
1723
        $newPlayer->FirstName = "Sam";
1724
        $newPlayer->Surname = "Minnee";
1725
        $newPlayer->write();
1726
1727
        // The idea of Sam as a prop is essentially humourous.
1728
        $newTeam->Players()->add($newPlayer, array("Position" => "Prop"));
1729
1730
        // Requery and uncache everything
1731
        $newTeam->flushCache();
1732
        $newTeam = DataObject::get_by_id(DataObjectTest\Team::class, $newTeamID);
1733
1734
        // Check that the Position many_many_extraField is extracted.
1735
        $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

1735
        $player = $newTeam->/** @scrutinizer ignore-call */ Players()->first();
Loading history...
1736
        $this->assertEquals('Sam', $player->FirstName);
1737
        $this->assertEquals("Prop", $player->Position);
1738
1739
        // Check that ordering a many-many relation by an aggregate column doesn't fail
1740
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
1741
        $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

1741
        $player->/** @scrutinizer ignore-call */ 
1742
                 Teams()->sort("count(DISTINCT \"DataObjectTest_Team_Players\".\"DataObjectTest_PlayerID\") DESC");
Loading history...
1742
    }
1743
1744
    /**
1745
     * Check that the queries generated for many-many relation queries can have unlimitedRowCount
1746
     * called on them.
1747
     */
1748
    public function testManyManyUnlimitedRowCount()
1749
    {
1750
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
1751
        // TODO: What's going on here?
1752
        $this->assertEquals(2, $player->Teams()->dataQuery()->query()->unlimitedRowCount());
1753
    }
1754
1755
    /**
1756
     * Tests that singular_name() generates sensible defaults.
1757
     */
1758
    public function testSingularName()
1759
    {
1760
        $assertions = array(
1761
            DataObjectTest\Player::class => 'Player',
1762
            DataObjectTest\Team::class => 'Team',
1763
            DataObjectTest\Fixture::class => 'Fixture',
1764
        );
1765
1766
        foreach ($assertions as $class => $expectedSingularName) {
1767
            $this->assertEquals(
1768
                $expectedSingularName,
1769
                singleton($class)->singular_name(),
1770
                "Assert that the singular_name for '$class' is correct."
1771
            );
1772
        }
1773
    }
1774
1775
    /**
1776
     * Tests that plural_name() generates sensible defaults.
1777
     */
1778
    public function testPluralName()
1779
    {
1780
        $assertions = array(
1781
            DataObjectTest\Player::class => 'Players',
1782
            DataObjectTest\Team::class => 'Teams',
1783
            DataObjectTest\Fixture::class => 'Fixtures',
1784
            DataObjectTest\Play::class => 'Plays',
1785
            DataObjectTest\Bogey::class => 'Bogeys',
1786
            DataObjectTest\Ploy::class => 'Ploys',
1787
        );
1788
        i18n::set_locale('en_NZ');
1789
        foreach ($assertions as $class => $expectedPluralName) {
1790
            $this->assertEquals(
1791
                $expectedPluralName,
1792
                DataObject::singleton($class)->plural_name(),
1793
                "Assert that the plural_name for '$class' is correct."
1794
            );
1795
            $this->assertEquals(
1796
                $expectedPluralName,
1797
                DataObject::singleton($class)->i18n_plural_name(),
1798
                "Assert that the i18n_plural_name for '$class' is correct."
1799
            );
1800
        }
1801
    }
1802
1803
    public function testHasDatabaseField()
1804
    {
1805
        $team = singleton(DataObjectTest\Team::class);
1806
        $subteam = singleton(DataObjectTest\SubTeam::class);
1807
1808
        $this->assertTrue(
1809
            $team->hasDatabaseField('Title'),
1810
            "hasOwnDatabaseField() works with \$db fields"
1811
        );
1812
        $this->assertTrue(
1813
            $team->hasDatabaseField('CaptainID'),
1814
            "hasOwnDatabaseField() works with \$has_one fields"
1815
        );
1816
        $this->assertFalse(
1817
            $team->hasDatabaseField('NonExistentField'),
1818
            "hasOwnDatabaseField() doesn't detect non-existend fields"
1819
        );
1820
        $this->assertTrue(
1821
            $team->hasDatabaseField('ExtendedDatabaseField'),
1822
            "hasOwnDatabaseField() works with extended fields"
1823
        );
1824
        $this->assertFalse(
1825
            $team->hasDatabaseField('SubclassDatabaseField'),
1826
            "hasOwnDatabaseField() doesn't pick up fields in subclasses on parent class"
1827
        );
1828
1829
        $this->assertTrue(
1830
            $subteam->hasDatabaseField('SubclassDatabaseField'),
1831
            "hasOwnDatabaseField() picks up fields in subclasses"
1832
        );
1833
    }
1834
1835
    public function testFieldTypes()
1836
    {
1837
        $obj = new DataObjectTest\Fixture();
1838
        $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...
1839
        $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...
1840
        $obj->write();
1841
        $obj->flushCache();
1842
1843
        $obj = DataObject::get_by_id(DataObjectTest\Fixture::class, $obj->ID);
1844
        $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...
1845
        $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...
1846
    }
1847
1848
    public function testTwoSubclassesWithTheSameFieldNameWork()
1849
    {
1850
        // Create two objects of different subclasses, setting the values of fields that are
1851
        // defined separately in each subclass
1852
        $obj1 = new DataObjectTest\SubTeam();
1853
        $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...
1854
        $obj2 = new DataObjectTest\OtherSubclassWithSameField();
1855
        $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...
1856
1857
        // Write them to the database
1858
        $obj1->write();
1859
        $obj2->write();
1860
1861
        // Check that the values of those fields are properly read from the database
1862
        $values = DataObject::get(
1863
            DataObjectTest\Team::class,
1864
            "\"DataObjectTest_Team\".\"ID\" IN
1865
			($obj1->ID, $obj2->ID)"
1866
        )->column("SubclassDatabaseField");
1867
        $this->assertEquals(array_intersect($values, array('obj1', 'obj2')), $values);
1868
    }
1869
1870
    public function testClassNameSetForNewObjects()
1871
    {
1872
        $d = new DataObjectTest\Player();
1873
        $this->assertEquals(DataObjectTest\Player::class, $d->ClassName);
1874
    }
1875
1876
    public function testHasValue()
1877
    {
1878
        $team = new DataObjectTest\Team();
1879
        $this->assertFalse($team->hasValue('Title', null, false));
1880
        $this->assertFalse($team->hasValue('DatabaseField', null, false));
1881
1882
        $team->Title = 'hasValue';
1883
        $this->assertTrue($team->hasValue('Title', null, false));
1884
        $this->assertFalse($team->hasValue('DatabaseField', null, false));
1885
1886
        $team->Title = '<p></p>';
1887
        $this->assertTrue(
1888
            $team->hasValue('Title', null, false),
1889
            'Test that an empty paragraph is a value for non-HTML fields.'
1890
        );
1891
1892
        $team->DatabaseField = 'hasValue';
1893
        $this->assertTrue($team->hasValue('Title', null, false));
1894
        $this->assertTrue($team->hasValue('DatabaseField', null, false));
1895
    }
1896
1897
    public function testHasMany()
1898
    {
1899
        $company = new DataObjectTest\Company();
1900
1901
        $this->assertEquals(
1902
            array(
1903
                'CurrentStaff' => DataObjectTest\Staff::class,
1904
                'PreviousStaff' => DataObjectTest\Staff::class
1905
            ),
1906
            $company->hasMany(),
1907
            'has_many strips field name data by default.'
1908
        );
1909
1910
        $this->assertEquals(
1911
            DataObjectTest\Staff::class,
1912
            DataObject::getSchema()->hasManyComponent(DataObjectTest\Company::class, 'CurrentStaff'),
1913
            'has_many strips field name data by default on single relationships.'
1914
        );
1915
1916
        $this->assertEquals(
1917
            array(
1918
                'CurrentStaff' => DataObjectTest\Staff::class . '.CurrentCompany',
1919
                'PreviousStaff' => DataObjectTest\Staff::class . '.PreviousCompany'
1920
            ),
1921
            $company->hasMany(false),
1922
            'has_many returns field name data when $classOnly is false.'
1923
        );
1924
1925
        $this->assertEquals(
1926
            DataObjectTest\Staff::class . '.CurrentCompany',
1927
            DataObject::getSchema()->hasManyComponent(DataObjectTest\Company::class, 'CurrentStaff', false),
1928
            'has_many returns field name data on single records when $classOnly is false.'
1929
        );
1930
    }
1931
1932
    public function testGetRemoteJoinField()
1933
    {
1934
        $schema = DataObject::getSchema();
1935
1936
        // Company schema
1937
        $staffJoinField = $schema->getRemoteJoinField(
1938
            DataObjectTest\Company::class,
1939
            'CurrentStaff',
1940
            'has_many',
1941
            $polymorphic
1942
        );
1943
        $this->assertEquals('CurrentCompanyID', $staffJoinField);
1944
        $this->assertFalse($polymorphic, 'DataObjectTest_Company->CurrentStaff is not polymorphic');
1945
        $previousStaffJoinField = $schema->getRemoteJoinField(
1946
            DataObjectTest\Company::class,
1947
            'PreviousStaff',
1948
            'has_many',
1949
            $polymorphic
1950
        );
1951
        $this->assertEquals('PreviousCompanyID', $previousStaffJoinField);
1952
        $this->assertFalse($polymorphic, 'DataObjectTest_Company->PreviousStaff is not polymorphic');
1953
1954
        // CEO Schema
1955
        $this->assertEquals(
1956
            'CEOID',
1957
            $schema->getRemoteJoinField(
1958
                DataObjectTest\CEO::class,
1959
                'Company',
1960
                'belongs_to',
1961
                $polymorphic
1962
            )
1963
        );
1964
        $this->assertFalse($polymorphic, 'DataObjectTest_CEO->Company is not polymorphic');
1965
        $this->assertEquals(
1966
            'PreviousCEOID',
1967
            $schema->getRemoteJoinField(
1968
                DataObjectTest\CEO::class,
1969
                'PreviousCompany',
1970
                'belongs_to',
1971
                $polymorphic
1972
            )
1973
        );
1974
        $this->assertFalse($polymorphic, 'DataObjectTest_CEO->PreviousCompany is not polymorphic');
1975
1976
        // Team schema
1977
        $this->assertEquals(
1978
            'Favourite',
1979
            $schema->getRemoteJoinField(
1980
                DataObjectTest\Team::class,
1981
                'Fans',
1982
                'has_many',
1983
                $polymorphic
1984
            )
1985
        );
1986
        $this->assertTrue($polymorphic, 'DataObjectTest_Team->Fans is polymorphic');
1987
        $this->assertEquals(
1988
            'TeamID',
1989
            $schema->getRemoteJoinField(
1990
                DataObjectTest\Team::class,
1991
                'Comments',
1992
                'has_many',
1993
                $polymorphic
1994
            )
1995
        );
1996
        $this->assertFalse($polymorphic, 'DataObjectTest_Team->Comments is not polymorphic');
1997
    }
1998
1999
    public function testBelongsTo()
2000
    {
2001
        $company = new DataObjectTest\Company();
2002
        $ceo = new DataObjectTest\CEO();
2003
2004
        $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...
2005
        $company->write();
2006
        $ceo->write();
2007
2008
        // Test belongs_to assignment
2009
        $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...
2010
        $company->write();
2011
2012
        $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

2012
        $this->assertEquals($company->ID, $ceo->/** @scrutinizer ignore-call */ Company()->ID, 'belongs_to returns the right results.');
Loading history...
2013
2014
        // Test belongs_to can be infered via getNonReciprocalComponent
2015
        // Note: Will be returned as has_many since the belongs_to is ignored.
2016
        $this->assertListEquals(
2017
            [['Name' => 'New Company']],
2018
            $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

2018
            /** @scrutinizer ignore-type */ $ceo->inferReciprocalComponent(DataObjectTest\Company::class, 'CEO')
Loading history...
2019
        );
2020
2021
        // Test has_one to a belongs_to can be infered via getNonReciprocalComponent
2022
        $this->assertEquals(
2023
            $ceo->ID,
2024
            $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...
2025
        );
2026
2027
        // Test automatic creation of class where no assigment exists
2028
        $ceo = new DataObjectTest\CEO();
2029
        $ceo->write();
2030
2031
        $this->assertTrue(
2032
            $ceo->Company() instanceof DataObjectTest\Company,
2033
            'DataObjects across belongs_to relations are automatically created.'
2034
        );
2035
        $this->assertEquals($ceo->ID, $ceo->Company()->CEOID, 'Remote IDs are automatically set.');
2036
2037
        // Write object with components
2038
        $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...
2039
        $ceo->write(false, false, false, true);
2040
        $this->assertTrue($ceo->Company()->isInDB(), 'write() writes belongs_to components to the database.');
2041
2042
        $newCEO = DataObject::get_by_id(DataObjectTest\CEO::class, $ceo->ID);
2043
        $this->assertEquals(
2044
            $ceo->Company()->ID,
2045
            $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

2045
            $newCEO->/** @scrutinizer ignore-call */ 
2046
                     Company()->ID,
Loading history...
2046
            'belongs_to can be retrieved from the database.'
2047
        );
2048
    }
2049
2050
    public function testBelongsToPolymorphic()
2051
    {
2052
        $company = new DataObjectTest\Company();
2053
        $ceo = new DataObjectTest\CEO();
2054
2055
        $company->write();
2056
        $ceo->write();
2057
2058
        // Test belongs_to assignment
2059
        $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...
2060
        $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...
2061
        $company->write();
2062
2063
        $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

2063
        $this->assertEquals($company->ID, $ceo->/** @scrutinizer ignore-call */ CompanyOwned()->ID, 'belongs_to returns the right results.');
Loading history...
2064
        $this->assertInstanceOf(
2065
            DataObjectTest\Company::class,
2066
            $ceo->CompanyOwned(),
2067
            'belongs_to returns the right results.'
2068
        );
2069
2070
        // Test automatic creation of class where no assigment exists
2071
        $ceo = new DataObjectTest\CEO();
2072
        $ceo->write();
2073
2074
        $this->assertTrue(
2075
            $ceo->CompanyOwned() instanceof DataObjectTest\Company,
2076
            'DataObjects across polymorphic belongs_to relations are automatically created.'
2077
        );
2078
        $this->assertEquals($ceo->ID, $ceo->CompanyOwned()->OwnerID, 'Remote IDs are automatically set.');
2079
        $this->assertInstanceOf($ceo->CompanyOwned()->OwnerClass, $ceo, 'Remote class is automatically  set');
2080
2081
        // Write object with components
2082
        $ceo->write(false, false, false, true);
2083
        $this->assertTrue($ceo->CompanyOwned()->isInDB(), 'write() writes belongs_to components to the database.');
2084
2085
        $newCEO = DataObject::get_by_id(DataObjectTest\CEO::class, $ceo->ID);
2086
        $this->assertEquals(
2087
            $ceo->CompanyOwned()->ID,
2088
            $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

2088
            $newCEO->/** @scrutinizer ignore-call */ 
2089
                     CompanyOwned()->ID,
Loading history...
2089
            'polymorphic belongs_to can be retrieved from the database.'
2090
        );
2091
    }
2092
2093
    /**
2094
     * @expectedException LogicException
2095
     */
2096
    public function testInvalidate()
2097
    {
2098
        $do = new DataObjectTest\Fixture();
2099
        $do->write();
2100
2101
        $do->delete();
2102
2103
        $do->delete(); // Prohibit invalid object manipulation
2104
        $do->write();
2105
        $do->duplicate();
2106
    }
2107
2108
    public function testToMap()
2109
    {
2110
        $obj = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
2111
2112
        $map = $obj->toMap();
2113
2114
        $this->assertArrayHasKey('ID', $map, 'Contains base fields');
2115
        $this->assertArrayHasKey('Title', $map, 'Contains fields from parent class');
2116
        $this->assertArrayHasKey('SubclassDatabaseField', $map, 'Contains fields from concrete class');
2117
2118
        $this->assertEquals(
2119
            $obj->ID,
2120
            $map['ID'],
2121
            'Contains values from base fields'
2122
        );
2123
        $this->assertEquals(
2124
            $obj->Title,
2125
            $map['Title'],
2126
            'Contains values from parent class fields'
2127
        );
2128
        $this->assertEquals(
2129
            $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...
2130
            $map['SubclassDatabaseField'],
2131
            'Contains values from concrete class fields'
2132
        );
2133
2134
        $newObj = new DataObjectTest\SubTeam();
0 ignored issues
show
Unused Code introduced by
The assignment to $newObj is dead and can be removed.
Loading history...
2135
        $this->assertArrayHasKey('Title', $map, 'Contains null fields');
2136
    }
2137
2138
    public function testIsEmpty()
2139
    {
2140
        $objEmpty = new DataObjectTest\Team();
2141
        $this->assertTrue($objEmpty->isEmpty(), 'New instance without populated defaults is empty');
2142
2143
        $objEmpty->Title = '0'; //
2144
        $this->assertFalse($objEmpty->isEmpty(), 'Zero value in attribute considered non-empty');
2145
    }
2146
2147
    public function testRelField()
2148
    {
2149
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
2150
        // Test traversal of a single has_one
2151
        $this->assertEquals("Team 1", $captain1->relField('FavouriteTeam.Title'));
2152
        // Test direct field access
2153
        $this->assertEquals("Captain", $captain1->relField('FirstName'));
2154
2155
        // Test empty link
2156
        $captain2 = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
2157
        $this->assertEmpty($captain2->relField('FavouriteTeam.Title'));
2158
        $this->assertNull($captain2->relField('FavouriteTeam.ReturnsNull'));
2159
        $this->assertNull($captain2->relField('FavouriteTeam.ReturnsNull.Title'));
2160
2161
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
2162
        // Test that we can traverse more than once, and that arbitrary methods are okay
2163
        $this->assertEquals("Team 1", $player->relField('Teams.First.Title'));
2164
2165
        $newPlayer = new DataObjectTest\Player();
2166
        $this->assertNull($newPlayer->relField('Teams.First.Title'));
2167
2168
        // Test that relField works on db field manipulations
2169
        $comment = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment3');
2170
        $this->assertEquals("PHIL IS A UNIQUE GUY, AND COMMENTS ON TEAM2", $comment->relField('Comment.UpperCase'));
2171
2172
        // relField throws exception on invalid properties
2173
        $this->expectException(LogicException::class);
2174
        $this->expectExceptionMessage("Not is not a relation/field on " . DataObjectTest\TeamComment::class);
2175
        $comment->relField('Not.A.Field');
2176
    }
2177
2178
    public function testRelObject()
2179
    {
2180
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
2181
2182
        // Test traversal of a single has_one
2183
        $this->assertInstanceOf(DBVarchar::class, $captain1->relObject('FavouriteTeam.Title'));
2184
        $this->assertEquals("Team 1", $captain1->relObject('FavouriteTeam.Title')->getValue());
2185
2186
        // Test empty link
2187
        $captain2 = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
2188
        $this->assertEmpty($captain2->relObject('FavouriteTeam.Title')->getValue());
2189
        $this->assertNull($captain2->relObject('FavouriteTeam.ReturnsNull.Title'));
2190
2191
        // Test direct field access
2192
        $this->assertInstanceOf(DBBoolean::class, $captain1->relObject('IsRetired'));
2193
        $this->assertEquals(1, $captain1->relObject('IsRetired')->getValue());
2194
2195
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
2196
        // Test that we can traverse more than once, and that arbitrary methods are okay
2197
        $this->assertInstanceOf(DBVarchar::class, $player->relObject('Teams.First.Title'));
2198
        $this->assertEquals("Team 1", $player->relObject('Teams.First.Title')->getValue());
2199
2200
        // relObject throws exception on invalid properties
2201
        $this->expectException(LogicException::class);
2202
        $this->expectExceptionMessage("Not is not a relation/field on " . DataObjectTest\Player::class);
2203
        $player->relObject('Not.A.Field');
2204
    }
2205
2206
    public function testLateStaticBindingStyle()
2207
    {
2208
        // Confirm that DataObjectTest_Player::get() operates as excepted
2209
        $this->assertEquals(4, DataObjectTest\Player::get()->count());
2210
        $this->assertInstanceOf(DataObjectTest\Player::class, DataObjectTest\Player::get()->first());
2211
2212
        // You can't pass arguments to LSB syntax - use the DataList methods instead.
2213
        $this->expectException(InvalidArgumentException::class);
2214
2215
        DataObjectTest\Player::get(null, "\"ID\" = 1");
2216
    }
2217
2218
    /**
2219
     * @expectedException \InvalidArgumentException
2220
     */
2221
    public function testBrokenLateStaticBindingStyle()
2222
    {
2223
        // If you call DataObject::get() you have to pass a first argument
2224
        DataObject::get();
2225
    }
2226
2227
    public function testBigIntField()
2228
    {
2229
        $staff = new DataObjectTest\Staff();
2230
        $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...
2231
        $staff->write();
2232
        $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...
2233
    }
2234
2235
    public function testGetOneMissingValueReturnsNull()
2236
    {
2237
2238
        // Test that missing values return null
2239
        $this->assertEquals(null, DataObject::get_one(
2240
            DataObjectTest\TeamComment::class,
2241
            ['"DataObjectTest_TeamComment"."Name"' => 'does not exists']
2242
        ));
2243
    }
2244
2245
    public function testSetFieldWithArrayOnScalarOnlyField()
2246
    {
2247
        $this->expectException(InvalidArgumentException::class);
2248
        $do = Company::singleton();
2249
        $do->FoundationYear = '1984';
2250
        $do->FoundationYear = array('Amount' => 123, 'Currency' => 'CAD');
2251
        $this->assertEmpty($do->FoundationYear);
2252
    }
2253
2254
    public function testSetFieldWithArrayOnCompositeField()
2255
    {
2256
        $do = Company::singleton();
2257
        $do->SalaryCap = array('Amount' => 123456, 'Currency' => 'CAD');
2258
        $this->assertNotEmpty($do->SalaryCap);
2259
    }
2260
2261
    public function testWriteManipulationWithNonScalarValuesAllowed()
2262
    {
2263
        $do = DataObjectTest\MockDynamicAssignmentDataObject::create();
2264
        $do->write();
2265
2266
        $do->StaticScalarOnlyField = true;
2267
        $do->DynamicScalarOnlyField = false;
2268
        $do->DynamicField = true;
2269
2270
        $do->write();
2271
2272
        $this->assertTrue($do->StaticScalarOnlyField);
2273
        $this->assertFalse($do->DynamicScalarOnlyField);
2274
        $this->assertTrue($do->DynamicField);
2275
    }
2276
2277
    public function testWriteManipulationWithNonScalarValuesDisallowed()
2278
    {
2279
        $this->expectException(InvalidArgumentException::class);
2280
2281
        $do = DataObjectTest\MockDynamicAssignmentDataObject::create();
2282
        $do->write();
2283
2284
        $do->StaticScalarOnlyField = false;
2285
        $do->DynamicScalarOnlyField = true;
2286
        $do->DynamicField = false;
2287
2288
        $do->write();
2289
    }
2290
2291
    public function testRecursiveWrite()
2292
    {
2293
2294
        $root = $this->objFromFixture(TreeNode::class, 'root');
2295
        $child = $this->objFromFixture(TreeNode::class, 'child');
2296
        $grandchild = $this->objFromFixture(TreeNode::class, 'grandchild');
2297
2298
        // Create a cycle ... this will test that we can't create an infinite loop
2299
        $root->CycleID = $grandchild->ID;
0 ignored issues
show
Bug Best Practice introduced by
The property CycleID does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
2300
        $root->write();
2301
2302
        // Our count will have been set while loading our fixtures, let's reset eveything back to 0
2303
        TreeNode::singleton()->resetCounts();
2304
        $root = TreeNode::get()->byID($root->ID);
2305
        $child = TreeNode::get()->byID($child->ID);
2306
        $grandchild = TreeNode::get()->byID($grandchild->ID);
2307
        $this->assertEquals(0, $root->WriteCount, 'Root node write count has been reset');
0 ignored issues
show
Bug Best Practice introduced by
The property WriteCount does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
2308
        $this->assertEquals(0, $child->WriteCount, 'Child node write count has been reset');
2309
        $this->assertEquals(0, $grandchild->WriteCount, 'Grand Child node write count has been reset');
2310
2311
        // Trigger a recursive write of the grand children
2312
        $grandchild->write(false, false, false, true);
2313
2314
        // Reload the DataObject from the DB to get the new Write Counts
2315
        $root = TreeNode::get()->byID($root->ID);
2316
        $child = TreeNode::get()->byID($child->ID);
2317
        $grandchild = TreeNode::get()->byID($grandchild->ID);
2318
2319
        $this->assertEquals(
2320
            1,
2321
            $grandchild->WriteCount,
2322
            'Grand child has been written once because write was directly called on it'
2323
        );
2324
        $this->assertEquals(
2325
            1,
2326
            $child->WriteCount,
2327
            'Child should has been written once because it is directly related to grand child'
2328
        );
2329
        $this->assertEquals(
2330
            1,
2331
            $root->WriteCount,
2332
            'Root should have been written once because it is indirectly related to grand child'
2333
        );
2334
    }
2335
2336
    public function testShallowRecursiveWrite()
2337
    {
2338
        $root = $this->objFromFixture(TreeNode::class, 'root');
2339
        $child = $this->objFromFixture(TreeNode::class, 'child');
2340
        $grandchild = $this->objFromFixture(TreeNode::class, 'grandchild');
2341
2342
        // Create a cycle ... this will test that we can't create an infinite loop
2343
        $root->CycleID = $grandchild->ID;
0 ignored issues
show
Bug Best Practice introduced by
The property CycleID does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
2344
        $root->write();
2345
2346
        // Our count will have been set while loading our fixtures, let's reset eveything back to 0
2347
        TreeNode::singleton()->resetCounts();
2348
        $root = TreeNode::get()->byID($root->ID);
2349
        $child = TreeNode::get()->byID($child->ID);
2350
        $grandchild = TreeNode::get()->byID($grandchild->ID);
2351
        $this->assertEquals(0, $root->WriteCount);
0 ignored issues
show
Bug Best Practice introduced by
The property WriteCount does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
2352
        $this->assertEquals(0, $child->WriteCount);
2353
        $this->assertEquals(0, $grandchild->WriteCount);
2354
2355
        // Recursively only affect component that have been loaded
2356
        $grandchild->write(false, false, false, ['recursive' => false]);
2357
2358
        // Reload the DataObject from the DB to get the new Write Counts
2359
        $root = TreeNode::get()->byID($root->ID);
2360
        $child = TreeNode::get()->byID($child->ID);
2361
        $grandchild = TreeNode::get()->byID($grandchild->ID);
2362
2363
        $this->assertEquals(
2364
            1,
2365
            $grandchild->WriteCount,
2366
            'Grand child was written once because write was directly called on it'
2367
        );
2368
        $this->assertEquals(
2369
            1,
2370
            $child->WriteCount,
2371
            'Child was written once because it is directly related grand child'
2372
        );
2373
        $this->assertEquals(
2374
            0,
2375
            $root->WriteCount,
2376
            'Root is 2 step remove from grand children. It was not written on a shallow recursive write.'
2377
        );
2378
    }
2379
}
2380