Completed
Push — 4 ( afb3c8...0b5f5e )
by Loz
43s queued 20s
created

DataObjectTest::testZeroIsFalse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

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

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

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

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

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

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

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

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

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

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

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

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

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

469
        $this->assertEquals($team1ID, $captain1->/** @scrutinizer ignore-call */ FavouriteTeam()->ID);
Loading history...
470
471
        // Test that getNonReciprocalComponent can find has_one from the has_many end
472
        $this->assertEquals(
473
            $team1ID,
474
            $captain1->inferReciprocalComponent(DataObjectTest\Team::class, 'PlayerFans')->ID
0 ignored issues
show
Bug Best Practice introduced by
The property ID does not exist on SilverStripe\ORM\DataList. Since you implemented __get, consider adding a @property annotation.
Loading history...
475
        );
476
477
        // Check entity with polymorphic has-one
478
        $fan1 = $this->objFromFixture(DataObjectTest\Fan::class, "fan1");
479
        $this->assertTrue((bool)$fan1->hasValue('Favourite'));
480
481
        // There will be fields named (relname)ID and (relname)Class for polymorphic
482
        // entities
483
        $this->assertEquals($team1ID, $fan1->FavouriteID);
0 ignored issues
show
Bug Best Practice introduced by
The property FavouriteID does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
484
        $this->assertEquals(DataObjectTest\Team::class, $fan1->FavouriteClass);
0 ignored issues
show
Bug Best Practice introduced by
The property FavouriteClass does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
485
486
        // There will be a method called $obj->relname() that returns the object itself
487
        $favourite = $fan1->Favourite();
0 ignored issues
show
Bug introduced by
The method Favourite() does not exist on SilverStripe\ORM\DataObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

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

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

572
        $this->assertTrue($team1->/** @scrutinizer ignore-call */ Comments()->count() == 2);
Loading history...
573
574
        $team1Comments = [
575
            ['Comment' => 'This is a team comment by Joe'],
576
            ['Comment' => 'This is a team comment by Bob'],
577
        ];
578
579
        // Test the IDs on the DataObjects are set correctly
580
        $this->assertListEquals($team1Comments, $team1->Comments());
581
582
        // Test that has_many can be infered from the has_one via getNonReciprocalComponent
583
        $this->assertListEquals(
584
            $team1Comments,
585
            $team1->inferReciprocalComponent(DataObjectTest\TeamComment::class, 'Team')
0 ignored issues
show
Bug introduced by
It seems like $team1->inferReciprocalC...Comment::class, 'Team') can also be of type SilverStripe\ORM\DataObject; however, parameter $list of SilverStripe\Dev\SapphireTest::assertListEquals() does only seem to accept SilverStripe\ORM\SS_List, maybe add an additional type check? ( Ignorable by Annotation )

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

585
            /** @scrutinizer ignore-type */ $team1->inferReciprocalComponent(DataObjectTest\TeamComment::class, 'Team')
Loading history...
586
        );
587
588
        // Test that we can add and remove items that already exist in the database
589
        $newComment = new DataObjectTest\TeamComment();
590
        $newComment->Name = "Automated commenter";
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\Tests\DataObjectTest\TeamComment. Since you implemented __set, consider adding a @property annotation.
Loading history...
591
        $newComment->Comment = "This is a new comment";
0 ignored issues
show
Bug Best Practice introduced by
The property Comment does not exist on SilverStripe\ORM\Tests\DataObjectTest\TeamComment. Since you implemented __set, consider adding a @property annotation.
Loading history...
592
        $newComment->write();
593
        $team1->Comments()->add($newComment);
594
        $this->assertEquals($team1->ID, $newComment->TeamID);
0 ignored issues
show
Bug Best Practice introduced by
The property TeamID does not exist on SilverStripe\ORM\Tests\DataObjectTest\TeamComment. Since you implemented __get, consider adding a @property annotation.
Loading history...
595
596
        $comment1 = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment1');
597
        $comment2 = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment2');
