DataObjectTest   F
last analyzed

Complexity

Total Complexity 104

Size/Duplication

Total Lines 2498
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 104
eloc 1418
c 2
b 0
f 0
dl 0
loc 2498
rs 0.8

80 Methods

Rating   Name   Duplication   Size   Complexity  
A getExtraDataObjects() 0 5 1
A testSingleton() 0 15 3
A testConstructAcceptsValues() 0 25 1
A setUp() 0 10 2
A provideSingletons() 0 21 1
A testDb() 0 52 1
A testDataObjectUpdateNew() 0 24 1
A testChangedFieldsWhenRestoringData() 0 9 1
A testDataObjectUpdate() 0 29 1
A testHasManyPolymorphicRelationships() 0 39 2
A testLimitAndCount() 0 13 1
A testHasOneAsField() 0 20 1
A testSearchableFields() 0 36 1
B testRandomSort() 0 42 9
A testCastingHelper() 0 9 1
A testFieldNamesThatMatchMethodNamesWork() 0 21 1
A testValidObjectsForBaseFields() 0 10 3
A testSummaryFieldsCustomLabels() 0 13 1
A testGetByIDCallerClass() 0 12 1
B testFieldInheritance() 0 89 1
A testGetRelationClass() 0 32 1
B testFieldExistence() 0 144 1
A testHasManyRelationships() 0 43 1
A testGet() 0 87 2
A testChangedFields() 0 47 1
A testGetHasOneRelations() 0 38 1
A testGetSubclassFields() 0 10 1
A testGetCaseInsensitive() 0 18 2
A testWriteNoChangesDoesntUpdateLastEdited() 0 21 1
A testHasOneRelationship() 0 75 1
A testWriteSavesToHasOneRelations() 0 42 1
A testForceChangeCantBeCancelledUntilWrite() 0 36 1
A testCanAccessHasOneObjectsAsMethods() 0 18 1
A testDataIntegrityWhenTwoSubclassesHaveSameField() 0 30 1
A testWritePropertyWithoutDBField() 0 19 1
A testChangedFieldsAfterWrite() 0 16 1
A testDelete() 0 26 5
A testIsChanged() 0 62 1
A testWriteManipulationWithNonScalarValuesDisallowed() 0 12 1
A testHasMany() 0 32 1
A testInvalidate() 0 11 1
A testBrokenLateStaticBindingStyle() 0 5 1
B testMultipleManyManyWithSameClass() 0 101 1
A testPopulateDefaults() 0 21 1
A testShallowRecursiveWrite() 0 41 1
A testZeroIsFalse() 0 21 1
A testBelongsToPolymorphic() 0 43 1
A testHasDatabaseField() 0 29 1
A testRelField() 0 29 1
A testRelObject() 0 26 1
A testClassNameSetForNewObjects() 0 4 1
A testPluralName() 0 21 2
A testBigIntField() 0 6 1
A testForceInsert() 0 31 3
A testHasOwnTable() 0 20 1
A testWritingValidDataObjectDoesntThrowException() 0 7 1
A testValidateModelDefinitionsFailsWithIntValue() 0 5 1
A testRecursiveWrite() 0 42 1
A testHasValue() 0 19 1
A testValidateModelDefinitionsFailsWithIntKey() 0 5 1
A testBelongsTo() 0 51 1
A testManyManyUnlimitedRowCount() 0 5 1
A testManyManyExtraFields() 0 69 1
A testSetFieldWithArrayOnScalarOnlyField() 0 7 1
A testValidateModelDefinitionsFailsWithArray() 0 5 1
A testWritingInvalidDataObjectThrowsException() 0 5 1
A testSingularName() 0 13 2
A testTwoSubclassesWithTheSameFieldNameWork() 0 20 1
A testGetOneMissingValueReturnsNull() 0 7 1
A testIsEmpty() 0 7 1
A testSubclassCreation() 0 8 1
A testIDFieldTypeAfterInsert() 0 6 1
A testToMap() 0 28 1
A testGetRemoteJoinField() 0 65 1
A testFieldTypes() 0 11 1
A testMerge() 0 37 1
A testNewClassInstance() 0 28 1
A testLateStaticBindingStyle() 0 10 1
A testSetFieldWithArrayOnCompositeField() 0 5 1
A testWriteManipulationWithNonScalarValuesAllowed() 0 14 1

How to fix   Complexity   

Complex Class

Complex classes like DataObjectTest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DataObjectTest, and based on these observations, apply Extract Interface, too.

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

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

491
        $this->assertEquals($team1ID, $captain1->/** @scrutinizer ignore-call */ FavouriteTeam()->ID);
Loading history...
492
493
        // Test that getNonReciprocalComponent can find has_one from the has_many end
494
        $this->assertEquals(
495
            $team1ID,
496
            $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...
497
        );
498
499
        // Check entity with polymorphic has-one
500
        $fan1 = $this->objFromFixture(DataObjectTest\Fan::class, "fan1");
501
        $this->assertTrue((bool)$fan1->hasValue('Favourite'));
502
503
        // There will be fields named (relname)ID and (relname)Class for polymorphic
504
        // entities
505
        $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...
506
        $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...
507
508
        // There will be a method called $obj->relname() that returns the object itself
509
        $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

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

594
        $this->assertTrue($team1->/** @scrutinizer ignore-call */ Comments()->count() == 2);
Loading history...
595
596
        $team1Comments = [
597
            ['Comment' => 'This is a team comment by Joe'],
598
            ['Comment' => 'This is a team comment by Bob'],
599
        ];
600
601
        // Test the IDs on the DataObjects are set correctly
602
        $this->assertListEquals($team1Comments, $team1->Comments());
603
604
        // Test that has_many can be infered from the has_one via getNonReciprocalComponent
