Passed
Push — search-or-choose-page ( 2890bb...a9ad34 )
by
unknown
08:26
created

testWriteManipulationWithNonScalarValuesAllowed()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

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

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

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

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

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

1654
        /** @scrutinizer ignore-call */ 
1655
        $equipmentSuppliers = $team->EquipmentSuppliers();
Loading history...
1655
1656
        // Check that DataObject::many_many() works as expected
1657
        $manyManyComponent = DataObject::getSchema()->manyManyComponent(DataObjectTest\Team::class, 'Sponsors');
1658
        $this->assertEquals(ManyManyList::class, $manyManyComponent['relationClass']);
1659
        $this->assertEquals(
1660
            DataObjectTest\Team::class,
1661
            $manyManyComponent['parentClass'],
1662
            'DataObject::many_many() didn\'t find the correct base class'
1663
        );
1664
        $this->assertEquals(
1665
            DataObjectTest\EquipmentCompany::class,
1666
            $manyManyComponent['childClass'],
1667
            'DataObject::many_many() didn\'t find the correct target class for the relation'
1668
        );
1669
        $this->assertEquals(
1670
            'DataObjectTest_EquipmentCompany_SponsoredTeams',
1671
            $manyManyComponent['join'],
1672
            'DataObject::many_many() didn\'t find the correct relation table'
1673
        );
1674
        $this->assertEquals('DataObjectTest_TeamID', $manyManyComponent['parentField']);
1675
        $this->assertEquals('DataObjectTest_EquipmentCompanyID', $manyManyComponent['childField']);
1676
1677
        // Check that ManyManyList still works
1678
        $this->assertEquals(2, $sponsors->count(), 'Rows are missing from relation');
1679
        $this->assertEquals(1, $equipmentSuppliers->count(), 'Rows are missing from relation');
1680
1681
        // Check everything works when no relation is present
1682
        $teamWithoutSponsor = $this->objFromFixture(DataObjectTest\Team::class, 'team3');
1683
        $this->assertInstanceOf(ManyManyList::class, $teamWithoutSponsor->Sponsors());
1684
        $this->assertEquals(0, $teamWithoutSponsor->Sponsors()->count());
1685
1686
        // Test that belongs_many_many can be infered from with getNonReciprocalComponent
1687
        $this->assertListEquals(
1688
            [
1689
                ['Name' => 'Company corp'],
1690
                ['Name' => 'Team co.'],
1691
            ],
1692
            $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

1692
            /** @scrutinizer ignore-type */ $team->inferReciprocalComponent(DataObjectTest\EquipmentCompany::class, 'SponsoredTeams')
Loading history...
1693
        );
1694
1695
        // Test that many_many can be infered from getNonReciprocalComponent
1696
        $this->assertListEquals(
1697
            [
1698
                ['Title' => 'Team 1'],
1699
                ['Title' => 'Team 2'],
1700
                ['Title' => 'Subteam 1'],
1701
            ],
1702
            $company2->inferReciprocalComponent(DataObjectTest\Team::class, 'Sponsors')
1703
        );
1704
1705
        // Check many_many_extraFields still works
1706
        $equipmentCompany = $this->objFromFixture(DataObjectTest\EquipmentCompany::class, 'equipmentcompany1');
1707
        $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

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

1815
        $player = $newTeam->/** @scrutinizer ignore-call */ Players()->first();
Loading history...
1816
        $this->assertEquals('Sam', $player->FirstName);
1817
        $this->assertEquals("Prop", $player->Position);
1818
1819
        // Check that ordering a many-many relation by an aggregate column doesn't fail
1820
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
1821
        $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

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

2129
        $this->assertEquals($company->ID, $ceo->/** @scrutinizer ignore-call */ Company()->ID, 'belongs_to returns the right results.');
Loading history...
2130
2131
        // Test belongs_to can be infered via getNonReciprocalComponent
2132
        // Note: Will be returned as has_many since the belongs_to is ignored.