598
        $team1->Comments()->remove($comment2);
599
600
        $team1CommentIDs = $team1->Comments()->sort('ID')->column('ID');
0 ignored issues
show
Bug introduced by
The method sort() does not exist on SilverStripe\ORM\SS_List. It seems like you code against a sub-type of said class. However, the method does not exist in SilverStripe\ORM\Filterable or SilverStripe\ORM\Limitable. Are you sure you never get one of those? ( Ignorable by Annotation )

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

600
        $team1CommentIDs = $team1->Comments()->/** @scrutinizer ignore-call */ sort('ID')->column('ID');
Loading history...
601
        $this->assertEquals(array($comment1->ID, $newComment->ID), $team1CommentIDs);
602
603
        // Test that removing an item from a list doesn't remove it from the same
604
        // relation belonging to a different object
605
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
606
        $team2 = $this->objFromFixture(DataObjectTest\Team::class, 'team2');
607
        $team2->Comments()->remove($comment1);
608
        $team1CommentIDs = $team1->Comments()->sort('ID')->column('ID');
609
        $this->assertEquals(array($comment1->ID, $newComment->ID), $team1CommentIDs);
610
    }
611
612
613
    /**
614
     * Test has many relationships against polymorphic has_one fields
615
     *   - Test getComponents() gets the ComponentSet of the other side of the relation
616
     *   - Test the IDs on the DataObjects are set correctly
617
     */
618
    public function testHasManyPolymorphicRelationships()
619
    {
620
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
621
622
        // Test getComponents() gets the ComponentSet of the other side of the relation
623
        $this->assertTrue($team1->Fans()->count() == 2);
0 ignored issues
show
Bug introduced by
The method Fans() does not exist on SilverStripe\ORM\DataObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

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

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

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

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

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

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

1654
        /** @scrutinizer ignore-call */ 
1655
        $equipmentSuppliers = $team->EquipmentSuppliers();
Loading history...
1655
1656
        // Check that DataObject::many_many() works as expected
1657
        $manyManyComponent = DataObject::getSchema()->manyManyComponent(DataObjectTest\Team::class, 'Sponsors');
1658
        $this->assertEquals(ManyManyList::class, $manyManyComponent['relationClass']);
1659
        $this->assertEquals(
1660
            DataObjectTest\Team::class,
1661
            $manyManyComponent['parentClass'],
1662
            'DataObject::many_many() didn\'t find the correct base class'
1663
        );
1664
        $this->assertEquals(
1665
            DataObjectTest\EquipmentCompany::class,
1666
            $manyManyComponent['childClass'],
1667
            'DataObject::many_many() didn\'t find the correct target class for the relation'
1668
        );
1669
        $this->assertEquals(
1670
            'DataObjectTest_EquipmentCompany_SponsoredTeams',
1671
            $manyManyComponent['join'],
1672
            'DataObject::many_many() didn\'t find the correct relation table'
1673
        );
1674
        $this->assertEquals('DataObjectTest_TeamID', $manyManyComponent['parentField']);
1675
        $this->assertEquals('DataObjectTest_EquipmentCompanyID', $manyManyComponent['childField']);
1676
1677
        // Check that ManyManyList still works
1678
        $this->assertEquals(2, $sponsors->count(), 'Rows are missing from relation');
1679
        $this->assertEquals(1, $equipmentSuppliers->count(), 'Rows are missing from relation');
1680
1681
        // Check everything works when no relation is present
1682
        $teamWithoutSponsor = $this->objFromFixture(DataObjectTest\Team::class, 'team3');
1683
        $this->assertInstanceOf(ManyManyList::class, $teamWithoutSponsor->Sponsors());
1684
        $this->assertEquals(0, $teamWithoutSponsor->Sponsors()->count());
1685
1686
        // Test that belongs_many_many can be infered from with getNonReciprocalComponent
1687
        $this->assertListEquals(
1688
            [
1689
                ['Name' => 'Company corp'],
1690
                ['Name' => 'Team co.'],
1691
            ],
1692
            $team->inferReciprocalComponent(DataObjectTest\EquipmentCompany::class, 'SponsoredTeams')
0 ignored issues
show
Bug introduced by
It seems like $team->inferReciprocalCo...lass, 'SponsoredTeams') can also be of type SilverStripe\ORM\DataObject; however, parameter $list of SilverStripe\Dev\SapphireTest::assertListEquals() does only seem to accept SilverStripe\ORM\SS_List, maybe add an additional type check? ( Ignorable by Annotation )

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

1692
            /** @scrutinizer ignore-type */ $team->inferReciprocalComponent(DataObjectTest\EquipmentCompany::class, 'SponsoredTeams')
Loading history...
1693
        );
