Passed
Push — master ( 91737d...b67073 )
by Sam
08:30
created

DataObjectTest::testZeroIsFalse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 15
nc 1
nop 0
dl 0
loc 21
rs 9.7666
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\ORM\Tests;
4
5
use InvalidArgumentException;
6
use LogicException;
7
use SilverStripe\Core\Config\Config;
8
use SilverStripe\Dev\SapphireTest;
9
use SilverStripe\i18n\i18n;
10
use SilverStripe\ORM\Connect\MySQLDatabase;
11
use SilverStripe\ORM\DataObject;
12
use SilverStripe\ORM\DataObjectSchema;
13
use SilverStripe\ORM\DB;
14
use SilverStripe\ORM\FieldType\DBBoolean;
15
use SilverStripe\ORM\FieldType\DBDatetime;
16
use SilverStripe\ORM\FieldType\DBField;
17
use SilverStripe\ORM\FieldType\DBPolymorphicForeignKey;
18
use SilverStripe\ORM\FieldType\DBVarchar;
19
use SilverStripe\ORM\ManyManyList;
20
use SilverStripe\ORM\Tests\DataObjectTest\Company;
21
use SilverStripe\ORM\Tests\DataObjectTest\Player;
22
use SilverStripe\ORM\Tests\DataObjectTest\Team;
23
use SilverStripe\View\ViewableData;
24
use stdClass;
25
26
class DataObjectTest extends SapphireTest
27
{
28
29
    protected static $fixture_file = 'DataObjectTest.yml';
30
31
    /**
32
     * Standard set of dataobject test classes
33
     *
34
     * @var array
35
     */
36
    public static $extra_data_objects = array(
37
        DataObjectTest\Team::class,
38
        DataObjectTest\Fixture::class,
39
        DataObjectTest\SubTeam::class,
40
        DataObjectTest\OtherSubclassWithSameField::class,
41
        DataObjectTest\FieldlessTable::class,
42
        DataObjectTest\FieldlessSubTable::class,
43
        DataObjectTest\ValidatedObject::class,
44
        DataObjectTest\Player::class,
45
        DataObjectTest\TeamComment::class,
46
        DataObjectTest\EquipmentCompany::class,
47
        DataObjectTest\SubEquipmentCompany::class,
48
        DataObjectTest\ExtendedTeamComment::class,
49
        DataObjectTest\Company::class,
50
        DataObjectTest\Staff::class,
51
        DataObjectTest\CEO::class,
52
        DataObjectTest\Fan::class,
53
        DataObjectTest\Play::class,
54
        DataObjectTest\Ploy::class,
55
        DataObjectTest\Bogey::class,
56
        DataObjectTest\Sortable::class,
57
        DataObjectTest\Bracket::class,
58
        DataObjectTest\RelationParent::class,
59
        DataObjectTest\RelationChildFirst::class,
60
        DataObjectTest\RelationChildSecond::class,
61
        DataObjectTest\MockDynamicAssignmentDataObject::class
62
    );
63
64
    public static function getExtraDataObjects()
65
    {
66
        return array_merge(
67
            DataObjectTest::$extra_data_objects,
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
68
            ManyManyListTest::$extra_data_objects
69
        );
70
    }
71
72
    /**
73
     * @dataProvider provideSingletons
74
     */
75
    public function testSingleton($inst, $defaultValue, $altDefaultValue)
76
    {
77
        $inst = $inst();
78
        // Test that populateDefaults() isn't called on singletons
79
        // which can lead to SQL errors during build, and endless loops
80
        if ($defaultValue) {
81
            $this->assertEquals($defaultValue, $inst->MyFieldWithDefault);
82
        } else {
83
            $this->assertEmpty($inst->MyFieldWithDefault);
84
        }
85
86
        if ($altDefaultValue) {
87
            $this->assertEquals($altDefaultValue, $inst->MyFieldWithAltDefault);
88
        } else {
89
            $this->assertEmpty($inst->MyFieldWithAltDefault);
90
        }
91
    }
92
93
    public function provideSingletons()
94
    {
95
        // because PHPUnit evalutes test providers *before* setUp methods
96
        // any extensions added in the setUp methods won't be available
97
        // we must return closures to generate the arguments at run time
98
        return array(
99
            'create() static method' => array(function () {
100
                return DataObjectTest\Fixture::create();
101
            }, 'Default Value', 'Default Value'),
102
            'New object creation' => array(function () {
103
                return new DataObjectTest\Fixture();
104
            }, 'Default Value', 'Default Value'),
105
            'singleton() function' => array(function () {
106
                return singleton(DataObjectTest\Fixture::class);
107
            }, null, null),
108
            'singleton() static method' => array(function () {
109
                return DataObjectTest\Fixture::singleton();
110
            }, null, null),
111
            'Manual constructor args' => array(function () {
112
                return new DataObjectTest\Fixture(null, true);
113
            }, null, null),
114
        );
115
    }
116
117
    public function testDb()
118
    {
119
        $schema = DataObject::getSchema();
120
        $dbFields = $schema->fieldSpecs(DataObjectTest\TeamComment::class);
121
122
        // Assert fields are included
123
        $this->assertArrayHasKey('Name', $dbFields);
124
125
        // Assert the base fields are included
126
        $this->assertArrayHasKey('Created', $dbFields);
127
        $this->assertArrayHasKey('LastEdited', $dbFields);
128
        $this->assertArrayHasKey('ClassName', $dbFields);
129
        $this->assertArrayHasKey('ID', $dbFields);
130
131
        // Assert that the correct field type is returned when passing a field
132
        $this->assertEquals('Varchar', $schema->fieldSpec(DataObjectTest\TeamComment::class, 'Name'));
133
        $this->assertEquals('Text', $schema->fieldSpec(DataObjectTest\TeamComment::class, 'Comment'));
134
135
        // Test with table required
136
        $this->assertEquals(
137
            DataObjectTest\TeamComment::class . '.Varchar',
138
            $schema->fieldSpec(DataObjectTest\TeamComment::class, 'Name', DataObjectSchema::INCLUDE_CLASS)
139
        );
140
        $this->assertEquals(
141
            DataObjectTest\TeamComment::class . '.Text',
142
            $schema->fieldSpec(DataObjectTest\TeamComment::class, 'Comment', DataObjectSchema::INCLUDE_CLASS)
143
        );
144
        $dbFields = $schema->fieldSpecs(DataObjectTest\ExtendedTeamComment::class);
145
146
        // fixed fields are still included in extended classes
147
        $this->assertArrayHasKey('Created', $dbFields);
148
        $this->assertArrayHasKey('LastEdited', $dbFields);
149
        $this->assertArrayHasKey('ClassName', $dbFields);
150
        $this->assertArrayHasKey('ID', $dbFields);
151
152
        // Assert overloaded fields have correct data type
153
        $this->assertEquals('HTMLText', $schema->fieldSpec(DataObjectTest\ExtendedTeamComment::class, 'Comment'));
154
        $this->assertEquals(
155
            'HTMLText',
156
            $dbFields['Comment'],
157
            'Calls to DataObject::db without a field specified return correct data types'
158
        );
159
160
        // assertEquals doesn't verify the order of array elements, so access keys manually to check order:
161
        // expected: array('Name' => 'Varchar', 'Comment' => 'HTMLText')
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
162
        $this->assertEquals(
163
            array(
164
                'Name',
165
                'Comment'
166
            ),
167
            array_slice(array_keys($dbFields), 4, 2),
168
            'DataObject::db returns fields in correct order'
169
        );
170
    }
171
172
    public function testConstructAcceptsValues()
173
    {
174
        // Values can be an array...
175
        $player = new DataObjectTest\Player(
176
            array(
177
                'FirstName' => 'James',
178
                'Surname' => 'Smith'
179
            )
180
        );
181
182
        $this->assertEquals('James', $player->FirstName);
183
        $this->assertEquals('Smith', $player->Surname);
184
185
        // ... or a stdClass inst
186
        $data = new stdClass();
187
        $data->FirstName = 'John';
188
        $data->Surname = 'Doe';
189
        $player = new DataObjectTest\Player($data);
0 ignored issues
show
Bug introduced by
$data of type stdClass is incompatible with the type array|null expected by parameter $record of SilverStripe\ORM\Tests\D...t\Player::__construct(). ( Ignorable by Annotation )

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

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

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

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

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

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

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

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

687
            $team1->/** @scrutinizer ignore-call */ 
688
                    Captain()->ID,
Loading history...
688
            'The captain exists for team 1'
689
        );
690
        $this->assertEquals(
691
            $player1->ID,
692
            $team1->getComponent('Captain')->ID,
693
            'The captain exists through the component getter'
694
        );
695
696
        $this->assertEquals(
697
            $team1->Captain()->FirstName,
698
            'Player 1',
699
            'Player 1 is the captain'
700
        );
701
        $this->assertEquals(
702
            $team1->getComponent('Captain')->FirstName,
703
            'Player 1',
704
            'Player 1 is the captain'
705
        );
706
707
        $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...
708
        $team1->write();
709
710
        $this->assertEquals($player2->ID, $team1->Captain()->ID);
711
        $this->assertEquals($player2->ID, $team1->getComponent('Captain')->ID);
712
        $this->assertEquals('Player 2', $team1->Captain()->FirstName);
713
        $this->assertEquals('Player 2', $team1->getComponent('Captain')->FirstName);
714
715
716
        // Set the favourite team for fan1
717
        $fan1->setField('FavouriteID', $team1->ID);
718
        $fan1->setField('FavouriteClass', get_class($team1));
719
720
        $this->assertEquals($team1->ID, $fan1->Favourite()->ID, 'The team is assigned to fan 1');
721
        $this->assertInstanceOf(get_class($team1), $fan1->Favourite(), 'The team is assigned to fan 1');
722
        $this->assertEquals(
723
            $team1->ID,
724
            $fan1->getComponent('Favourite')->ID,
725
            'The team exists through the component getter'
726
        );
727
        $this->assertInstanceOf(
728
            get_class($team1),
729
            $fan1->getComponent('Favourite'),
730
            'The team exists through the component getter'
731
        );
732
733
        $this->assertEquals(
734
            $fan1->Favourite()->Title,
735
            'Team 1',
736
            'Team 1 is the favourite'
737
        );
738
        $this->assertEquals(
739
            $fan1->getComponent('Favourite')->Title,
740
            'Team 1',
741
            'Team 1 is the favourite'
742
        );
743
    }
