Passed
Push — travis-php74 ( e33f92...3a6a6e )
by Sam
07:33
created

DataObjectTest::testHasValue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 13
nc 1
nop 0
dl 0
loc 19
rs 9.8333
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\ORM\Tests;
4
5
use InvalidArgumentException;
6
use LogicException;
7
use Page;
0 ignored issues
show
Bug introduced by
The type Page was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use SilverStripe\CMS\Model\SiteTree;
0 ignored issues
show
Bug introduced by
The type SilverStripe\CMS\Model\SiteTree was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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