2133
        $this->assertListEquals(
2134
            [['Name' => 'New Company']],
2135
            $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

2135
            /** @scrutinizer ignore-type */ $ceo->inferReciprocalComponent(DataObjectTest\Company::class, 'CEO')
Loading history...
2136
        );
2137
2138
        // Test has_one to a belongs_to can be infered via getNonReciprocalComponent
2139
        $this->assertEquals(
2140
            $ceo->ID,
2141
            $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...
2142
        );
2143
2144
        // Test automatic creation of class where no assigment exists
2145
        $ceo = new DataObjectTest\CEO();
2146
        $ceo->write();
2147
2148
        $this->assertTrue(
2149
            $ceo->Company() instanceof DataObjectTest\Company,
2150
            'DataObjects across belongs_to relations are automatically created.'
2151
        );
2152
        $this->assertEquals($ceo->ID, $ceo->Company()->CEOID, 'Remote IDs are automatically set.');
2153
2154
        // Write object with components
2155
        $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...
2156
        $ceo->write(false, false, false, true);
2157
        $this->assertTrue($ceo->Company()->isInDB(), 'write() writes belongs_to components to the database.');
2158
2159
        $newCEO = DataObject::get_by_id(DataObjectTest\CEO::class, $ceo->ID);
2160
        $this->assertEquals(
2161
            $ceo->Company()->ID,
2162
            $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

2162
            $newCEO->/** @scrutinizer ignore-call */ 
2163
                     Company()->ID,
Loading history...
2163
            'belongs_to can be retrieved from the database.'
2164
        );
2165
    }
2166
2167
    public function testBelongsToPolymorphic()
2168
    {
2169
        $company = new DataObjectTest\Company();
2170
        $ceo = new DataObjectTest\CEO();
2171
2172
        $company->write();
2173
        $ceo->write();
2174
2175
        // Test belongs_to assignment
2176
        $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...
2177
        $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...
2178
        $company->write();
2179
2180
        $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

2180
        $this->assertEquals($company->ID, $ceo->/** @scrutinizer ignore-call */ CompanyOwned()->ID, 'belongs_to returns the right results.');
Loading history...
2181
        $this->assertInstanceOf(
2182
            DataObjectTest\Company::class,
2183
            $ceo->CompanyOwned(),
2184
            'belongs_to returns the right results.'
2185
        );
2186
2187
        // Test automatic creation of class where no assigment exists
2188
        $ceo = new DataObjectTest\CEO();
2189
        $ceo->write();
2190
2191
        $this->assertTrue(
2192
            $ceo->CompanyOwned() instanceof DataObjectTest\Company,
2193
            'DataObjects across polymorphic belongs_to relations are automatically created.'
2194
        );
2195
        $this->assertEquals($ceo->ID, $ceo->CompanyOwned()->OwnerID, 'Remote IDs are automatically set.');
2196
        $this->assertInstanceOf($ceo->CompanyOwned()->OwnerClass, $ceo, 'Remote class is automatically  set');
2197
2198
        // Write object with components
2199
        $ceo->write(false, false, false, true);
2200
        $this->assertTrue($ceo->CompanyOwned()->isInDB(), 'write() writes belongs_to components to the database.');
2201
2202
        $newCEO = DataObject::get_by_id(DataObjectTest\CEO::class, $ceo->ID);
2203
        $this->assertEquals(
2204
            $ceo->CompanyOwned()->ID,
2205
            $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

2205
            $newCEO->/** @scrutinizer ignore-call */ 
2206
                     CompanyOwned()->ID,
Loading history...
2206
            'polymorphic belongs_to can be retrieved from the database.'
2207
        );
2208
    }
2209
2210
    /**
2211
     * @expectedException LogicException
2212
     */
2213
    public function testInvalidate()
2214
    {
2215
        $do = new DataObjectTest\Fixture();
2216
        $do->write();
2217
2218
        $do->delete();
2219
2220
        $do->delete(); // Prohibit invalid object manipulation
2221
        $do->write();
2222
        $do->duplicate();
2223
    }