744
745
    /**
746
     * Test has_one used as field getter/setter
747
     */
748
    public function testHasOneAsField()
749
    {
750
        /** @var DataObjectTest\Team $team1 */
751
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
752
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
753
        $captain2 = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
754
755
        // Setter: By RelationID
756
        $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...
757
        $team1->write();
758
        $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...
759
760
        // Setter: New object
761
        $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...
762
        $team1->write();
763
        $this->assertEquals($captain2->ID, $team1->Captain->ID);
764
765
        // Setter: Custom data (required by DataDifferencer)
766
        $team1->Captain = DBField::create_field('HTMLFragment', '<p>No captain</p>');
767
        $this->assertEquals('<p>No captain</p>', $team1->Captain);
768
    }
769
770
    /**
771
     * @todo Extend type change tests (e.g. '0'==NULL)
772
     */
773
    public function testChangedFields()
774
    {
775
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
776
        $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...
777
        $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...
778
779
        $this->assertEquals(
780
            $obj->getChangedFields(true, DataObject::CHANGE_STRICT),
781
            array(
782
                'FirstName' => array(
783
                    'before' => 'Captain',
784
                    'after' => 'Captain-changed',
785
                    'level' => DataObject::CHANGE_VALUE
786
                ),
787
                'IsRetired' => array(
788
                    'before' => 1,
789
                    'after' => true,
790
                    'level' => DataObject::CHANGE_STRICT
791
                )
792
            ),
793
            'Changed fields are correctly detected with strict type changes (level=1)'
794
        );
795
796
        $this->assertEquals(
797
            $obj->getChangedFields(true, DataObject::CHANGE_VALUE),
798
            array(
799
                'FirstName' => array(
800
                    'before' => 'Captain',
801
                    'after' => 'Captain-changed',
802
                    'level' => DataObject::CHANGE_VALUE
803
                )
804
            ),
805
            'Changed fields are correctly detected while ignoring type changes (level=2)'
806
        );
807
808
        $newObj = new DataObjectTest\Player();
809
        $newObj->FirstName = "New Player";
810
        $this->assertEquals(
811
            array(
812
                'FirstName' => array(
813
                    'before' => null,
814
                    'after' => 'New Player',
815
                    'level' => DataObject::CHANGE_VALUE
816
                )
817
            ),
818
            $newObj->getChangedFields(true, DataObject::CHANGE_VALUE),
819
            'Initialised fields are correctly detected as full changes'
820
        );
821
    }