605
        $this->assertListEquals(
606
            $team1Comments,
607
            $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

607
            /** @scrutinizer ignore-type */ $team1->inferReciprocalComponent(DataObjectTest\TeamComment::class, 'Team')
Loading history...
608
        );
609
610
        // Test that we can add and remove items that already exist in the database
611
        $newComment = new DataObjectTest\TeamComment();
612
        $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...
613
        $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...
614
        $newComment->write();
615
        $team1->Comments()->add($newComment);
616
        $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...
617
618
        $comment1 = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment1');
619
        $comment2 = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment2');
620
        $team1->Comments()->remove($comment2);
621
622
        $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

622
        $team1CommentIDs = $team1->Comments()->/** @scrutinizer ignore-call */ sort('ID')->column('ID');
Loading history...
623
        $this->assertEquals(array($comment1->ID, $newComment->ID), $team1CommentIDs);
624
625
        // Test that removing an item from a list doesn't remove it from the same
626
        // relation belonging to a different object
627
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
628
        $team2 = $this->objFromFixture(DataObjectTest\Team::class, 'team2');
629
        $team2->Comments()->remove($comment1);
630
        $team1CommentIDs = $team1->Comments()->sort('ID')->column('ID');
631
        $this->assertEquals(array($comment1->ID, $newComment->ID), $team1CommentIDs);
632
    }
633
634
635
    /**
636
     * Test has many relationships against polymorphic has_one fields
637
     *   - Test getComponents() gets the ComponentSet of the other side of the relation
638
     *   - Test the IDs on the DataObjects are set correctly
639
     */
640
    public function testHasManyPolymorphicRelationships()
641
    {
642
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
643
644
        // Test getComponents() gets the ComponentSet of the other side of the relation
645
        $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

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

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

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

1683
        /** @scrutinizer ignore-call */ 
1684
        $equipmentSuppliers = $team->EquipmentSuppliers();
Loading history...
1684
1685
        // Check that DataObject::many_many() works as expected
1686
        $manyManyComponent = DataObject::getSchema()->manyManyComponent(DataObjectTest\Team::class, 'Sponsors');
1687
        $this->assertEquals(ManyManyList::class, $manyManyComponent['relationClass']);
1688
        $this->assertEquals(
1689
            DataObjectTest\Team::class,
1690
            $manyManyComponent['parentClass'],
1691
            'DataObject::many_many() didn\'t find the correct base class'
1692
        );
1693
        $this->assertEquals(
1694
            DataObjectTest\EquipmentCompany::class,
1695
            $manyManyComponent['childClass'],
1696
            'DataObject::many_many() didn\'t find the correct target class for the relation'
1697
        );
1698
        $this->assertEquals(
1699
            'DataObjectTest_EquipmentCompany_SponsoredTeams',
1700
            $manyManyComponent['join'],
1701
            'DataObject::many_many() didn\'t find the correct relation table'
1702
        );
1703
        $this->assertEquals('DataObjectTest_TeamID', $manyManyComponent['parentField']);
1704
        $this->assertEquals('DataObjectTest_EquipmentCompanyID', $manyManyComponent['childField']);
1705
1706
        // Check that ManyManyList still works
1707
        $this->assertEquals(2, $sponsors->count(), 'Rows are missing from relation');
1708
        $this->assertEquals(1, $equipmentSuppliers->count(), 'Rows are missing from relation');
1709
1710
        // Check everything works when no relation is present
1711
        $teamWithoutSponsor = $this->objFromFixture(DataObjectTest\Team::class, 'team3');
1712
        $this->assertInstanceOf(ManyManyList::class, $teamWithoutSponsor->Sponsors());
1713
        $this->assertEquals(0, $teamWithoutSponsor->Sponsors()->count());
1714
1715
        // Test that belongs_many_many can be infered from with getNonReciprocalComponent
1716
        $this->assertListEquals(
1717
            [
1718
                ['Name' => 'Company corp'],
1719
                ['Name' => 'Team co.'],
1720
            ],
1721
            $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

1721
            /** @scrutinizer ignore-type */ $team->inferReciprocalComponent(DataObjectTest\EquipmentCompany::class, 'SponsoredTeams')
Loading history...
1722
        );
1723
1724
        // Test that many_many can be infered from getNonReciprocalComponent
1725
        $this->assertListEquals(
1726
            [
1727
                ['Title' => 'Team 1'],
1728
                ['Title' => 'Team 2'],
1729
                ['Title' => 'Subteam 1'],
1730
            ],
1731
            $company2->inferReciprocalComponent(DataObjectTest\Team::class, 'Sponsors')
1732
        );
1733
1734
        // Check many_many_extraFields still works
1735
        $equipmentCompany = $this->objFromFixture(DataObjectTest\EquipmentCompany::class, 'equipmentcompany1');
1736
        $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

1736
        $equipmentCompany->/** @scrutinizer ignore-call */ 
1737
                           SponsoredTeams()->add($teamWithoutSponsor, array('SponsorFee' => 1000));
Loading history...
1737
        $sponsoredTeams = $equipmentCompany->SponsoredTeams();
1738
        $this->assertEquals(
1739
            1000,
1740
            $sponsoredTeams->byID($teamWithoutSponsor->ID)->SponsorFee,
1741
            'Data from many_many_extraFields was not stored/extracted correctly'
1742
        );
1743
1744
        // Check subclasses correctly inherit multiple many_manys
1745
        $subTeam = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
1746
        $this->assertEquals(
1747
            2,
1748
            $subTeam->Sponsors()->count(),
1749
            'Child class did not inherit multiple many_manys'
1750
        );
1751
        $this->assertEquals(
1752
            1,
1753
            $subTeam->EquipmentSuppliers()->count(),
1754
            'Child class did not inherit multiple many_manys'
1755
        );
1756
        // Team 2 has one EquipmentCompany sponsor and one SubEquipmentCompany
1757
        $team2 = $this->objFromFixture(DataObjectTest\Team::class, 'team2');
1758
        $this->assertEquals(
1759
            2,
1760
            $team2->Sponsors()->count(),
1761
            'Child class did not inherit multiple belongs_many_manys'
1762
        );
1763
1764
        // Check many_many_extraFields also works from the belongs_many_many side
1765
        $sponsors = $team2->Sponsors();
1766
        $sponsors->add($equipmentCompany, array('SponsorFee' => 750));
1767
        $this->assertEquals(
1768
            750,
1769
            $sponsors->byID($equipmentCompany->ID)->SponsorFee,
1770
            'Data from many_many_extraFields was not stored/extracted correctly'
1771
        );
1772
1773
        $subEquipmentCompany = $this->objFromFixture(DataObjectTest\SubEquipmentCompany::class, 'subequipmentcompany1');
1774
        $subTeam->Sponsors()->add($subEquipmentCompany, array('SponsorFee' => 1200));
1775
        $this->assertEquals(
1776
            1200,
1777
            $subTeam->Sponsors()->byID($subEquipmentCompany->ID)->SponsorFee,
1778
            'Data from inherited many_many_extraFields was not stored/extracted correctly'
1779
        );
1780
    }