1694
1695
        // Test that many_many can be infered from getNonReciprocalComponent
1696
        $this->assertListEquals(
1697
            [
1698
                ['Title' => 'Team 1'],
1699
                ['Title' => 'Team 2'],
1700
                ['Title' => 'Subteam 1'],
1701
            ],
1702
            $company2->inferReciprocalComponent(DataObjectTest\Team::class, 'Sponsors')
1703
        );
1704
1705
        // Check many_many_extraFields still works
1706
        $equipmentCompany = $this->objFromFixture(DataObjectTest\EquipmentCompany::class, 'equipmentcompany1');
1707
        $equipmentCompany->SponsoredTeams()->add($teamWithoutSponsor, array('SponsorFee' => 1000));
0 ignored issues
show
Bug introduced by
The method SponsoredTeams() does not exist on SilverStripe\ORM\DataObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

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

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

1815
        $player = $newTeam->/** @scrutinizer ignore-call */ Players()->first();
Loading history...
1816
        $this->assertEquals('Sam', $player->FirstName);
1817
        $this->assertEquals("Prop", $player->Position);
1818
1819
        // Check that ordering a many-many relation by an aggregate column doesn't fail
1820
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
1821
        $player->Teams()->sort("count(DISTINCT \"DataObjectTest_Team_Players\".\"DataObjectTest_PlayerID\") DESC");
0 ignored issues
show
Bug introduced by
The method Teams() does not exist on SilverStripe\ORM\DataObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

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

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

2129
        $this->assertEquals($company->ID, $ceo->/** @scrutinizer ignore-call */ Company()->ID, 'belongs_to returns the right results.');
Loading history...
2130
2131
        // Test belongs_to can be infered via getNonReciprocalComponent
2132
        // Note: Will be returned as has_many since the belongs_to is ignored.
2133
        $this->assertListEquals(
2134
            [['Name' => 'New Company']],
2135
            $ceo->inferReciprocalComponent(DataObjectTest\Company::class, 'CEO')
0 ignored issues
show
Bug introduced by
It seems like $ceo->inferReciprocalCom...\Company::class, 'CEO') can also be of type SilverStripe\ORM\DataObject; however, parameter $list of SilverStripe\Dev\SapphireTest::assertListEquals() does only seem to accept SilverStripe\ORM\SS_List, maybe add an additional type check? ( Ignorable by Annotation )

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

2135
            /** @scrutinizer ignore-type */ $ceo->inferReciprocalComponent(DataObjectTest\Company::class, 'CEO')
Loading history...
2136
        );
2137
2138
        // Test has_one to a belongs_to can be infered via getNonReciprocalComponent
2139
        $this->assertEquals(
2140
            $ceo->ID,
2141
            $company->inferReciprocalComponent(DataObjectTest\CEO::class, 'Company')->ID
0 ignored issues
show
Bug Best Practice introduced by
The property ID does not exist on SilverStripe\ORM\DataList. Since you implemented __get, consider adding a @property annotation.
Loading history...
2142
        );
2143
2144
        // Test automatic creation of class where no assigment exists
2145
        $ceo = new DataObjectTest\CEO();
2146
        $ceo->write();
2147
2148
        $this->assertTrue(
2149
            $ceo->Company() instanceof DataObjectTest\Company,
2150
            'DataObjects across belongs_to relations are automatically created.'
2151
        );