822
823
    public function testChangedFieldsWhenRestoringData()
824
    {
825
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
826
        $obj->FirstName = 'Captain-changed';
0 ignored issues
show
Bug Best Practice introduced by
The property FirstName does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
827
        $obj->FirstName = 'Captain';
828
829
        $this->assertEquals(
830
            [],
831
            $obj->getChangedFields(true, DataObject::CHANGE_STRICT)
832
        );
833
    }
834
835
    public function testChangedFieldsAfterWrite()
836
    {
837
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
838
        $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...
839
        $obj->write();
840
        $obj->FirstName = 'Captain';
841
842
        $this->assertEquals(
843
            array(
844
                'FirstName' => array(
845
                    'before' => 'Captain-changed',
846
                    'after' => 'Captain',
847
                    'level' => DataObject::CHANGE_VALUE,
848
                ),
849
            ),
850
            $obj->getChangedFields(true, DataObject::CHANGE_VALUE)
851
        );
852
    }
853
854
    public function testForceChangeCantBeCancelledUntilWrite()
855
    {
856
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
857
        $this->assertFalse($obj->isChanged('FirstName'));
858
        $this->assertFalse($obj->isChanged('Surname'));
859
860
        // Force change marks the records as changed
861
        $obj->forceChange();
862
        $this->assertTrue($obj->isChanged('FirstName'));
863
        $this->assertTrue($obj->isChanged('Surname'));
864
865
        // ...but not if we explicitly ask if the value has changed
866
        $this->assertFalse($obj->isChanged('FirstName', DataObject::CHANGE_VALUE));
867
        $this->assertFalse($obj->isChanged('Surname', DataObject::CHANGE_VALUE));
868
869
        // Not overwritten by setting the value to is original value
870
        $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...
871
        $this->assertTrue($obj->isChanged('FirstName'));
872
        $this->assertTrue($obj->isChanged('Surname'));
873
874
        // Not overwritten by changing it to something else and back again
875
        $obj->FirstName = 'Captain-changed';
876
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_VALUE));
877
878
        $obj->FirstName = 'Captain';