1781
1782
    public function testManyManyExtraFields()
1783
    {
1784
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1785
        $schema = DataObject::getSchema();
1786
1787
        // Get all extra fields
1788
        $teamExtraFields = $team->manyManyExtraFields();
1789
        $this->assertEquals(
1790
            array(
1791
                'Players' => array('Position' => 'Varchar(100)')
1792
            ),
1793
            $teamExtraFields
1794
        );
1795
1796
        // Ensure fields from parent classes are included
1797
        $subTeam = singleton(DataObjectTest\SubTeam::class);
1798
        $teamExtraFields = $subTeam->manyManyExtraFields();
1799
        $this->assertEquals(
1800
            array(
1801
                'Players' => array('Position' => 'Varchar(100)'),
1802
                'FormerPlayers' => array('Position' => 'Varchar(100)')
1803
            ),
1804
            $teamExtraFields
1805
        );
1806
1807
        // Extra fields are immediately available on the Team class (defined in $many_many_extraFields)
1808
        $teamExtraFields = $schema->manyManyExtraFieldsForComponent(DataObjectTest\Team::class, 'Players');
1809
        $this->assertEquals(
1810
            $teamExtraFields,
1811
            array(
1812
                'Position' => 'Varchar(100)'
1813
            )
1814
        );
1815
1816
        // We'll have to go through the relation to get the extra fields on Player
1817
        $playerExtraFields = $schema->manyManyExtraFieldsForComponent(DataObjectTest\Player::class, 'Teams');
1818
        $this->assertEquals(
1819
            $playerExtraFields,
1820
            array(
1821
                'Position' => 'Varchar(100)'
1822
            )
1823
        );
1824
1825
        // Iterate through a many-many relationship and confirm that extra fields are included
1826
        $newTeam = new DataObjectTest\Team();
1827
        $newTeam->Title = "New team";
1828
        $newTeam->write();
1829
        $newTeamID = $newTeam->ID;
1830
1831
        $newPlayer = new DataObjectTest\Player();
1832
        $newPlayer->FirstName = "Sam";
1833
        $newPlayer->Surname = "Minnee";
1834
        $newPlayer->write();
1835
1836
        // The idea of Sam as a prop is essentially humourous.
1837
        $newTeam->Players()->add($newPlayer, array("Position" => "Prop"));
1838
1839
        // Requery and uncache everything
1840
        $newTeam->flushCache();
1841
        $newTeam = DataObject::get_by_id(DataObjectTest\Team::class, $newTeamID);
1842
1843
        // Check that the Position many_many_extraField is extracted.
1844
        $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

1844
        $player = $newTeam->/** @scrutinizer ignore-call */ Players()->first();
Loading history...
1845
        $this->assertEquals('Sam', $player->FirstName);
1846
        $this->assertEquals("Prop", $player->Position);
1847
1848
        // Check that ordering a many-many relation by an aggregate column doesn't fail
1849
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
1850
        $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

1850
        $player->/** @scrutinizer ignore-call */ 
1851
                 Teams()->sort("count(DISTINCT \"DataObjectTest_Team_Players\".\"DataObjectTest_PlayerID\") DESC");
Loading history...
1851
    }
1852
1853
    /**
1854
     * Check that the queries generated for many-many relation queries can have unlimitedRowCount
1855
     * called on them.
1856
     */
1857
    public function testManyManyUnlimitedRowCount()
1858
    {
1859
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
1860
        // TODO: What's going on here?
1861
        $this->assertEquals(2, $player->Teams()->dataQuery()->query()->unlimitedRowCount());
1862
    }
1863
1864
    /**
1865
     * Tests that singular_name() generates sensible defaults.
1866
     */
1867
    public function testSingularName()
1868
    {
1869
        $assertions = array(
1870
            DataObjectTest\Player::class => 'Player',
1871
            DataObjectTest\Team::class => 'Team',
1872
            DataObjectTest\Fixture::class => 'Fixture',
1873
        );
1874
1875
        foreach ($assertions as $class => $expectedSingularName) {
1876
            $this->assertEquals(
1877
                $expectedSingularName,
1878
                singleton($class)->singular_name(),
1879
                "Assert that the singular_name for '$class' is correct."
1880
            );
1881
        }
1882
    }
1883
1884
    /**
1885
     * Tests that plural_name() generates sensible defaults.
1886
     */
1887
    public function testPluralName()