2152
        $this->assertEquals($ceo->ID, $ceo->Company()->CEOID, 'Remote IDs are automatically set.');
2153
2154
        // Write object with components
2155
        $ceo->Name = 'Edward Scissorhands';
0 ignored issues
show
Bug Best Practice introduced by
The property Name does not exist on SilverStripe\ORM\Tests\DataObjectTest\CEO. Since you implemented __set, consider adding a @property annotation.
Loading history...
2156
        $ceo->write(false, false, false, true);
2157
        $this->assertTrue($ceo->Company()->isInDB(), 'write() writes belongs_to components to the database.');
2158
2159
        $newCEO = DataObject::get_by_id(DataObjectTest\CEO::class, $ceo->ID);
2160
        $this->assertEquals(
2161
            $ceo->Company()->ID,
2162
            $newCEO->Company()->ID,
0 ignored issues
show
Bug introduced by
The method Company() does not exist on SilverStripe\ORM\DataObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

2162
            $newCEO->/** @scrutinizer ignore-call */ 
2163
                     Company()->ID,
Loading history...
2163
            'belongs_to can be retrieved from the database.'
2164
        );
2165
    }
2166
2167
    public function testBelongsToPolymorphic()
2168
    {
2169
        $company = new DataObjectTest\Company();
2170
        $ceo = new DataObjectTest\CEO();
2171
2172
        $company->write();
2173
        $ceo->write();
2174
2175
        // Test belongs_to assignment
2176
        $company->OwnerID = $ceo->ID;
0 ignored issues
show
Bug Best Practice introduced by
The property OwnerID does not exist on SilverStripe\ORM\Tests\DataObjectTest\Company. Since you implemented __set, consider adding a @property annotation.
Loading history...
2177
        $company->OwnerClass = DataObjectTest\CEO::class;
0 ignored issues
show
Bug Best Practice introduced by
The property OwnerClass does not exist on SilverStripe\ORM\Tests\DataObjectTest\Company. Since you implemented __set, consider adding a @property annotation.
Loading history...
2178
        $company->write();
2179
2180
        $this->assertEquals($company->ID, $ceo->CompanyOwned()->ID, 'belongs_to returns the right results.');
0 ignored issues
show
Bug introduced by
The method CompanyOwned() does not exist on SilverStripe\ORM\Tests\DataObjectTest\CEO. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

2180
        $this->assertEquals($company->ID, $ceo->/** @scrutinizer ignore-call */ CompanyOwned()->ID, 'belongs_to returns the right results.');
Loading history...
2181
        $this->assertInstanceOf(
2182
            DataObjectTest\Company::class,
2183
            $ceo->CompanyOwned(),
2184
            'belongs_to returns the right results.'
2185
        );
2186
2187
        // Test automatic creation of class where no assigment exists
2188
        $ceo = new DataObjectTest\CEO();
2189
        $ceo->write();
2190
2191
        $this->assertTrue(
2192
            $ceo->CompanyOwned() instanceof DataObjectTest\Company,
2193
            'DataObjects across polymorphic belongs_to relations are automatically created.'
2194
        );
2195
        $this->assertEquals($ceo->ID, $ceo->CompanyOwned()->OwnerID, 'Remote IDs are automatically set.');
2196
        $this->assertInstanceOf($ceo->CompanyOwned()->OwnerClass, $ceo, 'Remote class is automatically  set');
2197
2198
        // Write object with components
2199
        $ceo->write(false, false, false, true);
2200
        $this->assertTrue($ceo->CompanyOwned()->isInDB(), 'write() writes belongs_to components to the database.');
2201
2202
        $newCEO = DataObject::get_by_id(DataObjectTest\CEO::class, $ceo->ID);
2203
        $this->assertEquals(
2204
            $ceo->CompanyOwned()->ID,
2205
            $newCEO->CompanyOwned()->ID,
0 ignored issues
show
Bug introduced by
The method CompanyOwned() does not exist on SilverStripe\ORM\DataObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

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