879
        $this->assertFalse($obj->isChanged('FirstName', DataObject::CHANGE_VALUE));
880
        $this->assertTrue($obj->isChanged('FirstName'));
881
        $this->assertTrue($obj->isChanged('Surname'));
882
883
        // Cleared after write
884
        $obj->write();
885
        $this->assertFalse($obj->isChanged('FirstName'));
886
        $this->assertFalse($obj->isChanged('Surname'));
887
888
        $obj->FirstName = 'Captain';
889
        $this->assertFalse($obj->isChanged('FirstName'));
890
    }
891
892
    /**
893
     * @skipUpgrade
894
     */
895
    public function testIsChanged()
896
    {
897
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
898
        $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...
899
        $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...
900
        $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...
901
902
        // Now that DB fields are changed, isChanged is true
903
        $this->assertTrue($obj->isChanged('NonDBField'));
904
        $this->assertFalse($obj->isChanged('NonField'));
905
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_STRICT));
906
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_VALUE));
907
        $this->assertTrue($obj->isChanged('IsRetired', DataObject::CHANGE_STRICT));
908
        $this->assertFalse($obj->isChanged('IsRetired', DataObject::CHANGE_VALUE));
909
        $this->assertFalse($obj->isChanged('Email', 1), 'Doesnt change mark unchanged property');
910
        $this->assertFalse($obj->isChanged('Email', 2), 'Doesnt change mark unchanged property');
911
912
        $newObj = new DataObjectTest\Player();
913
        $newObj->FirstName = "New Player";
914
        $this->assertTrue($newObj->isChanged('FirstName', DataObject::CHANGE_STRICT));
915
        $this->assertTrue($newObj->isChanged('FirstName', DataObject::CHANGE_VALUE));
916
        $this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_STRICT));
917
        $this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_VALUE));
918
919
        $newObj->write();
920
        $this->assertFalse($newObj->ischanged());
921
        $this->assertFalse($newObj->isChanged('FirstName', DataObject::CHANGE_STRICT));
922
        $this->assertFalse($newObj->isChanged('FirstName', DataObject::CHANGE_VALUE));
923
        $this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_STRICT));
924
        $this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_VALUE));
925
926
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
927
        $obj->FirstName = null;
928
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_STRICT));
929
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_VALUE));
930
931
        $obj->write();
932
        $obj->FirstName = null;
933
        $this->assertFalse($obj->isChanged('FirstName', DataObject::CHANGE_STRICT), 'Unchanged property was marked as changed');
934
        $obj->FirstName = 0;
935
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_STRICT), 'Strict (type) change was not detected');
936
        $this->assertFalse($obj->isChanged('FirstName', DataObject::CHANGE_VALUE), 'Type-only change was marked as a value change');
937
938
        /* Test when there's not field provided */
939
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
940
        $this->assertFalse($obj->isChanged());
941
        $obj->NonDBField = 'new value';
942
        $this->assertFalse($obj->isChanged());
943
        $obj->FirstName = "New Player";
944
        $this->assertTrue($obj->isChanged());