1888
    {
1889
        $assertions = array(
1890
            DataObjectTest\Player::class => 'Players',
1891
            DataObjectTest\Team::class => 'Teams',
1892
            DataObjectTest\Fixture::class => 'Fixtures',
1893
            DataObjectTest\Play::class => 'Plays',
1894
            DataObjectTest\Bogey::class => 'Bogeys',
1895
            DataObjectTest\Ploy::class => 'Ploys',
1896
        );
1897
        i18n::set_locale('en_NZ');
1898
        foreach ($assertions as $class => $expectedPluralName) {
1899
            $this->assertEquals(
1900
                $expectedPluralName,
1901
                DataObject::singleton($class)->plural_name(),
1902
                "Assert that the plural_name for '$class' is correct."
1903
            );
1904
            $this->assertEquals(
1905
                $expectedPluralName,
1906
                DataObject::singleton($class)->i18n_plural_name(),
1907
                "Assert that the i18n_plural_name for '$class' is correct."
1908
            );
1909
        }
1910
    }
1911
1912
    public function testHasDatabaseField()
1913
    {
1914
        $team = singleton(DataObjectTest\Team::class);
1915
        $subteam = singleton(DataObjectTest\SubTeam::class);
1916
1917
        $this->assertTrue(
1918
            $team->hasDatabaseField('Title'),
1919
            "hasOwnDatabaseField() works with \$db fields"
1920
        );
1921
        $this->assertTrue(
1922
            $team->hasDatabaseField('CaptainID'),
1923
            "hasOwnDatabaseField() works with \$has_one fields"
1924
        );
1925
        $this->assertFalse(
1926
            $team->hasDatabaseField('NonExistentField'),
1927
            "hasOwnDatabaseField() doesn't detect non-existend fields"
1928
        );
1929
        $this->assertTrue(
1930
            $team->hasDatabaseField('ExtendedDatabaseField'),
1931
            "hasOwnDatabaseField() works with extended fields"
1932
        );
1933
        $this->assertFalse(
1934
            $team->hasDatabaseField('SubclassDatabaseField'),
1935
            "hasOwnDatabaseField() doesn't pick up fields in subclasses on parent class"
1936
        );
1937
1938
        $this->assertTrue(
1939
            $subteam->hasDatabaseField('SubclassDatabaseField'),
1940
            "hasOwnDatabaseField() picks up fields in subclasses"
1941
        );
1942
    }
1943
1944
    public function testFieldTypes()
1945
    {
1946
        $obj = new DataObjectTest\Fixture();
1947
        $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...
1948
        $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...
1949
        $obj->write();
1950
        $obj->flushCache();
1951
1952
        $obj = DataObject::get_by_id(DataObjectTest\Fixture::class, $obj->ID);
1953
        $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...
1954
        $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...
1955
    }
1956
1957
    /**
1958
     * Tests that the autogenerated ID is returned as int
1959
     */
1960
    public function testIDFieldTypeAfterInsert()
1961
    {
1962
        $obj = new DataObjectTest\Fixture();
1963
        $obj->write();
1964
1965
        $this->assertIsInt($obj->ID);
1966
    }
1967
1968
    /**
1969
     * Tests that zero values are returned with the correct types
1970
     */
1971
    public function testZeroIsFalse()
1972
    {
1973
        $obj = new DataObjectTest\Fixture();
1974
        $obj->MyInt = 0;
0 ignored issues
show
Bug Best Practice introduced by
The property MyInt does not exist on SilverStripe\ORM\Tests\DataObjectTest\Fixture. Since you implemented __set, consider adding a @property annotation.
Loading history...
1975
        $obj->MyDecimal = 0.00;
0 ignored issues
show
Bug Best Practice introduced by
The property MyDecimal does not exist on SilverStripe\ORM\Tests\DataObjectTest\Fixture. Since you implemented __set, consider adding a @property annotation.
Loading history...
1976
        $obj->MyCurrency = 0.00;
0 ignored issues
show
Bug Best Practice introduced by
The property MyCurrency does not exist on SilverStripe\ORM\Tests\DataObjectTest\Fixture. Since you implemented __set, consider adding a @property annotation.
Loading history...
1977
        $obj->write();
1978
1979
        $this->assertEquals(0, $obj->MyInt, 'DBInt fields should be integer on first assignment');
0 ignored issues
show
Bug Best Practice introduced by
The property MyInt does not exist on SilverStripe\ORM\Tests\DataObjectTest\Fixture. Since you implemented __get, consider adding a @property annotation.
Loading history...
1980
        $this->assertEquals(0.00, $obj->MyDecimal, 'DBDecimal fields should be float on first assignment');
0 ignored issues
show
Bug Best Practice introduced by
The property MyDecimal does not exist on SilverStripe\ORM\Tests\DataObjectTest\Fixture. Since you implemented __get, consider adding a @property annotation.
Loading history...
1981
        $this->assertEquals(0.00, $obj->MyCurrency, 'DBCurrency fields should be float on first assignment');
0 ignored issues
show
Bug Best Practice introduced by
The property MyCurrency does not exist on SilverStripe\ORM\Tests\DataObjectTest\Fixture. Since you implemented __get, consider adding a @property annotation.
Loading history...
1982
1983
        $obj2 = DataObjectTest\Fixture::get()->byId($obj->ID);
1984
1985
        $this->assertEquals(0, $obj2->MyInt, 'DBInt fields should be integer');
0 ignored issues
show
Bug Best Practice introduced by
The property MyInt does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1986
        $this->assertEquals(0.00, $obj2->MyDecimal, 'DBDecimal fields should be float');
0 ignored issues
show
Bug Best Practice introduced by
The property MyDecimal does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1987
        $this->assertEquals(0.00, $obj2->MyCurrency, 'DBCurrency fields should be float');
0 ignored issues
show
Bug Best Practice introduced by
The property MyCurrency does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1988
1989
        $this->assertFalse((bool)$obj2->MyInt, 'DBInt zero fields should be falsey on fetch from DB');
1990
        $this->assertFalse((bool)$obj2->MyDecimal, 'DBDecimal zero fields should be falsey on fetch from DB');
1991
        $this->assertFalse((bool)$obj2->MyCurrency, 'DBCurrency zero fields should be falsey on fetch from DB');
1992
    }