2224
2225
    public function testToMap()
2226
    {
2227
        $obj = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
2228
2229
        $map = $obj->toMap();
2230
2231
        $this->assertArrayHasKey('ID', $map, 'Contains base fields');
2232
        $this->assertArrayHasKey('Title', $map, 'Contains fields from parent class');
2233
        $this->assertArrayHasKey('SubclassDatabaseField', $map, 'Contains fields from concrete class');
2234
2235
        $this->assertEquals(
2236
            $obj->ID,
2237
            $map['ID'],
2238
            'Contains values from base fields'
2239
        );
2240
        $this->assertEquals(
2241
            $obj->Title,
2242
            $map['Title'],
2243
            'Contains values from parent class fields'
2244
        );
2245
        $this->assertEquals(
2246
            $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...
2247
            $map['SubclassDatabaseField'],
2248
            'Contains values from concrete class fields'
2249
        );
2250
2251
        $newObj = new DataObjectTest\SubTeam();
0 ignored issues
show
Unused Code introduced by
The assignment to $newObj is dead and can be removed.
Loading history...
2252
        $this->assertArrayHasKey('Title', $map, 'Contains null fields');
2253
    }
2254
2255
    public function testIsEmpty()
2256
    {
2257
        $objEmpty = new DataObjectTest\Team();
2258
        $this->assertTrue($objEmpty->isEmpty(), 'New instance without populated defaults is empty');
2259
2260
        $objEmpty->Title = '0'; //
2261
        $this->assertFalse($objEmpty->isEmpty(), 'Zero value in attribute considered non-empty');
2262
    }
2263
2264
    public function testRelField()
2265
    {
2266
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
2267
        // Test traversal of a single has_one
2268
        $this->assertEquals("Team 1", $captain1->relField('FavouriteTeam.Title'));
2269
        // Test direct field access
2270
        $this->assertEquals("Captain", $captain1->relField('FirstName'));
2271
2272
        // Test empty link
2273
        $captain2 = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
2274
        $this->assertEmpty($captain2->relField('FavouriteTeam.Title'));
2275
        $this->assertNull($captain2->relField('FavouriteTeam.ReturnsNull'));
2276
        $this->assertNull($captain2->relField('FavouriteTeam.ReturnsNull.Title'));
2277
2278
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
2279
        // Test that we can traverse more than once, and that arbitrary methods are okay
2280
        $this->assertEquals("Team 1", $player->relField('Teams.First.Title'));
2281
2282
        $newPlayer = new DataObjectTest\Player();
2283
        $this->assertNull($newPlayer->relField('Teams.First.Title'));
2284
2285
        // Test that relField works on db field manipulations
2286
        $comment = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment3');
2287
        $this->assertEquals("PHIL IS A UNIQUE GUY, AND COMMENTS ON TEAM2", $comment->relField('Comment.UpperCase'));
2288
2289
        // relField throws exception on invalid properties
2290
        $this->expectException(LogicException::class);
2291
        $this->expectExceptionMessage("Not is not a relation/field on " . DataObjectTest\TeamComment::class);
2292
        $comment->relField('Not.A.Field');
2293
    }
2294
2295
    public function testRelObject()
2296
    {
2297
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
2298
2299
        // Test traversal of a single has_one
2300
        $this->assertInstanceOf(DBVarchar::class, $captain1->relObject('FavouriteTeam.Title'));
2301
        $this->assertEquals("Team 1", $captain1->relObject('FavouriteTeam.Title')->getValue());
2302
2303
        // Test empty link
2304
        $captain2 = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
2305
        $this->assertEmpty($captain2->relObject('FavouriteTeam.Title')->getValue());
2306
        $this->assertNull($captain2->relObject('FavouriteTeam.ReturnsNull.Title'));
2307
2308
        // Test direct field access
2309
        $this->assertInstanceOf(DBBoolean::class, $captain1->relObject('IsRetired'));
2310
        $this->assertEquals(1, $captain1->relObject('IsRetired')->getValue());
2311
2312
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
2313
        // Test that we can traverse more than once, and that arbitrary methods are okay
2314
        $this->assertInstanceOf(DBVarchar::class, $player->relObject('Teams.First.Title'));
2315
        $this->assertEquals("Team 1", $player->relObject('Teams.First.Title')->getValue());
2316
2317
        // relObject throws exception on invalid properties
2318
        $this->expectException(LogicException::class);
2319
        $this->expectExceptionMessage("Not is not a relation/field on " . DataObjectTest\Player::class);
2320
        $player->relObject('Not.A.Field');
2321
    }