945
946
        $obj->write();
947
        $this->assertFalse($obj->isChanged());
948
    }
949
950
    public function testRandomSort()
951
    {
952
        /* If we perform the same regularly sorted query twice, it should return the same results */
953
        $itemsA = DataObject::get(DataObjectTest\TeamComment::class, "", "ID");
954
        foreach ($itemsA as $item) {
955
            $keysA[] = $item->ID;
956
        }
957
958
        $itemsB = DataObject::get(DataObjectTest\TeamComment::class, "", "ID");
959
        foreach ($itemsB as $item) {
960
            $keysB[] = $item->ID;
961
        }
962
963
        /* Test when there's not field provided */
964
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
965
        $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...
966
        $this->assertTrue($obj->isChanged());
967
968
        $obj->write();
969
        $this->assertFalse($obj->isChanged());
970
971
        /* If we perform the same random query twice, it shouldn't return the same results */
972
        $itemsA = DataObject::get(DataObjectTest\TeamComment::class, "", DB::get_conn()->random());
973
        $itemsB = DataObject::get(DataObjectTest\TeamComment::class, "", DB::get_conn()->random());
974
        $itemsC = DataObject::get(DataObjectTest\TeamComment::class, "", DB::get_conn()->random());
975
        $itemsD = DataObject::get(DataObjectTest\TeamComment::class, "", DB::get_conn()->random());
976
        foreach ($itemsA as $item) {
977
            $keysA[] = $item->ID;
978
        }
979
        foreach ($itemsB as $item) {
980
            $keysB[] = $item->ID;
981
        }
982
        foreach ($itemsC as $item) {
983
            $keysC[] = $item->ID;
984
        }
985
        foreach ($itemsD as $item) {
986
            $keysD[] = $item->ID;
987
        }
988
989
        // These shouldn't all be the same (run it 4 times to minimise chance of an accidental collision)
990
        // There's about a 1 in a billion chance of an accidental collision
991
        $this->assertTrue($keysA != $keysB || $keysB != $keysC || $keysC != $keysD);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $keysB seems to be defined by a foreach iteration on line 959. 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 982. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
Comprehensibility Best Practice introduced by
The variable $keysD seems to be defined by a foreach iteration on line 985. 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 954. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
992
    }
993
994
    public function testWriteSavesToHasOneRelations()
995
    {
996
        /* DataObject::write() should save to a has_one relationship if you set a field called (relname)ID */
997
        $team = new DataObjectTest\Team();
998
        $captainID = $this->idFromFixture(DataObjectTest\Player::class, 'player1');
999
        $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...
1000
        $team->write();
1001
        $this->assertEquals(
1002
            $captainID,
1003
            DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $team->ID")->value()
1004
        );
1005
1006
        /* After giving it a value, you should also be able to set it back to null */
1007
        $team->CaptainID = '';
1008
        $team->write();
1009
        $this->assertEquals(
1010
            0,
1011
            DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $team->ID")->value()
1012
        );
1013
1014
        /* You should also be able to save a blank to it when it's first created */
1015
        $team = new DataObjectTest\Team();
1016
        $team->CaptainID = '';
1017
        $team->write();
1018
        $this->assertEquals(
1019
            0,
1020
            DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $team->ID")->value()
1021
        );
1022
1023
        /* Ditto for existing records without a value */
1024
        $existingTeam = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1025
        $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...
1026
        $existingTeam->write();
1027
        $this->assertEquals(
1028
            0,
1029
            DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $existingTeam->ID")->value()
1030
        );
1031
    }
1032
1033
    public function testCanAccessHasOneObjectsAsMethods()
1034
    {
1035
        /* If you have a has_one relation 'Captain' on $obj, and you set the $obj->CaptainID = (ID), then the
1036
        * object itself should be accessible as $obj->Captain() */
1037
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1038
        $captainID = $this->idFromFixture(DataObjectTest\Player::class, 'captain1');
1039
1040
        $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...
1041
        $this->assertNotNull($team->Captain());
1042
        $this->assertEquals($captainID, $team->Captain()->ID);
1043
1044
        // Test for polymorphic has_one relations
1045
        $fan = $this->objFromFixture(DataObjectTest\Fan::class, 'fan1');
1046
        $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...
1047
        $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...
1048
        $this->assertNotNull($fan->Favourite());
1049
        $this->assertEquals($team->ID, $fan->Favourite()->ID);
1050
        $this->assertInstanceOf(DataObjectTest\Team::class, $fan->Favourite());
1051
    }