1993
1994
    public function testTwoSubclassesWithTheSameFieldNameWork()
1995
    {
1996
        // Create two objects of different subclasses, setting the values of fields that are
1997
        // defined separately in each subclass
1998
        $obj1 = new DataObjectTest\SubTeam();
1999
        $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...
2000
        $obj2 = new DataObjectTest\OtherSubclassWithSameField();
2001
        $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...
2002
2003
        // Write them to the database
2004
        $obj1->write();
2005
        $obj2->write();
2006
2007
        // Check that the values of those fields are properly read from the database
2008
        $values = DataObject::get(
2009
            DataObjectTest\Team::class,
2010
            "\"DataObjectTest_Team\".\"ID\" IN
2011
			($obj1->ID, $obj2->ID)"
2012
        )->column("SubclassDatabaseField");
2013
        $this->assertEquals(array_intersect($values, array('obj1', 'obj2')), $values);
2014
    }
2015
2016
    public function testClassNameSetForNewObjects()
2017
    {
2018
        $d = new DataObjectTest\Player();
2019
        $this->assertEquals(DataObjectTest\Player::class, $d->ClassName);
2020
    }
2021
2022
    public function testHasValue()
2023
    {
2024
        $team = new DataObjectTest\Team();
2025
        $this->assertFalse($team->hasValue('Title', null, false));
2026
        $this->assertFalse($team->hasValue('DatabaseField', null, false));
2027
2028
        $team->Title = 'hasValue';
2029
        $this->assertTrue($team->hasValue('Title', null, false));
2030
        $this->assertFalse($team->hasValue('DatabaseField', null, false));
2031
2032
        $team->Title = '<p></p>';
2033
        $this->assertTrue(
2034
            $team->hasValue('Title', null, false),
2035
            'Test that an empty paragraph is a value for non-HTML fields.'
2036
        );
2037
2038
        $team->DatabaseField = 'hasValue';
2039
        $this->assertTrue($team->hasValue('Title', null, false));
2040
        $this->assertTrue($team->hasValue('DatabaseField', null, false));
2041
    }
2042
2043
    public function testHasMany()
2044
    {
2045
        $company = new DataObjectTest\Company();
2046
2047
        $this->assertEquals(
2048
            array(
2049
                'CurrentStaff' => DataObjectTest\Staff::class,
2050
                'PreviousStaff' => DataObjectTest\Staff::class
2051
            ),
2052
            $company->hasMany(),
2053
            'has_many strips field name data by default.'
2054
        );
2055
2056
        $this->assertEquals(
2057
            DataObjectTest\Staff::class,
2058
            DataObject::getSchema()->hasManyComponent(DataObjectTest\Company::class, 'CurrentStaff'),
2059
            'has_many strips field name data by default on single relationships.'
2060
        );
2061
2062
        $this->assertEquals(
2063
            array(
2064
                'CurrentStaff' => DataObjectTest\Staff::class . '.CurrentCompany',
2065
                'PreviousStaff' => DataObjectTest\Staff::class . '.PreviousCompany'
2066
            ),
2067
            $company->hasMany(false),
2068
            'has_many returns field name data when $classOnly is false.'
2069
        );
2070
2071
        $this->assertEquals(
2072
            DataObjectTest\Staff::class . '.CurrentCompany',
2073
            DataObject::getSchema()->hasManyComponent(DataObjectTest\Company::class, 'CurrentStaff', false),
2074
            'has_many returns field name data on single records when $classOnly is false.'
2075
        );
2076
    }
2077
2078
    public function testGetRemoteJoinField()
2079
    {
2080
        $schema = DataObject::getSchema();
2081
2082
        // Company schema
2083
        $staffJoinField = $schema->getRemoteJoinField(
2084
            DataObjectTest\Company::class,
2085
            'CurrentStaff',
2086
            'has_many',
2087
            $polymorphic
2088
        );
2089
        $this->assertEquals('CurrentCompanyID', $staffJoinField);
2090
        $this->assertFalse($polymorphic, 'DataObjectTest_Company->CurrentStaff is not polymorphic');
2091
        $previousStaffJoinField = $schema->getRemoteJoinField(
2092
            DataObjectTest\Company::class,
2093
            'PreviousStaff',
2094
            'has_many',
2095
            $polymorphic
2096
        );
2097
        $this->assertEquals('PreviousCompanyID', $previousStaffJoinField);
2098
        $this->assertFalse($polymorphic, 'DataObjectTest_Company->PreviousStaff is not polymorphic');
2099
2100
        // CEO Schema
2101
        $this->assertEquals(
2102
            'CEOID',
2103
            $schema->getRemoteJoinField(
2104
                DataObjectTest\CEO::class,
2105
                'Company',
2106
                'belongs_to',
2107
                $polymorphic
2108
            )
2109
        );
2110
        $this->assertFalse($polymorphic, 'DataObjectTest_CEO->Company is not polymorphic');
2111
        $this->assertEquals(
2112
            'PreviousCEOID',
2113
            $schema->getRemoteJoinField(
2114
                DataObjectTest\CEO::class,
2115
                'PreviousCompany',
2116
                'belongs_to',
2117
                $polymorphic
2118
            )
2119
        );
2120
        $this->assertFalse($polymorphic, 'DataObjectTest_CEO->PreviousCompany is not polymorphic');
2121
2122
        // Team schema
2123
        $this->assertEquals(
2124
            'Favourite',
2125
            $schema->getRemoteJoinField(
2126
                DataObjectTest\Team::class,
2127
                'Fans',
2128
                'has_many',
2129
                $polymorphic
2130
            )
2131
        );
2132
        $this->assertTrue($polymorphic, 'DataObjectTest_Team->Fans is polymorphic');
2133
        $this->assertEquals(
2134
            'TeamID',
2135
            $schema->getRemoteJoinField(
2136
                DataObjectTest\Team::class,
2137
                'Comments',
2138
                'has_many',
2139
                $polymorphic
2140
            )
2141
        );
2142
        $this->assertFalse($polymorphic, 'DataObjectTest_Team->Comments is not polymorphic');
2143
    }
