Passed
Push — master ( 6410ef...806142 )
by Robbie
48:34 queued 35:53
created

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

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

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

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

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

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

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

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

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

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

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

1683
            /** @scrutinizer ignore-type */ $team->inferReciprocalComponent(DataObjectTest\EquipmentCompany::class, 'SponsoredTeams')
Loading history...
1684
        );
1685
1686
        // Test that many_many can be infered from getNonReciprocalComponent
1687
        $this->assertListEquals(
1688
            [
1689
                ['Title' => 'Team 1'],
1690
                ['Title' => 'Team 2'],
1691
                ['Title' => 'Subteam 1'],
1692
            ],
1693
            $company2->inferReciprocalComponent(DataObjectTest\Team::class, 'Sponsors')
1694
        );
1695
1696
        // Check many_many_extraFields still works
1697
        $equipmentCompany = $this->objFromFixture(DataObjectTest\EquipmentCompany::class, 'equipmentcompany1');
1698
        $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

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

1806
        $player = $newTeam->/** @scrutinizer ignore-call */ Players()->first();
Loading history...
1807
        $this->assertEquals('Sam', $player->FirstName);
1808
        $this->assertEquals("Prop", $player->Position);
1809
1810
        // Check that ordering a many-many relation by an aggregate column doesn't fail
1811
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
1812
        $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

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

2083
        $this->assertEquals($company->ID, $ceo->/** @scrutinizer ignore-call */ Company()->ID, 'belongs_to returns the right results.');
Loading history...
2084
2085
        // Test belongs_to can be infered via getNonReciprocalComponent
2086
        // Note: Will be returned as has_many since the belongs_to is ignored.
2087
        $this->assertListEquals(
2088
            [['Name' => 'New Company']],
2089
            $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

2089
            /** @scrutinizer ignore-type */ $ceo->inferReciprocalComponent(DataObjectTest\Company::class, 'CEO')
Loading history...
2090
        );
2091
2092
        // Test has_one to a belongs_to can be infered via getNonReciprocalComponent
2093
        $this->assertEquals(
2094
            $ceo->ID,
2095
            $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...
2096
        );
2097
2098
        // Test automatic creation of class where no assigment exists
2099
        $ceo = new DataObjectTest\CEO();
2100
        $ceo->write();
2101
2102
        $this->assertTrue(
2103
            $ceo->Company() instanceof DataObjectTest\Company,
2104
            'DataObjects across belongs_to relations are automatically created.'
2105
        );
2106
        $this->assertEquals($ceo->ID, $ceo->Company()->CEOID, 'Remote IDs are automatically set.');
2107
2108
        // Write object with components
2109
        $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...
2110
        $ceo->write(false, false, false, true);
2111
        $this->assertTrue($ceo->Company()->isInDB(), 'write() writes belongs_to components to the database.');
2112
2113
        $newCEO = DataObject::get_by_id(DataObjectTest\CEO::class, $ceo->ID);
2114
        $this->assertEquals(
2115
            $ceo->Company()->ID,
2116
            $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

2116
            $newCEO->/** @scrutinizer ignore-call */ 
2117
                     Company()->ID,
Loading history...
2117
            'belongs_to can be retrieved from the database.'
2118
        );
2119
    }
2120
2121
    public function testBelongsToPolymorphic()