1052
1053
    public function testFieldNamesThatMatchMethodNamesWork()
1054
    {
1055
        /* Check that a field name that corresponds to a method on DataObject will still work */
1056
        $obj = new DataObjectTest\Fixture();
1057
        $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...
1058
        $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...
1059
        $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...
1060
        $obj->write();
1061
1062
        $this->assertNotNull($obj->ID);
1063
        $this->assertEquals(
1064
            'value1',
1065
            DB::query("SELECT \"Data\" FROM \"DataObjectTest_Fixture\" WHERE \"ID\" = $obj->ID")->value()
1066
        );
1067
        $this->assertEquals(
1068
            'value2',
1069
            DB::query("SELECT \"DbObject\" FROM \"DataObjectTest_Fixture\" WHERE \"ID\" = $obj->ID")->value()
1070
        );
1071
        $this->assertEquals(
1072
            'value3',
1073
            DB::query("SELECT \"Duplicate\" FROM \"DataObjectTest_Fixture\" WHERE \"ID\" = $obj->ID")->value()
1074
        );
1075
    }
1076
1077
    /**
1078
     * @todo Re-enable all test cases for field existence after behaviour has been fixed
1079
     */
1080
    public function testFieldExistence()
1081
    {
1082
        $teamInstance = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1083
        $teamSingleton = singleton(DataObjectTest\Team::class);
1084
1085
        $subteamInstance = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
1086
        $schema = DataObject::getSchema();
1087
1088
        /* hasField() singleton checks */
1089
        $this->assertTrue(
1090
            $teamSingleton->hasField('ID'),
1091
            'hasField() finds built-in fields in singletons'
1092
        );
1093
        $this->assertTrue(
1094
            $teamSingleton->hasField('Title'),
1095
            'hasField() finds custom fields in singletons'
1096
        );
1097
1098
        /* hasField() instance checks */
1099
        $this->assertFalse(
1100
            $teamInstance->hasField('NonExistingField'),
1101
            'hasField() doesnt find non-existing fields in instances'
1102
        );
1103
        $this->assertTrue(
1104
            $teamInstance->hasField('ID'),
1105
            'hasField() finds built-in fields in instances'
1106
        );
1107
        $this->assertTrue(
1108
            $teamInstance->hasField('Created'),
1109
            'hasField() finds built-in fields in instances'
1110
        );
1111
        $this->assertTrue(
1112
            $teamInstance->hasField('DatabaseField'),
1113
            'hasField() finds custom fields in instances'
1114
        );
1115
        //$this->assertFalse($teamInstance->hasField('SubclassDatabaseField'),
0 ignored issues
show
Unused Code Comprehensibility introduced by
82% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1116
        //'hasField() doesnt find subclass fields in parentclass instances');
1117
        $this->assertTrue(
1118
            $teamInstance->hasField('DynamicField'),
1119
            'hasField() finds dynamic getters in instances'
1120
        );
1121
        $this->assertTrue(
1122
            $teamInstance->hasField('HasOneRelationshipID'),
1123
            'hasField() finds foreign keys in instances'
1124
        );
1125
        $this->assertTrue(
1126
            $teamInstance->hasField('ExtendedDatabaseField'),
1127
            'hasField() finds extended fields in instances'
1128
        );
1129
        $this->assertTrue(
1130
            $teamInstance->hasField('ExtendedHasOneRelationshipID'),
1131
            'hasField() finds extended foreign keys in instances'
1132
        );
1133
        //$this->assertTrue($teamInstance->hasField('ExtendedDynamicField'),
0 ignored issues
show
Unused Code Comprehensibility introduced by
82% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1134
        //'hasField() includes extended dynamic getters in instances');
1135
1136
        /* hasField() subclass checks */
1137
        $this->assertTrue(
1138
            $subteamInstance->hasField('ID'),
1139
            'hasField() finds built-in fields in subclass instances'
1140
        );
1141
        $this->assertTrue(
1142
            $subteamInstance->hasField('Created'),
1143
            'hasField() finds built-in fields in subclass instances'
1144
        );
1145
        $this->assertTrue(
1146
            $subteamInstance->hasField('DatabaseField'),
1147
            'hasField() finds custom fields in subclass instances'
1148
        );
1149
        $this->assertTrue(
1150
            $subteamInstance->hasField('SubclassDatabaseField'),
1151
            'hasField() finds custom fields in subclass instances'
1152
        );
1153
        $this->assertTrue(
1154
            $subteamInstance->hasField('DynamicField'),
1155
            'hasField() finds dynamic getters in subclass instances'
1156
        );
1157
        $this->assertTrue(
1158
            $subteamInstance->hasField('HasOneRelationshipID'),
1159
            'hasField() finds foreign keys in subclass instances'
1160
        );
1161
        $this->assertTrue(
1162
            $subteamInstance->hasField('ExtendedDatabaseField'),
1163
            'hasField() finds extended fields in subclass instances'
1164
        );
1165
        $this->assertTrue(
1166
            $subteamInstance->hasField('ExtendedHasOneRelationshipID'),
1167
            'hasField() finds extended foreign keys in subclass instances'
1168
        );
1169
1170
        /* hasDatabaseField() singleton checks */
1171
        //$this->assertTrue($teamSingleton->hasDatabaseField('ID'),
0 ignored issues
show
Unused Code Comprehensibility introduced by
82% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1172
        //'hasDatabaseField() finds built-in fields in singletons');
1173
        $this->assertNotEmpty(
1174
            $schema->fieldSpec(DataObjectTest\Team::class, 'Title'),
1175
            'hasDatabaseField() finds custom fields in singletons'
1176
        );
1177
1178
        /* hasDatabaseField() instance checks */
1179
        $this->assertNull(
1180
            $schema->fieldSpec(DataObjectTest\Team::class, 'NonExistingField'),
1181
            'hasDatabaseField() doesnt find non-existing fields in instances'
1182
        );
1183
        //$this->assertNotEmpty($schema->fieldSpec(DataObjectTest_Team::class, 'ID'),
0 ignored issues
show
Unused Code Comprehensibility introduced by
69% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1184
        //'hasDatabaseField() finds built-in fields in instances');
1185
        $this->assertNotEmpty(
1186
            $schema->fieldSpec(DataObjectTest\Team::class, 'Created'),
1187
            'hasDatabaseField() finds built-in fields in instances'
1188
        );
1189
        $this->assertNotEmpty(
1190
            $schema->fieldSpec(DataObjectTest\Team::class, 'DatabaseField'),
1191
            'hasDatabaseField() finds custom fields in instances'
1192
        );
1193
        $this->assertNull(
1194
            $schema->fieldSpec(DataObjectTest\Team::class, 'SubclassDatabaseField'),
1195
            'hasDatabaseField() doesnt find subclass fields in parentclass instances'
1196
        );
1197
        //$this->assertNull($schema->fieldSpec(DataObjectTest_Team::class, 'DynamicField'),
0 ignored issues
show
Unused Code Comprehensibility introduced by
69% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1198
        //'hasDatabaseField() doesnt dynamic getters in instances');
1199
        $this->assertNotEmpty(
1200
            $schema->fieldSpec(DataObjectTest\Team::class, 'HasOneRelationshipID'),
1201
            'hasDatabaseField() finds foreign keys in instances'
1202
        );
1203
        $this->assertNotEmpty(
1204
            $schema->fieldSpec(DataObjectTest\Team::class, 'ExtendedDatabaseField'),
1205
            'hasDatabaseField() finds extended fields in instances'
1206
        );
1207
        $this->assertNotEmpty(
1208
            $schema->fieldSpec(DataObjectTest\Team::class, 'ExtendedHasOneRelationshipID'),
1209
            'hasDatabaseField() finds extended foreign keys in instances'
1210
        );
1211
        $this->assertNull(
1212
            $schema->fieldSpec(DataObjectTest\Team::class, 'ExtendedDynamicField'),
1213
            'hasDatabaseField() doesnt include extended dynamic getters in instances'
1214
        );
1215
1216
        /* hasDatabaseField() subclass checks */
1217
        $this->assertNotEmpty(
1218
            $schema->fieldSpec(DataObjectTest\SubTeam::class, 'DatabaseField'),
1219
            'hasField() finds custom fields in subclass instances'
1220
        );
1221
        $this->assertNotEmpty(
1222
            $schema->fieldSpec(DataObjectTest\SubTeam::class, 'SubclassDatabaseField'),
1223
            'hasField() finds custom fields in subclass instances'
1224
        );
1225
    }