2144
2145
    public function testBelongsTo()
2146
    {
2147
        $company = new DataObjectTest\Company();
2148
        $ceo = new DataObjectTest\CEO();
2149
2150
        $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...
2151
        $company->write();
2152
        $ceo->write();
2153
2154
        // Test belongs_to assignment
2155
        $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...
2156
        $company->write();
2157
2158
        $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

2158
        $this->assertEquals($company->ID, $ceo->/** @scrutinizer ignore-call */ Company()->ID, 'belongs_to returns the right results.');
Loading history...
2159
2160
        // Test belongs_to can be infered via getNonReciprocalComponent
2161
        // Note: Will be returned as has_many since the belongs_to is ignored.
2162
        $this->assertListEquals(
2163
            [['Name' => 'New Company']],
2164
            $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

2164
            /** @scrutinizer ignore-type */ $ceo->inferReciprocalComponent(DataObjectTest\Company::class, 'CEO')
Loading history...
2165
        );
2166
2167
        // Test has_one to a belongs_to can be infered via getNonReciprocalComponent
2168
        $this->assertEquals(
2169
            $ceo->ID,
2170
            $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...
2171
        );
2172
2173
        // Test automatic creation of class where no assigment exists
2174
        $ceo = new DataObjectTest\CEO();
2175
        $ceo->write();
2176
2177
        $this->assertTrue(
2178
            $ceo->Company() instanceof DataObjectTest\Company,
2179
            'DataObjects across belongs_to relations are automatically created.'
2180
        );
2181
        $this->assertEquals($ceo->ID, $ceo->Company()->CEOID, 'Remote IDs are automatically set.');
2182
2183
        // Write object with components
2184
        $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...
2185
        $ceo->write(false, false, false, true);
2186
        $this->assertFalse(
2187
            $ceo->Company()->isInDB(),
2188
            'write() does not write belongs_to components to the database that do not already exist.'
2189
        );
2190
2191
        $newCEO = DataObject::get_by_id(DataObjectTest\CEO::class, $ceo->ID);
2192
        $this->assertEquals(
2193
            $ceo->Company()->ID,
2194
            $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

2194
            $newCEO->/** @scrutinizer ignore-call */ 
2195
                     Company()->ID,
Loading history...
2195
            'belongs_to can be retrieved from the database.'
2196
        );
2197
    }
2198
2199
    public function testBelongsToPolymorphic()
2200
    {
2201
        $company = new DataObjectTest\Company();
2202
        $ceo = new DataObjectTest\CEO();
2203
2204
        $company->write();
2205
        $ceo->write();
2206
2207
        // Test belongs_to assignment
2208
        $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...
2209
        $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...
2210
        $company->write();
2211
2212
        $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

2212
        $this->assertEquals($company->ID, $ceo->/** @scrutinizer ignore-call */ CompanyOwned()->ID, 'belongs_to returns the right results.');
Loading history...
2213
        $this->assertInstanceOf(
2214
            DataObjectTest\Company::class,
2215
            $ceo->CompanyOwned(),
2216
            'belongs_to returns the right results.'
2217
        );
2218
2219
        // Test automatic creation of class where no assignment exists
2220
        $ceo = new DataObjectTest\CEO();
2221
        $ceo->write();
2222
2223
        $this->assertTrue(
2224
            $ceo->CompanyOwned() instanceof DataObjectTest\Company,
2225
            'DataObjects across polymorphic belongs_to relations are automatically created.'
2226
        );
2227
        $this->assertEquals($ceo->ID, $ceo->CompanyOwned()->OwnerID, 'Remote IDs are automatically set.');
2228
        $this->assertInstanceOf($ceo->CompanyOwned()->OwnerClass, $ceo, 'Remote class is automatically set.');
2229
2230
        // Skip writing components that do not exist
2231
        $ceo->write(false, false, false, true);
2232
        $this->assertFalse(
2233
            $ceo->CompanyOwned()->isInDB(),
2234
            'write() does not write belongs_to components to the database that do not already exist.'
2235
        );
2236
2237
        $newCEO = DataObject::get_by_id(DataObjectTest\CEO::class, $ceo->ID);
2238
        $this->assertEquals(
2239
            $ceo->CompanyOwned()->ID,
2240
            $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

2240
            $newCEO->/** @scrutinizer ignore-call */ 
2241
                     CompanyOwned()->ID,
Loading history...
2241
            'polymorphic belongs_to can be retrieved from the database.'
2242
        );
2243
    }
2244
2245
    public function testInvalidate()
2246
    {
2247
        $this->expectException(LogicException::class);
2248
        $do = new DataObjectTest\Fixture();
2249
        $do->write();
2250
2251
        $do->delete();
2252
2253
        $do->delete(); // Prohibit invalid object manipulation
2254
        $do->write();
2255
        $do->duplicate();
2256
    }
2257
2258
    public function testToMap()
