Passed
Push — less-original ( c19c33...fdf3fc )
by Sam
05:22
created

testForceChangeCantBeCancelledUntilWrite()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 20
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 14
nc 1
nop 0
dl 0
loc 20
rs 9.7998
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\ORM\Tests;
4
5
use InvalidArgumentException;
6
use LogicException;
7
use SilverStripe\Core\Config\Config;
8
use SilverStripe\Dev\SapphireTest;
9
use SilverStripe\i18n\i18n;
10
use SilverStripe\ORM\Connect\MySQLDatabase;
11
use SilverStripe\ORM\DataObject;
12
use SilverStripe\ORM\DataObjectSchema;
13
use SilverStripe\ORM\DB;
14
use SilverStripe\ORM\FieldType\DBBoolean;
15
use SilverStripe\ORM\FieldType\DBDatetime;
16
use SilverStripe\ORM\FieldType\DBField;
17
use SilverStripe\ORM\FieldType\DBPolymorphicForeignKey;
18
use SilverStripe\ORM\FieldType\DBVarchar;
19
use SilverStripe\ORM\ManyManyList;
20
use SilverStripe\ORM\Tests\DataObjectTest\Player;
21
use SilverStripe\View\ViewableData;
22
use stdClass;
23
24
class DataObjectTest extends SapphireTest
25
{
26
27
    protected static $fixture_file = 'DataObjectTest.yml';
28
29
    /**
30
     * Standard set of dataobject test classes
31
     *
32
     * @var array
33
     */
34
    public static $extra_data_objects = array(
35
        DataObjectTest\Team::class,
36
        DataObjectTest\Fixture::class,
37
        DataObjectTest\SubTeam::class,
38
        DataObjectTest\OtherSubclassWithSameField::class,
39
        DataObjectTest\FieldlessTable::class,
40
        DataObjectTest\FieldlessSubTable::class,
41
        DataObjectTest\ValidatedObject::class,
42
        DataObjectTest\Player::class,
43
        DataObjectTest\TeamComment::class,
44
        DataObjectTest\EquipmentCompany::class,
45
        DataObjectTest\SubEquipmentCompany::class,
46
        DataObjectTest\ExtendedTeamComment::class,
47
        DataObjectTest\Company::class,
48
        DataObjectTest\Staff::class,
49
        DataObjectTest\CEO::class,
50
        DataObjectTest\Fan::class,
51
        DataObjectTest\Play::class,
52
        DataObjectTest\Ploy::class,
53
        DataObjectTest\Bogey::class,
54
        DataObjectTest\Sortable::class,
55
        DataObjectTest\Bracket::class,
56
        DataObjectTest\RelationParent::class,
57
        DataObjectTest\RelationChildFirst::class,
58
        DataObjectTest\RelationChildSecond::class,
59
    );
60
61
    public static function getExtraDataObjects()
62
    {
63
        return array_merge(
64
            DataObjectTest::$extra_data_objects,
65
            ManyManyListTest::$extra_data_objects
66
        );
67
    }
68
69
    public function testDb()
70
    {
71
        $schema = DataObject::getSchema();
72
        $dbFields = $schema->fieldSpecs(DataObjectTest\TeamComment::class);
73
74
        // Assert fields are included
75
        $this->assertArrayHasKey('Name', $dbFields);
76
77
        // Assert the base fields are included
78
        $this->assertArrayHasKey('Created', $dbFields);
79
        $this->assertArrayHasKey('LastEdited', $dbFields);
80
        $this->assertArrayHasKey('ClassName', $dbFields);
81
        $this->assertArrayHasKey('ID', $dbFields);
82
83
        // Assert that the correct field type is returned when passing a field
84
        $this->assertEquals('Varchar', $schema->fieldSpec(DataObjectTest\TeamComment::class, 'Name'));
85
        $this->assertEquals('Text', $schema->fieldSpec(DataObjectTest\TeamComment::class, 'Comment'));
86
87
        // Test with table required
88
        $this->assertEquals(
89
            DataObjectTest\TeamComment::class . '.Varchar',
90
            $schema->fieldSpec(DataObjectTest\TeamComment::class, 'Name', DataObjectSchema::INCLUDE_CLASS)
91
        );
92
        $this->assertEquals(
93
            DataObjectTest\TeamComment::class . '.Text',
94
            $schema->fieldSpec(DataObjectTest\TeamComment::class, 'Comment', DataObjectSchema::INCLUDE_CLASS)
95
        );
96
        $dbFields = $schema->fieldSpecs(DataObjectTest\ExtendedTeamComment::class);
97
98
        // fixed fields are still included in extended classes
99
        $this->assertArrayHasKey('Created', $dbFields);
100
        $this->assertArrayHasKey('LastEdited', $dbFields);
101
        $this->assertArrayHasKey('ClassName', $dbFields);
102
        $this->assertArrayHasKey('ID', $dbFields);
103
104
        // Assert overloaded fields have correct data type
105
        $this->assertEquals('HTMLText', $schema->fieldSpec(DataObjectTest\ExtendedTeamComment::class, 'Comment'));
106
        $this->assertEquals(
107
            'HTMLText',
108
            $dbFields['Comment'],
109
            'Calls to DataObject::db without a field specified return correct data types'
110
        );
111
112
        // assertEquals doesn't verify the order of array elements, so access keys manually to check order:
113
        // expected: array('Name' => 'Varchar', 'Comment' => 'HTMLText')
114
        $this->assertEquals(
115
            array(
116
                'Name',
117
                'Comment'
118
            ),
119
            array_slice(array_keys($dbFields), 4, 2),
120
            'DataObject::db returns fields in correct order'
121
        );
122
    }
123
124
    public function testConstructAcceptsValues()
125
    {
126
        // Values can be an array...
127
        $player = new DataObjectTest\Player(
128
            array(
129
                'FirstName' => 'James',
130
                'Surname' => 'Smith'
131
            )
132
        );
133
134
        $this->assertEquals('James', $player->FirstName);
135
        $this->assertEquals('Smith', $player->Surname);
136
137
        // ... or a stdClass inst
138
        $data = new stdClass();
139
        $data->FirstName = 'John';
140
        $data->Surname = 'Doe';
141
        $player = new DataObjectTest\Player($data);
0 ignored issues
show
Bug introduced by
$data of type stdClass is incompatible with the type null|array 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

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

421
        $this->assertEquals($team1ID, $captain1->/** @scrutinizer ignore-call */ FavouriteTeam()->ID);
Loading history...
422
423
        // Test that getNonReciprocalComponent can find has_one from the has_many end
424
        $this->assertEquals(
425
            $team1ID,
426
            $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...
427
        );
428
429
        // Check entity with polymorphic has-one
430
        $fan1 = $this->objFromFixture(DataObjectTest\Fan::class, "fan1");
431
        $this->assertTrue((bool)$fan1->hasValue('Favourite'));
432
433
        // There will be fields named (relname)ID and (relname)Class for polymorphic
434
        // entities
435
        $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...
436
        $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...
437
438
        // There will be a method called $obj->relname() that returns the object itself
439
        $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

439
        /** @scrutinizer ignore-call */ 
440
        $favourite = $fan1->Favourite();
Loading history...
440
        $this->assertEquals($team1ID, $favourite->ID);
441
        $this->assertInstanceOf(DataObjectTest\Team::class, $favourite);
442
443
        // check behaviour of dbObject with polymorphic relations
444
        $favouriteDBObject = $fan1->dbObject('Favourite');
445
        $favouriteValue = $favouriteDBObject->getValue();
446
        $this->assertInstanceOf(DBPolymorphicForeignKey::class, $favouriteDBObject);
447
        $this->assertEquals($favourite->ID, $favouriteValue->ID);
448
        $this->assertEquals($favourite->ClassName, $favouriteValue->ClassName);
449
    }
450
451
    public function testLimitAndCount()
452
    {
453
        $players = DataObject::get(DataObjectTest\Player::class);
454
455
        // There's 4 records in total
456
        $this->assertEquals(4, $players->count());
457
458
        // Testing "##, ##" syntax
459
        $this->assertEquals(4, $players->limit(20)->count());
460
        $this->assertEquals(4, $players->limit(20, 0)->count());
461
        $this->assertEquals(0, $players->limit(20, 20)->count());
462
        $this->assertEquals(2, $players->limit(2, 0)->count());
463
        $this->assertEquals(1, $players->limit(5, 3)->count());
464
    }
465
466
    public function testWriteNoChangesDoesntUpdateLastEdited()
467
    {
468
        // set mock now so we can be certain of LastEdited time for our test
469
        DBDatetime::set_mock_now('2017-01-01 00:00:00');
470
        $obj = new Player();
471
        $obj->FirstName = 'Test';
472
        $obj->Surname = 'Plater';
473
        $obj->Email = '[email protected]';
474
        $obj->write();
475
        $this->assertEquals('2017-01-01 00:00:00', $obj->LastEdited);
476
        $writtenObj = Player::get()->byID($obj->ID);
477
        $this->assertEquals('2017-01-01 00:00:00', $writtenObj->LastEdited);
478
479
        // set mock now so we get a new LastEdited if, for some reason, it's updated
480
        DBDatetime::set_mock_now('2017-02-01 00:00:00');
481
        $writtenObj->write();
482
        $this->assertEquals('2017-01-01 00:00:00', $writtenObj->LastEdited);
483
        $this->assertEquals($obj->ID, $writtenObj->ID);
484
485
        $reWrittenObj = Player::get()->byID($writtenObj->ID);
486
        $this->assertEquals('2017-01-01 00:00:00', $reWrittenObj->LastEdited);
487
    }
488
489
    /**
490
     * Test writing of database columns which don't correlate to a DBField,
491
     * e.g. all relation fields on has_one/has_many like "ParentID".
492
     */
493
    public function testWritePropertyWithoutDBField()
494
    {
495
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
496
        $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...
497
        $obj->write();
498
499
        // reload the page from the database
500
        $savedObj = DataObject::get_by_id(DataObjectTest\Player::class, $obj->ID);
501
        $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...
502
503
        // Test with porymorphic relation
504
        $obj2 = $this->objFromFixture(DataObjectTest\Fan::class, "fan1");
505
        $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...
506
        $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...
507
        $obj2->write();
508
509
        $savedObj2 = DataObject::get_by_id(DataObjectTest\Fan::class, $obj2->ID);
510
        $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...
511
        $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...
512
    }
513
514
    /**
515
     * Test has many relationships
516
     *   - Test getComponents() gets the ComponentSet of the other side of the relation
517
     *   - Test the IDs on the DataObjects are set correctly
518
     */
519
    public function testHasManyRelationships()
520
    {
521
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
522
523
        // Test getComponents() gets the ComponentSet of the other side of the relation
524
        $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

524
        $this->assertTrue($team1->/** @scrutinizer ignore-call */ Comments()->count() == 2);
Loading history...
525
526
        $team1Comments = [
527
            ['Comment' => 'This is a team comment by Joe'],
528
            ['Comment' => 'This is a team comment by Bob'],
529
        ];
530
531
        // Test the IDs on the DataObjects are set correctly
532
        $this->assertListEquals($team1Comments, $team1->Comments());
533
534
        // Test that has_many can be infered from the has_one via getNonReciprocalComponent
535
        $this->assertListEquals(
536
            $team1Comments,
537
            $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

537
            /** @scrutinizer ignore-type */ $team1->inferReciprocalComponent(DataObjectTest\TeamComment::class, 'Team')
Loading history...
538
        );
539
540
        // Test that we can add and remove items that already exist in the database
541
        $newComment = new DataObjectTest\TeamComment();
542
        $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...
543
        $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...
544
        $newComment->write();
545
        $team1->Comments()->add($newComment);
546
        $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...
547
548
        $comment1 = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment1');
549
        $comment2 = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment2');
550
        $team1->Comments()->remove($comment2);
551
552
        $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

552
        $team1CommentIDs = $team1->Comments()->/** @scrutinizer ignore-call */ sort('ID')->column('ID');
Loading history...
553
        $this->assertEquals(array($comment1->ID, $newComment->ID), $team1CommentIDs);
554
555
        // Test that removing an item from a list doesn't remove it from the same
556
        // relation belonging to a different object
557
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
558
        $team2 = $this->objFromFixture(DataObjectTest\Team::class, 'team2');
559
        $team2->Comments()->remove($comment1);
560
        $team1CommentIDs = $team1->Comments()->sort('ID')->column('ID');
561
        $this->assertEquals(array($comment1->ID, $newComment->ID), $team1CommentIDs);
562
    }
563
564
565
    /**
566
     * Test has many relationships against polymorphic has_one fields
567
     *   - Test getComponents() gets the ComponentSet of the other side of the relation
568
     *   - Test the IDs on the DataObjects are set correctly
569
     */
570
    public function testHasManyPolymorphicRelationships()
571
    {
572
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
573
574
        // Test getComponents() gets the ComponentSet of the other side of the relation
575
        $this->assertTrue($team1->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

575
        $this->assertTrue($team1->/** @scrutinizer ignore-call */ Fans()->count() == 2);
Loading history...
576
577
        // Test the IDs/Classes on the DataObjects are set correctly
578
        foreach ($team1->Fans() as $fan) {
579
            $this->assertEquals($team1->ID, $fan->FavouriteID, 'Fan has the correct FavouriteID');
580
            $this->assertEquals(DataObjectTest\Team::class, $fan->FavouriteClass, 'Fan has the correct FavouriteClass');
581
        }
582
583
        // Test that we can add and remove items that already exist in the database
584
        $newFan = new DataObjectTest\Fan();
585
        $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...
586
        $newFan->write();
587
        $team1->Fans()->add($newFan);
588
        $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...
589
        $this->assertEquals(
590
            DataObjectTest\Team::class,
591
            $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...
592
            'Newly created fan has the correct FavouriteClass'
593
        );
594
595
        $fan1 = $this->objFromFixture(DataObjectTest\Fan::class, 'fan1');
596
        $fan3 = $this->objFromFixture(DataObjectTest\Fan::class, 'fan3');
597
        $team1->Fans()->remove($fan3);
598
599
        $team1FanIDs = $team1->Fans()->sort('ID')->column('ID');
600
        $this->assertEquals(array($fan1->ID, $newFan->ID), $team1FanIDs);
601
602
        // Test that removing an item from a list doesn't remove it from the same
603
        // relation belonging to a different object
604
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
605
        $player1 = $this->objFromFixture(DataObjectTest\Player::class, 'player1');
606
        $player1->Fans()->remove($fan1);
607
        $team1FanIDs = $team1->Fans()->sort('ID')->column('ID');
608
        $this->assertEquals(array($fan1->ID, $newFan->ID), $team1FanIDs);
609
    }
610
611
612
    public function testHasOneRelationship()
613
    {
614
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
615
        $player1 = $this->objFromFixture(DataObjectTest\Player::class, 'player1');
616
        $player2 = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
617
        $fan1 = $this->objFromFixture(DataObjectTest\Fan::class, 'fan1');
618
619
        // Test relation probing
620
        $this->assertFalse((bool)$team1->hasValue('Captain', null, false));
621
        $this->assertFalse((bool)$team1->hasValue('CaptainID', null, false));
622
623
        // Add a captain to team 1
624
        $team1->setField('CaptainID', $player1->ID);
625
        $team1->write();
626
627
        $this->assertTrue((bool)$team1->hasValue('Captain', null, false));
628
        $this->assertTrue((bool)$team1->hasValue('CaptainID', null, false));
629
630
        $this->assertEquals(
631
            $player1->ID,
632
            $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

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

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

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

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

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

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

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

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

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

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

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

2090
            $newCEO->/** @scrutinizer ignore-call */ 
2091
                     CompanyOwned()->ID,
Loading history...
2091
            'polymorphic belongs_to can be retrieved from the database.'
2092
        );
2093
    }
2094
2095
    /**
2096
     * @expectedException LogicException
2097
     */
2098
    public function testInvalidate()
2099
    {
2100
        $do = new DataObjectTest\Fixture();
2101
        $do->write();
2102
2103
        $do->delete();
2104
2105
        $do->delete(); // Prohibit invalid object manipulation
2106
        $do->write();
2107
        $do->duplicate();
2108
    }
2109
2110
    public function testToMap()
2111
    {
2112
        $obj = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
2113
2114
        $map = $obj->toMap();
2115
2116
        $this->assertArrayHasKey('ID', $map, 'Contains base fields');
2117
        $this->assertArrayHasKey('Title', $map, 'Contains fields from parent class');
2118
        $this->assertArrayHasKey('SubclassDatabaseField', $map, 'Contains fields from concrete class');
2119
2120
        $this->assertEquals(
2121
            $obj->ID,
2122
            $map['ID'],
2123
            'Contains values from base fields'
2124
        );
2125
        $this->assertEquals(
2126
            $obj->Title,
2127
            $map['Title'],
2128
            'Contains values from parent class fields'
2129
        );
2130
        $this->assertEquals(
2131
            $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...
2132
            $map['SubclassDatabaseField'],
2133
            'Contains values from concrete class fields'
2134
        );
2135
2136
        $newObj = new DataObjectTest\SubTeam();
0 ignored issues
show
Unused Code introduced by
The assignment to $newObj is dead and can be removed.
Loading history...
2137
        $this->assertArrayHasKey('Title', $map, 'Contains null fields');
2138
    }
2139
2140
    public function testIsEmpty()
2141
    {
2142
        $objEmpty = new DataObjectTest\Team();
2143
        $this->assertTrue($objEmpty->isEmpty(), 'New instance without populated defaults is empty');
2144
2145
        $objEmpty->Title = '0'; //
2146
        $this->assertFalse($objEmpty->isEmpty(), 'Zero value in attribute considered non-empty');
2147
    }
2148
2149
    public function testRelField()
2150
    {
2151
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
2152
        // Test traversal of a single has_one
2153
        $this->assertEquals("Team 1", $captain1->relField('FavouriteTeam.Title'));
2154
        // Test direct field access
2155
        $this->assertEquals("Captain", $captain1->relField('FirstName'));
2156
2157
        // Test empty link
2158
        $captain2 = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
2159
        $this->assertEmpty($captain2->relField('FavouriteTeam.Title'));
2160
        $this->assertNull($captain2->relField('FavouriteTeam.ReturnsNull'));
2161
        $this->assertNull($captain2->relField('FavouriteTeam.ReturnsNull.Title'));
2162
2163
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
2164
        // Test that we can traverse more than once, and that arbitrary methods are okay
2165
        $this->assertEquals("Team 1", $player->relField('Teams.First.Title'));
2166
2167
        $newPlayer = new DataObjectTest\Player();
2168
        $this->assertNull($newPlayer->relField('Teams.First.Title'));
2169
2170
        // Test that relField works on db field manipulations
2171
        $comment = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment3');
2172
        $this->assertEquals("PHIL IS A UNIQUE GUY, AND COMMENTS ON TEAM2", $comment->relField('Comment.UpperCase'));
2173
2174
        // relField throws exception on invalid properties
2175
        $this->expectException(LogicException::class);
2176
        $this->expectExceptionMessage("Not is not a relation/field on " . DataObjectTest\TeamComment::class);
2177
        $comment->relField('Not.A.Field');
2178
    }
2179
2180
    public function testRelObject()
2181
    {
2182
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
2183
2184
        // Test traversal of a single has_one
2185
        $this->assertInstanceOf(DBVarchar::class, $captain1->relObject('FavouriteTeam.Title'));
2186
        $this->assertEquals("Team 1", $captain1->relObject('FavouriteTeam.Title')->getValue());
2187
2188
        // Test empty link
2189
        $captain2 = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
2190
        $this->assertEmpty($captain2->relObject('FavouriteTeam.Title')->getValue());
2191
        $this->assertNull($captain2->relObject('FavouriteTeam.ReturnsNull.Title'));
2192
2193
        // Test direct field access
2194
        $this->assertInstanceOf(DBBoolean::class, $captain1->relObject('IsRetired'));
2195
        $this->assertEquals(1, $captain1->relObject('IsRetired')->getValue());
2196
2197
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
2198
        // Test that we can traverse more than once, and that arbitrary methods are okay
2199
        $this->assertInstanceOf(DBVarchar::class, $player->relObject('Teams.First.Title'));
2200
        $this->assertEquals("Team 1", $player->relObject('Teams.First.Title')->getValue());
2201
2202
        // relObject throws exception on invalid properties
2203
        $this->expectException(LogicException::class);
2204
        $this->expectExceptionMessage("Not is not a relation/field on " . DataObjectTest\Player::class);
2205
        $player->relObject('Not.A.Field');
2206
    }
2207
2208
    public function testLateStaticBindingStyle()
2209
    {
2210
        // Confirm that DataObjectTest_Player::get() operates as excepted
2211
        $this->assertEquals(4, DataObjectTest\Player::get()->count());
2212
        $this->assertInstanceOf(DataObjectTest\Player::class, DataObjectTest\Player::get()->first());
2213
2214
        // You can't pass arguments to LSB syntax - use the DataList methods instead.
2215
        $this->expectException(InvalidArgumentException::class);
2216
2217
        DataObjectTest\Player::get(null, "\"ID\" = 1");
2218
    }
2219
2220
    /**
2221
     * @expectedException \InvalidArgumentException
2222
     */
2223
    public function testBrokenLateStaticBindingStyle()
2224
    {
2225
        // If you call DataObject::get() you have to pass a first argument
2226
        DataObject::get();
2227
    }
2228
2229
    public function testBigIntField()
2230
    {
2231
        $staff = new DataObjectTest\Staff();
2232
        $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...
2233
        $staff->write();
2234
        $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...
2235
    }
2236
2237
    public function testGetOneMissingValueReturnsNull()
2238
    {
2239
2240
        // Test that missing values return null
2241
        $this->assertEquals(null, DataObject::get_one(
2242
            DataObjectTest\TeamComment::class,
2243
            ['"DataObjectTest_TeamComment"."Name"' => 'does not exists']
2244
        ));
2245
    }
2246
}
2247