1226
1227
    /**
1228
     * @todo Re-enable all test cases for field inheritance aggregation after behaviour has been fixed
1229
     */
1230
    public function testFieldInheritance()
1231
    {
1232
        $schema = DataObject::getSchema();
1233
1234
        // Test logical fields (including composite)
1235
        $teamSpecifications = $schema->fieldSpecs(DataObjectTest\Team::class);
1236
        $expected = array(
1237
            'ID',
1238
            'ClassName',
1239
            'LastEdited',
1240
            'Created',
1241
            'Title',
1242
            'DatabaseField',
1243
            'ExtendedDatabaseField',
1244
            'CaptainID',
1245
            'FounderID',
1246
            'HasOneRelationshipID',
1247
            'ExtendedHasOneRelationshipID'
1248
        );
1249
        $actual = array_keys($teamSpecifications);
1250
        sort($expected);
1251
        sort($actual);
1252
        $this->assertEquals(
1253
            $expected,
1254
            $actual,
1255
            'fieldSpecifications() contains all fields defined on instance: base, extended and foreign keys'
1256
        );
1257
1258
        $teamFields = $schema->databaseFields(DataObjectTest\Team::class, false);
1259
        $expected = array(
1260
            'ID',
1261
            'ClassName',
1262
            'LastEdited',
1263
            'Created',
1264
            'Title',
1265
            'DatabaseField',
1266
            'ExtendedDatabaseField',
1267
            'CaptainID',
1268
            'FounderID',
1269
            'HasOneRelationshipID',
1270
            'ExtendedHasOneRelationshipID'
1271
        );
1272
        $actual = array_keys($teamFields);
1273
        sort($expected);
1274
        sort($actual);
1275
        $this->assertEquals(
1276
            $expected,
1277
            $actual,
1278
            'databaseFields() contains only fields defined on instance, including base, extended and foreign keys'
1279
        );
1280
1281
        $subteamSpecifications = $schema->fieldSpecs(DataObjectTest\SubTeam::class);
1282
        $expected = array(
1283
            'ID',
1284
            'ClassName',
1285
            'LastEdited',
1286
            'Created',
1287
            'Title',
1288
            'DatabaseField',
1289
            'ExtendedDatabaseField',
1290
            'CaptainID',
1291
            'FounderID',
1292
            'HasOneRelationshipID',
1293
            'ExtendedHasOneRelationshipID',
1294
            'SubclassDatabaseField',
1295
            'ParentTeamID',
1296
        );
1297
        $actual = array_keys($subteamSpecifications);
1298
        sort($expected);
1299
        sort($actual);
1300
        $this->assertEquals(
1301
            $expected,
1302
            $actual,
1303
            'fieldSpecifications() on subclass contains all fields, including base, extended  and foreign keys'
1304
        );
1305
1306
        $subteamFields = $schema->databaseFields(DataObjectTest\SubTeam::class, false);
1307
        $expected = array(
1308
            'ID',
1309
            'SubclassDatabaseField',
1310
            'ParentTeamID',
1311
        );
1312
        $actual = array_keys($subteamFields);
1313
        sort($expected);
1314
        sort($actual);
1315
        $this->assertEquals(
1316
            $expected,
1317
            $actual,
1318
            'databaseFields() on subclass contains only fields defined on instance'
1319
        );
1320
    }