2259
    {
2260
        $obj = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
2261
2262
        $map = $obj->toMap();
2263
2264
        $this->assertArrayHasKey('ID', $map, 'Contains base fields');
2265
        $this->assertArrayHasKey('Title', $map, 'Contains fields from parent class');
2266
        $this->assertArrayHasKey('SubclassDatabaseField', $map, 'Contains fields from concrete class');
2267
2268
        $this->assertEquals(
2269
            $obj->ID,
2270
            $map['ID'],
2271
            'Contains values from base fields'
2272
        );
2273
        $this->assertEquals(
2274
            $obj->Title,
2275
            $map['Title'],
2276
            'Contains values from parent class fields'
2277
        );
2278
        $this->assertEquals(
2279
            $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...
2280
            $map['SubclassDatabaseField'],
2281
            'Contains values from concrete class fields'
2282
        );
2283
2284
        $newObj = new DataObjectTest\SubTeam();
0 ignored issues
show
Unused Code introduced by
The assignment to $newObj is dead and can be removed.
Loading history...
2285
        $this->assertArrayHasKey('Title', $map, 'Contains null fields');
2286
    }
2287
2288
    public function testIsEmpty()
2289
    {
2290
        $objEmpty = new DataObjectTest\Team();
2291
        $this->assertTrue($objEmpty->isEmpty(), 'New instance without populated defaults is empty');
2292
2293
        $objEmpty->Title = '0'; //
2294
        $this->assertFalse($objEmpty->isEmpty(), 'Zero value in attribute considered non-empty');
2295
    }
2296
2297
    public function testRelField()
2298
    {
2299
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
2300
        // Test traversal of a single has_one
2301
        $this->assertEquals("Team 1", $captain1->relField('FavouriteTeam.Title'));
2302
        // Test direct field access
2303
        $this->assertEquals("Captain", $captain1->relField('FirstName'));
2304
2305
        // Test empty link
2306
        $captain2 = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
2307
        $this->assertEmpty($captain2->relField('FavouriteTeam.Title'));
2308
        $this->assertNull($captain2->relField('FavouriteTeam.ReturnsNull'));
2309
        $this->assertNull($captain2->relField('FavouriteTeam.ReturnsNull.Title'));
2310
2311
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
2312
        // Test that we can traverse more than once, and that arbitrary methods are okay
2313
        $this->assertEquals("Team 1", $player->relField('Teams.First.Title'));
2314
2315
        $newPlayer = new DataObjectTest\Player();
2316
        $this->assertNull($newPlayer->relField('Teams.First.Title'));
2317
2318
        // Test that relField works on db field manipulations
2319
        $comment = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment3');
2320
        $this->assertEquals("PHIL IS A UNIQUE GUY, AND COMMENTS ON TEAM2", $comment->relField('Comment.UpperCase'));
2321
2322
        // relField throws exception on invalid properties
2323
        $this->expectException(LogicException::class);
2324
        $this->expectExceptionMessage("Not is not a relation/field on " . DataObjectTest\TeamComment::class);
2325
        $comment->relField('Not.A.Field');
2326
    }
2327
2328
    public function testRelObject()
2329
    {
2330
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
2331
2332
        // Test traversal of a single has_one
2333
        $this->assertInstanceOf(DBVarchar::class, $captain1->relObject('FavouriteTeam.Title'));
2334
        $this->assertEquals("Team 1", $captain1->relObject('FavouriteTeam.Title')->getValue());
2335
2336
        // Test empty link
2337
        $captain2 = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
2338
        $this->assertEmpty($captain2->relObject('FavouriteTeam.Title')->getValue());
2339
        $this->assertNull($captain2->relObject('FavouriteTeam.ReturnsNull.Title'));
2340
2341
        // Test direct field access
2342
        $this->assertInstanceOf(DBBoolean::class, $captain1->relObject('IsRetired'));
2343
        $this->assertEquals(1, $captain1->relObject('IsRetired')->getValue());
2344
2345
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
2346
        // Test that we can traverse more than once, and that arbitrary methods are okay
2347
        $this->assertInstanceOf(DBVarchar::class, $player->relObject('Teams.First.Title'));
2348
        $this->assertEquals("Team 1", $player->relObject('Teams.First.Title')->getValue());
2349
2350
        // relObject throws exception on invalid properties
2351
        $this->expectException(LogicException::class);
2352
        $this->expectExceptionMessage("Not is not a relation/field on " . DataObjectTest\Player::class);
2353
        $player->relObject('Not.A.Field');
2354
    }
2355
2356
    public function testLateStaticBindingStyle()
2357
    {
2358
        // Confirm that DataObjectTest_Player::get() operates as excepted
2359
        $this->assertEquals(4, DataObjectTest\Player::get()->count());
2360
        $this->assertInstanceOf(DataObjectTest\Player::class, DataObjectTest\Player::get()->first());
2361
2362
        // You can't pass arguments to LSB syntax - use the DataList methods instead.
2363
        $this->expectException(InvalidArgumentException::class);
2364
2365
        DataObjectTest\Player::get(null, "\"ID\" = 1");
2366
    }
2367
2368
    public function testBrokenLateStaticBindingStyle()
2369
    {
2370
        $this->expectException(\InvalidArgumentException::class);
2371
        // If you call DataObject::get() you have to pass a first argument
2372
        DataObject::get();
2373
    }
2374
2375
    public function testBigIntField()
2376
    {
2377
        $staff = new DataObjectTest\Staff();
2378
        $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...
2379
        $staff->write();
2380
        $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...
2381
    }
2382
2383
    public function testGetOneMissingValueReturnsNull()
2384
    {
2385
2386
        // Test that missing values return null
2387
        $this->assertEquals(null, DataObject::get_one(
2388
            DataObjectTest\TeamComment::class,
2389
            ['"DataObjectTest_TeamComment"."Name"' => 'does not exists']
2390
        ));
2391
    }