2122
    {
2123
        $company = new DataObjectTest\Company();
2124
        $ceo = new DataObjectTest\CEO();
2125
2126
        $company->write();
2127
        $ceo->write();
2128
2129
        // Test belongs_to assignment
2130
        $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...
2131
        $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...
2132
        $company->write();
2133
2134
        $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

2134
        $this->assertEquals($company->ID, $ceo->/** @scrutinizer ignore-call */ CompanyOwned()->ID, 'belongs_to returns the right results.');
Loading history...
2135
        $this->assertInstanceOf(
2136
            DataObjectTest\Company::class,
2137
            $ceo->CompanyOwned(),
2138
            'belongs_to returns the right results.'
2139
        );
2140
2141
        // Test automatic creation of class where no assigment exists
2142
        $ceo = new DataObjectTest\CEO();
2143
        $ceo->write();
2144
2145
        $this->assertTrue(
2146
            $ceo->CompanyOwned() instanceof DataObjectTest\Company,
2147
            'DataObjects across polymorphic belongs_to relations are automatically created.'
2148
        );
2149
        $this->assertEquals($ceo->ID, $ceo->CompanyOwned()->OwnerID, 'Remote IDs are automatically set.');
2150
        $this->assertInstanceOf($ceo->CompanyOwned()->OwnerClass, $ceo, 'Remote class is automatically  set');
2151
2152
        // Write object with components
2153
        $ceo->write(false, false, false, true);
2154
        $this->assertTrue($ceo->CompanyOwned()->isInDB(), 'write() writes belongs_to components to the database.');
2155
2156
        $newCEO = DataObject::get_by_id(DataObjectTest\CEO::class, $ceo->ID);
2157
        $this->assertEquals(
2158
            $ceo->CompanyOwned()->ID,
2159
            $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

2159
            $newCEO->/** @scrutinizer ignore-call */ 
2160
                     CompanyOwned()->ID,
Loading history...
2160
            'polymorphic belongs_to can be retrieved from the database.'
2161
        );
2162
    }
2163
2164
    /**
2165
     * @expectedException LogicException
2166
     */
2167
    public function testInvalidate()
2168
    {
2169
        $do = new DataObjectTest\Fixture();
2170
        $do->write();
2171
2172
        $do->delete();
2173
2174
        $do->delete(); // Prohibit invalid object manipulation
2175
        $do->write();
2176
        $do->duplicate();
2177
    }
2178
2179
    public function testToMap()
2180
    {
2181
        $obj = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
2182
2183
        $map = $obj->toMap();
2184
2185
        $this->assertArrayHasKey('ID', $map, 'Contains base fields');
2186
        $this->assertArrayHasKey('Title', $map, 'Contains fields from parent class');
2187
        $this->assertArrayHasKey('SubclassDatabaseField', $map, 'Contains fields from concrete class');
2188
2189
        $this->assertEquals(
2190
            $obj->ID,
2191
            $map['ID'],
2192
            'Contains values from base fields'
2193
        );
2194
        $this->assertEquals(
2195
            $obj->Title,
2196
            $map['Title'],
2197
            'Contains values from parent class fields'
2198
        );
2199
        $this->assertEquals(
2200
            $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...
2201
            $map['SubclassDatabaseField'],
2202
            'Contains values from concrete class fields'
2203
        );
2204
2205
        $newObj = new DataObjectTest\SubTeam();
0 ignored issues
show
Unused Code introduced by
The assignment to $newObj is dead and can be removed.
Loading history...
2206
        $this->assertArrayHasKey('Title', $map, 'Contains null fields');
2207
    }
2208
2209
    public function testIsEmpty()
2210
    {
2211
        $objEmpty = new DataObjectTest\Team();
2212
        $this->assertTrue($objEmpty->isEmpty(), 'New instance without populated defaults is empty');
2213
2214
        $objEmpty->Title = '0'; //
2215
        $this->assertFalse($objEmpty->isEmpty(), 'Zero value in attribute considered non-empty');
2216
    }
2217
2218
    public function testRelField()