1321
1322
    public function testSearchableFields()
1323
    {
1324
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
1325
        $fields = $player->searchableFields();
1326
        $this->assertArrayHasKey(
1327
            'IsRetired',
1328
            $fields,
1329
            'Fields defined by $searchable_fields static are correctly detected'
1330
        );
1331
        $this->assertArrayHasKey(
1332
            'ShirtNumber',
1333
            $fields,
1334
            'Fields defined by $searchable_fields static are correctly detected'
1335
        );
1336
1337
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1338
        $fields = $team->searchableFields();
1339
        $this->assertArrayHasKey(
1340
            'Title',
1341
            $fields,
1342
            'Fields can be inherited from the $summary_fields static, including methods called on fields'
1343
        );
1344
        $this->assertArrayHasKey(
1345
            'Captain.ShirtNumber',
1346
            $fields,
1347
            'Fields on related objects can be inherited from the $summary_fields static'
1348
        );
1349
        $this->assertArrayHasKey(
1350
            'Captain.FavouriteTeam.Title',
1351
            $fields,
1352
            'Fields on related objects can be inherited from the $summary_fields static'
1353
        );
1354
1355
        $testObj = new DataObjectTest\Fixture();
1356
        $fields = $testObj->searchableFields();
1357
        $this->assertEmpty($fields);
1358
    }
1359
1360
    public function testCastingHelper()
1361
    {
1362
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1363
1364
        $this->assertEquals('Varchar', $team->castingHelper('Title'), 'db field wasn\'t casted correctly');
1365
        $this->assertEquals('HTMLVarchar', $team->castingHelper('DatabaseField'), 'db field wasn\'t casted correctly');
1366
1367
        $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

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

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

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

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

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

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

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

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

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

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

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