2392
2393
    public function testSetFieldWithArrayOnScalarOnlyField()
2394
    {
2395
        $this->expectException(InvalidArgumentException::class);
2396
        $do = Company::singleton();
2397
        $do->FoundationYear = '1984';
2398
        $do->FoundationYear = array('Amount' => 123, 'Currency' => 'CAD');
2399
        $this->assertEmpty($do->FoundationYear);
2400
    }
2401
2402
    public function testSetFieldWithArrayOnCompositeField()
2403
    {
2404
        $do = Company::singleton();
2405
        $do->SalaryCap = array('Amount' => 123456, 'Currency' => 'CAD');
2406
        $this->assertNotEmpty($do->SalaryCap);
2407
    }
2408
2409
    public function testWriteManipulationWithNonScalarValuesAllowed()
2410
    {
2411
        $do = DataObjectTest\MockDynamicAssignmentDataObject::create();
2412
        $do->write();
2413
2414
        $do->StaticScalarOnlyField = true;
2415
        $do->DynamicScalarOnlyField = false;
2416
        $do->DynamicField = true;
2417
2418
        $do->write();
2419
2420
        $this->assertTrue($do->StaticScalarOnlyField);
2421
        $this->assertFalse($do->DynamicScalarOnlyField);
2422
        $this->assertTrue($do->DynamicField);
2423
    }
2424
2425
    public function testWriteManipulationWithNonScalarValuesDisallowed()
2426
    {
2427
        $this->expectException(InvalidArgumentException::class);
2428
2429
        $do = DataObjectTest\MockDynamicAssignmentDataObject::create();
2430
        $do->write();
2431
2432
        $do->StaticScalarOnlyField = false;
2433
        $do->DynamicScalarOnlyField = true;
2434
        $do->DynamicField = false;
2435
2436
        $do->write();
2437
    }
2438
2439
    public function testRecursiveWrite()
2440
    {
2441
2442
        $root = $this->objFromFixture(TreeNode::class, 'root');
2443
        $child = $this->objFromFixture(TreeNode::class, 'child');
2444
        $grandchild = $this->objFromFixture(TreeNode::class, 'grandchild');
2445
2446
        // Create a cycle ... this will test that we can't create an infinite loop
2447
        $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...
2448
        $root->write();
2449
2450
        // Our count will have been set while loading our fixtures, let's reset eveything back to 0
2451
        TreeNode::singleton()->resetCounts();
2452
        $root = TreeNode::get()->byID($root->ID);
2453
        $child = TreeNode::get()->byID($child->ID);
2454
        $grandchild = TreeNode::get()->byID($grandchild->ID);
2455
        $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...
2456
        $this->assertEquals(0, $child->WriteCount, 'Child node write count has been reset');
2457
        $this->assertEquals(0, $grandchild->WriteCount, 'Grand Child node write count has been reset');
2458
2459
        // Trigger a recursive write of the grand children
2460
        $grandchild->write(false, false, false, true);
2461
2462
        // Reload the DataObject from the DB to get the new Write Counts
2463
        $root = TreeNode::get()->byID($root->ID);
2464
        $child = TreeNode::get()->byID($child->ID);
2465
        $grandchild = TreeNode::get()->byID($grandchild->ID);
2466
2467
        $this->assertEquals(
2468
            1,
2469
            $grandchild->WriteCount,
2470
            'Grand child has been written once because write was directly called on it'
2471
        );
2472
        $this->assertEquals(
2473
            1,
2474
            $child->WriteCount,
2475
            'Child should has been written once because it is directly related to grand child'
2476
        );
2477
        $this->assertEquals(
2478
            1,
2479
            $root->WriteCount,
2480
            'Root should have been written once because it is indirectly related to grand child'
2481
        );
2482
    }
2483
2484
    public function testShallowRecursiveWrite()
2485
    {
2486
        $root = $this->objFromFixture(TreeNode::class, 'root');
2487
        $child = $this->objFromFixture(TreeNode::class, 'child');
2488
        $grandchild = $this->objFromFixture(TreeNode::class, 'grandchild');
2489
2490
        // Create a cycle ... this will test that we can't create an infinite loop
2491
        $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...
2492
        $root->write();
2493
2494
        // Our count will have been set while loading our fixtures, let's reset eveything back to 0
2495
        TreeNode::singleton()->resetCounts();
2496
        $root = TreeNode::get()->byID($root->ID);
2497
        $child = TreeNode::get()->byID($child->ID);
2498
        $grandchild = TreeNode::get()->byID($grandchild->ID);
2499
        $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...
2500
        $this->assertEquals(0, $child->WriteCount);
2501
        $this->assertEquals(0, $grandchild->WriteCount);
2502
2503
        // Recursively only affect component that have been loaded
2504
        $grandchild->write(false, false, false, ['recursive' => false]);
2505
2506
        // Reload the DataObject from the DB to get the new Write Counts
2507
        $root = TreeNode::get()->byID($root->ID);
2508
        $child = TreeNode::get()->byID($child->ID);
2509
        $grandchild = TreeNode::get()->byID($grandchild->ID);
2510
2511
        $this->assertEquals(
2512
            1,
2513
            $grandchild->WriteCount,
2514
            'Grand child was written once because write was directly called on it'
2515
        );
2516
        $this->assertEquals(
2517
            1,
2518
            $child->WriteCount,
2519
            'Child was written once because it is directly related grand child'
2520
        );
2521
        $this->assertEquals(
2522
            0,
2523
            $root->WriteCount,
2524
            'Root is 2 step remove from grand children. It was not written on a shallow recursive write.'
2525
        );
2526
    }
2527
}
2528