2219
    {
2220
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
2221
        // Test traversal of a single has_one
2222
        $this->assertEquals("Team 1", $captain1->relField('FavouriteTeam.Title'));
2223
        // Test direct field access
2224
        $this->assertEquals("Captain", $captain1->relField('FirstName'));
2225
2226
        // Test empty link
2227
        $captain2 = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
2228
        $this->assertEmpty($captain2->relField('FavouriteTeam.Title'));
2229
        $this->assertNull($captain2->relField('FavouriteTeam.ReturnsNull'));
2230
        $this->assertNull($captain2->relField('FavouriteTeam.ReturnsNull.Title'));
2231
2232
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
2233
        // Test that we can traverse more than once, and that arbitrary methods are okay
2234
        $this->assertEquals("Team 1", $player->relField('Teams.First.Title'));
2235
2236
        $newPlayer = new DataObjectTest\Player();
2237
        $this->assertNull($newPlayer->relField('Teams.First.Title'));
2238
2239
        // Test that relField works on db field manipulations
2240
        $comment = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment3');
2241
        $this->assertEquals("PHIL IS A UNIQUE GUY, AND COMMENTS ON TEAM2", $comment->relField('Comment.UpperCase'));
2242
2243
        // relField throws exception on invalid properties
2244
        $this->expectException(LogicException::class);
2245
        $this->expectExceptionMessage("Not is not a relation/field on " . DataObjectTest\TeamComment::class);
2246
        $comment->relField('Not.A.Field');
2247
    }
2248
2249
    public function testRelObject()
2250
    {
2251
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
2252
2253
        // Test traversal of a single has_one
2254
        $this->assertInstanceOf(DBVarchar::class, $captain1->relObject('FavouriteTeam.Title'));
2255
        $this->assertEquals("Team 1", $captain1->relObject('FavouriteTeam.Title')->getValue());
2256
2257
        // Test empty link
2258
        $captain2 = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
2259
        $this->assertEmpty($captain2->relObject('FavouriteTeam.Title')->getValue());
2260
        $this->assertNull($captain2->relObject('FavouriteTeam.ReturnsNull.Title'));
2261
2262
        // Test direct field access
2263
        $this->assertInstanceOf(DBBoolean::class, $captain1->relObject('IsRetired'));
2264
        $this->assertEquals(1, $captain1->relObject('IsRetired')->getValue());
2265
2266
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
2267
        // Test that we can traverse more than once, and that arbitrary methods are okay
2268
        $this->assertInstanceOf(DBVarchar::class, $player->relObject('Teams.First.Title'));
2269
        $this->assertEquals("Team 1", $player->relObject('Teams.First.Title')->getValue());
2270
2271
        // relObject throws exception on invalid properties
2272
        $this->expectException(LogicException::class);
2273
        $this->expectExceptionMessage("Not is not a relation/field on " . DataObjectTest\Player::class);
2274
        $player->relObject('Not.A.Field');
2275
    }
2276
2277
    public function testLateStaticBindingStyle()
2278
    {
2279
        // Confirm that DataObjectTest_Player::get() operates as excepted
2280
        $this->assertEquals(4, DataObjectTest\Player::get()->count());
2281
        $this->assertInstanceOf(DataObjectTest\Player::class, DataObjectTest\Player::get()->first());
2282
2283
        // You can't pass arguments to LSB syntax - use the DataList methods instead.
2284
        $this->expectException(InvalidArgumentException::class);
2285
2286
        DataObjectTest\Player::get(null, "\"ID\" = 1");
2287
    }
2288
2289
    /**
2290
     * @expectedException \InvalidArgumentException
2291
     */
2292
    public function testBrokenLateStaticBindingStyle()
2293
    {
2294
        // If you call DataObject::get() you have to pass a first argument
2295
        DataObject::get();
2296
    }
2297
2298
    public function testBigIntField()
2299
    {
2300
        $staff = new DataObjectTest\Staff();
2301
        $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...
2302
        $staff->write();
2303
        $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...
2304
    }
2305
2306
    public function testGetOneMissingValueReturnsNull()
2307
    {
2308
2309
        // Test that missing values return null
2310
        $this->assertEquals(null, DataObject::get_one(
2311
            DataObjectTest\TeamComment::class,
2312
            ['"DataObjectTest_TeamComment"."Name"' => 'does not exists']
2313
        ));
2314
    }
2315
}
2316