2322
2323
    public function testLateStaticBindingStyle()
2324
    {
2325
        // Confirm that DataObjectTest_Player::get() operates as excepted
2326
        $this->assertEquals(4, DataObjectTest\Player::get()->count());
2327
        $this->assertInstanceOf(DataObjectTest\Player::class, DataObjectTest\Player::get()->first());
2328
2329
        // You can't pass arguments to LSB syntax - use the DataList methods instead.
2330
        $this->expectException(InvalidArgumentException::class);
2331
2332
        DataObjectTest\Player::get(null, "\"ID\" = 1");
2333
    }
2334
2335
    /**
2336
     * @expectedException \InvalidArgumentException
2337
     */
2338
    public function testBrokenLateStaticBindingStyle()
2339
    {
2340
        // If you call DataObject::get() you have to pass a first argument
2341
        DataObject::get();
2342
    }
2343
2344
    public function testBigIntField()
2345
    {
2346
        $staff = new DataObjectTest\Staff();
2347
        $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...
2348
        $staff->write();
2349
        $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...
2350
    }
2351
2352
    public function testGetOneMissingValueReturnsNull()
2353
    {
2354
2355
        // Test that missing values return null
2356
        $this->assertEquals(null, DataObject::get_one(
2357
            DataObjectTest\TeamComment::class,
2358
            ['"DataObjectTest_TeamComment"."Name"' => 'does not exists']
2359
        ));
2360
    }
2361
2362
    public function testSetFieldWithArrayOnScalarOnlyField()
2363
    {
2364
        $this->expectException(InvalidArgumentException::class);
2365
        $do = Company::singleton();
2366
        $do->FoundationYear = '1984';
2367
        $do->FoundationYear = array('Amount' => 123, 'Currency' => 'CAD');
2368
        $this->assertEmpty($do->FoundationYear);
2369
    }
2370
2371
    public function testSetFieldWithArrayOnCompositeField()
2372
    {
2373
        $do = Company::singleton();
2374
        $do->SalaryCap = array('Amount' => 123456, 'Currency' => 'CAD');
2375
        $this->assertNotEmpty($do->SalaryCap);
2376
    }
2377
2378
    public function testWriteManipulationWithNonScalarValuesAllowed()
2379
    {
2380
        $do = DataObjectTest\MockDynamicAssignmentDataObject::create();
2381
        $do->write();
2382
2383
        $do->StaticScalarOnlyField = true;
2384
        $do->DynamicScalarOnlyField = false;
2385
        $do->DynamicField = true;
2386
2387
        $do->write();
2388
2389
        $this->assertTrue($do->StaticScalarOnlyField);
2390
        $this->assertFalse($do->DynamicScalarOnlyField);
2391
        $this->assertTrue($do->DynamicField);
2392
    }
2393
2394
    public function testWriteManipulationWithNonScalarValuesDisallowed()
2395
    {
2396
        $this->expectException(InvalidArgumentException::class);
2397
2398
        $do = DataObjectTest\MockDynamicAssignmentDataObject::create();
2399
        $do->write();
2400
2401
        $do->StaticScalarOnlyField = false;
2402
        $do->DynamicScalarOnlyField = true;
2403
        $do->DynamicField = false;
2404
2405
        $do->write();
2406
    }
2407
}
2408