Passed
Pull Request — 4 (#10017)
by Thomas
09:46
created

DataObjectTest::testDBObjectEnum()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

587
        $this->assertTrue($team1->/** @scrutinizer ignore-call */ Comments()->count() == 2);
Loading history...
588
589
        $team1Comments = [
590
            ['Comment' => 'This is a team comment by Joe'],
591
            ['Comment' => 'This is a team comment by Bob'],
592
        ];
593
594
        // Test the IDs on the DataObjects are set correctly
595
        $this->assertListEquals($team1Comments, $team1->Comments());
596
597
        // Test that has_many can be inferred from the has_one via getNonReciprocalComponent
598
        $this->assertListEquals(
599
            $team1Comments,
600
            $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

600
            /** @scrutinizer ignore-type */ $team1->inferReciprocalComponent(DataObjectTest\TeamComment::class, 'Team')
Loading history...
601
        );
602
603
        // Test that we can add and remove items that already exist in the database
604
        $newComment = new DataObjectTest\TeamComment();
605
        $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...
606
        $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...
607
        $newComment->write();
608
        $team1->Comments()->add($newComment);
609
        $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...
610
611
        $comment1 = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment1');
612
        $comment2 = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment2');
613
        $team1->Comments()->remove($comment2);
614
615
        $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

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

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

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

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

1702
        /** @scrutinizer ignore-call */ 
1703
        $equipmentSuppliers = $team->EquipmentSuppliers();
Loading history...
1703
1704
        // Check that DataObject::many_many() works as expected
1705
        $manyManyComponent = DataObject::getSchema()->manyManyComponent(DataObjectTest\Team::class, 'Sponsors');
1706
        $this->assertEquals(ManyManyList::class, $manyManyComponent['relationClass']);
1707
        $this->assertEquals(
1708
            DataObjectTest\Team::class,
1709
            $manyManyComponent['parentClass'],
1710
            'DataObject::many_many() didn\'t find the correct base class'
1711
        );
1712
        $this->assertEquals(
1713
            DataObjectTest\EquipmentCompany::class,
1714
            $manyManyComponent['childClass'],
1715
            'DataObject::many_many() didn\'t find the correct target class for the relation'
1716
        );
1717
        $this->assertEquals(
1718
            'DataObjectTest_EquipmentCompany_SponsoredTeams',
1719
            $manyManyComponent['join'],
1720
            'DataObject::many_many() didn\'t find the correct relation table'
1721
        );
1722
        $this->assertEquals('DataObjectTest_TeamID', $manyManyComponent['parentField']);
1723
        $this->assertEquals('DataObjectTest_EquipmentCompanyID', $manyManyComponent['childField']);
1724
1725
        // Check that ManyManyList still works
1726
        $this->assertEquals(2, $sponsors->count(), 'Rows are missing from relation');
1727
        $this->assertEquals(1, $equipmentSuppliers->count(), 'Rows are missing from relation');
1728
1729
        // Check everything works when no relation is present
1730
        $teamWithoutSponsor = $this->objFromFixture(DataObjectTest\Team::class, 'team3');
1731
        $this->assertInstanceOf(ManyManyList::class, $teamWithoutSponsor->Sponsors());
1732
        $this->assertEquals(0, $teamWithoutSponsor->Sponsors()->count());
1733
1734
        // Test that belongs_many_many can be inferred from with getNonReciprocalComponent
1735
        $this->assertListEquals(
1736
            [
1737
                ['Name' => 'Company corp'],
1738
                ['Name' => 'Team co.'],
1739
            ],
1740
            $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

1740
            /** @scrutinizer ignore-type */ $team->inferReciprocalComponent(DataObjectTest\EquipmentCompany::class, 'SponsoredTeams')
Loading history...
1741
        );
1742
1743
        // Test that many_many can be inferred from getNonReciprocalComponent
1744
        $this->assertListEquals(
1745
            [
1746
                ['Title' => 'Team 1'],
1747
                ['Title' => 'Team 2'],
1748
                ['Title' => 'Subteam 1'],
1749
            ],
1750
            $company2->inferReciprocalComponent(DataObjectTest\Team::class, 'Sponsors')
1751
        );
1752
1753
        // Check many_many_extraFields still works
1754
        $equipmentCompany = $this->objFromFixture(DataObjectTest\EquipmentCompany::class, 'equipmentcompany1');
1755
        $equipmentCompany->SponsoredTeams()->add($teamWithoutSponsor, ['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

1755
        $equipmentCompany->/** @scrutinizer ignore-call */ 
1756
                           SponsoredTeams()->add($teamWithoutSponsor, ['SponsorFee' => 1000]);
Loading history...
1756
        $sponsoredTeams = $equipmentCompany->SponsoredTeams();
1757
        $this->assertEquals(
1758
            1000,
1759
            $sponsoredTeams->byID($teamWithoutSponsor->ID)->SponsorFee,
1760
            'Data from many_many_extraFields was not stored/extracted correctly'
1761
        );
1762
1763
        // Check subclasses correctly inherit multiple many_manys
1764
        $subTeam = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
1765
        $this->assertEquals(
1766
            2,
1767
            $subTeam->Sponsors()->count(),
1768
            'Child class did not inherit multiple many_manys'
1769
        );
1770
        $this->assertEquals(
1771
            1,
1772
            $subTeam->EquipmentSuppliers()->count(),
1773
            'Child class did not inherit multiple many_manys'
1774
        );
1775
        // Team 2 has one EquipmentCompany sponsor and one SubEquipmentCompany
1776
        $team2 = $this->objFromFixture(DataObjectTest\Team::class, 'team2');
1777
        $this->assertEquals(
1778
            2,
1779
            $team2->Sponsors()->count(),
1780
            'Child class did not inherit multiple belongs_many_manys'
1781
        );
1782
1783
        // Check many_many_extraFields also works from the belongs_many_many side
1784
        $sponsors = $team2->Sponsors();
1785
        $sponsors->add($equipmentCompany, ['SponsorFee' => 750]);
1786
        $this->assertEquals(
1787
            750,
1788
            $sponsors->byID($equipmentCompany->ID)->SponsorFee,
1789
            'Data from many_many_extraFields was not stored/extracted correctly'
1790
        );
1791
1792
        $subEquipmentCompany = $this->objFromFixture(DataObjectTest\SubEquipmentCompany::class, 'subequipmentcompany1');
1793
        $subTeam->Sponsors()->add($subEquipmentCompany, ['SponsorFee' => 1200]);
1794
        $this->assertEquals(
1795
            1200,
1796
            $subTeam->Sponsors()->byID($subEquipmentCompany->ID)->SponsorFee,
1797
            'Data from inherited many_many_extraFields was not stored/extracted correctly'
1798
        );
1799
    }
1800
1801
    public function testManyManyExtraFields()
1802
    {
1803
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1804
        $schema = DataObject::getSchema();
1805
1806
        // Get all extra fields
1807
        $teamExtraFields = $team->manyManyExtraFields();
1808
        $this->assertEquals(
1809
            [
1810
                'Players' => ['Position' => 'Varchar(100)']
1811
            ],
1812
            $teamExtraFields
1813
        );
1814
1815
        // Ensure fields from parent classes are included
1816
        $subTeam = singleton(DataObjectTest\SubTeam::class);
1817
        $teamExtraFields = $subTeam->manyManyExtraFields();
1818
        $this->assertEquals(
1819
            [
1820
                'Players' => ['Position' => 'Varchar(100)'],
1821
                'FormerPlayers' => ['Position' => 'Varchar(100)']
1822
            ],
1823
            $teamExtraFields
1824
        );
1825
1826
        // Extra fields are immediately available on the Team class (defined in $many_many_extraFields)
1827
        $teamExtraFields = $schema->manyManyExtraFieldsForComponent(DataObjectTest\Team::class, 'Players');
1828
        $this->assertEquals(
1829
            $teamExtraFields,
1830
            [
1831
                'Position' => 'Varchar(100)'
1832
            ]
1833
        );
1834
1835
        // We'll have to go through the relation to get the extra fields on Player
1836
        $playerExtraFields = $schema->manyManyExtraFieldsForComponent(DataObjectTest\Player::class, 'Teams');
1837
        $this->assertEquals(
1838
            $playerExtraFields,
1839
            [
1840
                'Position' => 'Varchar(100)'
1841
            ]
1842
        );
1843
1844
        // Iterate through a many-many relationship and confirm that extra fields are included
1845
        $newTeam = new DataObjectTest\Team();
1846
        $newTeam->Title = "New team";
1847
        $newTeam->write();
1848
        $newTeamID = $newTeam->ID;
1849
1850
        $newPlayer = new DataObjectTest\Player();
1851
        $newPlayer->FirstName = "Sam";
1852
        $newPlayer->Surname = "Minnee";
1853
        $newPlayer->write();
1854
1855
        // The idea of Sam as a prop is essentially humourous.
1856
        $newTeam->Players()->add($newPlayer, ["Position" => "Prop"]);
1857
1858
        // Requery and uncache everything
1859
        $newTeam->flushCache();
1860
        $newTeam = DataObject::get_by_id(DataObjectTest\Team::class, $newTeamID);
1861
1862
        // Check that the Position many_many_extraField is extracted.
1863
        $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

1863
        $player = $newTeam->/** @scrutinizer ignore-call */ Players()->first();
Loading history...
1864
        $this->assertEquals('Sam', $player->FirstName);
1865
        $this->assertEquals("Prop", $player->Position);
1866
1867
        // Check that ordering a many-many relation by an aggregate column doesn't fail
1868
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
1869
        $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

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

2177
        $this->assertEquals($company->ID, $ceo->/** @scrutinizer ignore-call */ Company()->ID, 'belongs_to returns the right results.');
Loading history...
2178
2179
        // Test belongs_to can be inferred via getNonReciprocalComponent
2180
        // Note: Will be returned as has_many since the belongs_to is ignored.
2181
        $this->assertListEquals(
2182
            [['Name' => 'New Company']],
2183
            $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

2183
            /** @scrutinizer ignore-type */ $ceo->inferReciprocalComponent(DataObjectTest\Company::class, 'CEO')
Loading history...
2184
        );
2185
2186
        // Test has_one to a belongs_to can be inferred via getNonReciprocalComponent
2187
        $this->assertEquals(
2188
            $ceo->ID,
2189
            $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...
2190
        );
2191
2192
        // Test automatic creation of class where no assignment exists
2193
        $ceo = new DataObjectTest\CEO();
2194
        $ceo->write();
2195
2196
        $this->assertTrue(
2197
            $ceo->Company() instanceof DataObjectTest\Company,
2198
            'DataObjects across belongs_to relations are automatically created.'
2199
        );
2200
        $this->assertEquals($ceo->ID, $ceo->Company()->CEOID, 'Remote IDs are automatically set.');
2201
2202
        // Write object with components
2203
        $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...
2204
        $ceo->write(false, false, false, true);
2205
        $this->assertFalse(
2206
            $ceo->Company()->isInDB(),
2207
            'write() does not write belongs_to components to the database that do not already exist.'
2208
        );
2209
2210
        $newCEO = DataObject::get_by_id(DataObjectTest\CEO::class, $ceo->ID);
2211
        $this->assertEquals(
2212
            $ceo->Company()->ID,
2213
            $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

2213
            $newCEO->/** @scrutinizer ignore-call */ 
2214
                     Company()->ID,
Loading history...
2214
            'belongs_to can be retrieved from the database.'
2215
        );
2216
    }
2217
2218
    public function testBelongsToPolymorphic()
2219
    {
2220
        $company = new DataObjectTest\Company();
2221
        $ceo = new DataObjectTest\CEO();
2222
2223
        $company->write();
2224
        $ceo->write();
2225
2226
        // Test belongs_to assignment
2227
        $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...
2228
        $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...
2229
        $company->write();
2230
2231
        $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

2231
        $this->assertEquals($company->ID, $ceo->/** @scrutinizer ignore-call */ CompanyOwned()->ID, 'belongs_to returns the right results.');
Loading history...
2232
        $this->assertInstanceOf(
2233
            DataObjectTest\Company::class,
2234
            $ceo->CompanyOwned(),
2235
            'belongs_to returns the right results.'
2236
        );
2237
2238
        // Test automatic creation of class where no assignment exists
2239
        $ceo = new DataObjectTest\CEO();
2240
        $ceo->write();
2241
2242
        $this->assertTrue(
2243
            $ceo->CompanyOwned() instanceof DataObjectTest\Company,
2244
            'DataObjects across polymorphic belongs_to relations are automatically created.'
2245
        );
2246
        $this->assertEquals($ceo->ID, $ceo->CompanyOwned()->OwnerID, 'Remote IDs are automatically set.');
2247
        $this->assertInstanceOf($ceo->CompanyOwned()->OwnerClass, $ceo, 'Remote class is automatically set.');
2248
2249
        // Skip writing components that do not exist
2250
        $ceo->write(false, false, false, true);
2251
        $this->assertFalse(
2252
            $ceo->CompanyOwned()->isInDB(),
2253
            'write() does not write belongs_to components to the database that do not already exist.'
2254
        );
2255
2256
        $newCEO = DataObject::get_by_id(DataObjectTest\CEO::class, $ceo->ID);
2257
        $this->assertEquals(
2258
            $ceo->CompanyOwned()->ID,
2259
            $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

2259
            $newCEO->/** @scrutinizer ignore-call */ 
2260
                     CompanyOwned()->ID,
Loading history...
2260
            'polymorphic belongs_to can be retrieved from the database.'
2261
        );
2262
    }
2263
2264
    /**
2265
     * @expectedException LogicException
2266
     */
2267
    public function testInvalidate()
2268
    {
2269
        $do = new DataObjectTest\Fixture();
2270
        $do->write();
2271
2272
        $do->delete();
2273
2274
        $do->delete(); // Prohibit invalid object manipulation
2275
        $do->write();
2276
        $do->duplicate();
2277
    }
2278
2279
    public function testToMap()
2280
    {
2281
        $obj = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
2282
2283
        $map = $obj->toMap();
2284
2285
        $this->assertArrayHasKey('ID', $map, 'Should contain ID');
2286
        $this->assertArrayHasKey('ClassName', $map, 'Should contain ClassName');
2287
        $this->assertArrayHasKey('Created', $map, 'Should contain base Created');
2288
        $this->assertArrayHasKey('LastEdited', $map, 'Should contain base LastEdited');
2289
        $this->assertArrayHasKey('Title', $map, 'Should contain fields from parent class');
2290
        $this->assertArrayHasKey('SubclassDatabaseField', $map, 'Should contain fields from concrete class');
2291
2292
        $this->assertEquals('DB value of SubclassFieldWithOverride (override)', $obj->SubclassFieldWithOverride, 'Object uses custom field getter');
0 ignored issues
show
Bug Best Practice introduced by
The property SubclassFieldWithOverride does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
2293
        $this->assertEquals('DB value of SubclassFieldWithOverride', $map['SubclassFieldWithOverride'], 'toMap does not use custom field getter');
2294
2295
        $this->assertEquals(
2296
            $obj->ID,
2297
            $map['ID'],
2298
            'Contains values from base fields'
2299
        );
2300
        $this->assertEquals(
2301
            $obj->Title,
2302
            $map['Title'],
2303
            'Contains values from parent class fields'
2304
        );
2305
        $this->assertEquals(
2306
            $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...
2307
            $map['SubclassDatabaseField'],
2308
            'Contains values from concrete class fields'
2309
        );
2310
2311
        $newObj = new DataObjectTest\SubTeam(['Title' => null]);
2312
        $this->assertArrayNotHasKey('Title', $newObj->toMap(), 'Should not contain new null fields');
2313
2314
        $newObj->Title = '';
2315
        $this->assertArrayHasKey('Title', $newObj->toMap(), 'Should contain fields once they are set, even if falsey');
2316
2317
        $newObj->Title = null;
2318
        $this->assertArrayNotHasKey('Title', $newObj->toMap(), 'Should not contain reset-to-null fields');
2319
2320
        $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam3_with_empty_fields');
2321
        $this->assertArrayNotHasKey('SubclassDatabaseField', $newObj->toMap(), 'Should not contain null re-hydrated fields');
2322
    }
2323
2324
    public function testIsEmpty()
2325
    {
2326
        $objEmpty = new DataObjectTest\Team();
2327
        $this->assertTrue($objEmpty->isEmpty(), 'New instance without populated defaults is empty');
2328
2329
        $objEmpty->Title = '0'; //
2330
        $this->assertFalse($objEmpty->isEmpty(), 'Zero value in attribute considered non-empty');
2331
    }
2332
2333
    public function testRelField()
2334
    {
2335
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
2336
        // Test traversal of a single has_one
2337
        $this->assertEquals("Team 1", $captain1->relField('FavouriteTeam.Title'));
2338
        // Test direct field access
2339
        $this->assertEquals("Captain", $captain1->relField('FirstName'));
2340
2341
        // Test empty link
2342
        $captain2 = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
2343
        $this->assertEmpty($captain2->relField('FavouriteTeam.Title'));
2344
        $this->assertNull($captain2->relField('FavouriteTeam.ReturnsNull'));
2345
        $this->assertNull($captain2->relField('FavouriteTeam.ReturnsNull.Title'));
2346
2347
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
2348
        // Test that we can traverse more than once, and that arbitrary methods are okay
2349
        $this->assertEquals("Team 1", $player->relField('Teams.First.Title'));
2350
2351
        $newPlayer = new DataObjectTest\Player();
2352
        $this->assertNull($newPlayer->relField('Teams.First.Title'));
2353
2354
        // Test that relField works on db field manipulations
2355
        $comment = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment3');
2356
        $this->assertEquals("PHIL IS A UNIQUE GUY, AND COMMENTS ON TEAM2", $comment->relField('Comment.UpperCase'));
2357
2358
        // relField throws exception on invalid properties
2359
        $this->expectException(LogicException::class);
2360
        $this->expectExceptionMessage("Not is not a relation/field on " . DataObjectTest\TeamComment::class);
2361
        $comment->relField('Not.A.Field');
2362
    }
2363
2364
    public function testRelObject()
2365
    {
2366
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
2367
2368
        // Test traversal of a single has_one
2369
        $this->assertInstanceOf(DBVarchar::class, $captain1->relObject('FavouriteTeam.Title'));
2370
        $this->assertEquals("Team 1", $captain1->relObject('FavouriteTeam.Title')->getValue());
2371
2372
        // Test empty link
2373
        $captain2 = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
2374
        $this->assertEmpty($captain2->relObject('FavouriteTeam.Title')->getValue());
2375
        $this->assertNull($captain2->relObject('FavouriteTeam.ReturnsNull.Title'));
2376
2377
        // Test direct field access
2378
        $this->assertInstanceOf(DBBoolean::class, $captain1->relObject('IsRetired'));
2379
        $this->assertEquals(1, $captain1->relObject('IsRetired')->getValue());
2380
2381
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
2382
        // Test that we can traverse more than once, and that arbitrary methods are okay
2383
        $this->assertInstanceOf(DBVarchar::class, $player->relObject('Teams.First.Title'));
2384
        $this->assertEquals("Team 1", $player->relObject('Teams.First.Title')->getValue());
2385
2386
        // relObject throws exception on invalid properties
2387
        $this->expectException(LogicException::class);
2388
        $this->expectExceptionMessage("Not is not a relation/field on " . DataObjectTest\Player::class);
2389
        $player->relObject('Not.A.Field');
2390
    }
2391
2392
    public function testLateStaticBindingStyle()
2393
    {
2394
        // Confirm that DataObjectTest_Player::get() operates as excepted
2395
        $this->assertEquals(4, DataObjectTest\Player::get()->count());
2396
        $this->assertInstanceOf(DataObjectTest\Player::class, DataObjectTest\Player::get()->first());
2397
2398
        // You can't pass arguments to LSB syntax - use the DataList methods instead.
2399
        $this->expectException(InvalidArgumentException::class);
2400
2401
        DataObjectTest\Player::get(null, "\"ID\" = 1");
2402
    }
2403
2404
    /**
2405
     * @expectedException \InvalidArgumentException
2406
     */
2407
    public function testBrokenLateStaticBindingStyle()
2408
    {
2409
        // If you call DataObject::get() you have to pass a first argument
2410
        DataObject::get();
2411
    }
2412
2413
    public function testBigIntField()
2414
    {
2415
        $staff = new DataObjectTest\Staff();
2416
        $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...
2417
        $staff->write();
2418
        $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...
2419
    }
2420
2421
    public function testGetOneMissingValueReturnsNull()
2422
    {
2423
2424
        // Test that missing values return null
2425
        $this->assertEquals(null, DataObject::get_one(
2426
            DataObjectTest\TeamComment::class,
2427
            ['"DataObjectTest_TeamComment"."Name"' => 'does not exists']
2428
        ));
2429
    }
2430
2431
    public function testSetFieldWithArrayOnScalarOnlyField()
2432
    {
2433
        $this->expectException(InvalidArgumentException::class);
2434
        $do = Company::singleton();
2435
        $do->FoundationYear = '1984';
2436
        $do->FoundationYear = ['Amount' => 123, 'Currency' => 'CAD'];
2437
        $this->assertEmpty($do->FoundationYear);
2438
    }
2439
2440
    public function testSetFieldWithArrayOnCompositeField()
2441
    {
2442
        $do = Company::singleton();
2443
        $do->SalaryCap = ['Amount' => 123456, 'Currency' => 'CAD'];
2444
        $this->assertNotEmpty($do->SalaryCap);
2445
    }
2446
2447
    public function testWriteManipulationWithNonScalarValuesAllowed()
2448
    {
2449
        $do = DataObjectTest\MockDynamicAssignmentDataObject::create();
2450
        $do->write();
2451
2452
        $do->StaticScalarOnlyField = true;
2453
        $do->DynamicScalarOnlyField = false;
2454
        $do->DynamicField = true;
2455
2456
        $do->write();
2457
2458
        $this->assertTrue($do->StaticScalarOnlyField);
2459
        $this->assertFalse($do->DynamicScalarOnlyField);
2460
        $this->assertTrue($do->DynamicField);
2461
    }
2462
2463
    public function testWriteManipulationWithNonScalarValuesDisallowed()
2464
    {
2465
        $this->expectException(InvalidArgumentException::class);
2466
2467
        $do = DataObjectTest\MockDynamicAssignmentDataObject::create();
2468
        $do->write();
2469
2470
        $do->StaticScalarOnlyField = false;
2471
        $do->DynamicScalarOnlyField = true;
2472
        $do->DynamicField = false;
2473
2474
        $do->write();
2475
    }
2476
2477
    public function testRecursiveWrite()
2478
    {
2479
2480
        $root = $this->objFromFixture(TreeNode::class, 'root');
2481
        $child = $this->objFromFixture(TreeNode::class, 'child');
2482
        $grandchild = $this->objFromFixture(TreeNode::class, 'grandchild');
2483
2484
        // Create a cycle ... this will test that we can't create an infinite loop
2485
        $root->CycleID = $grandchild->ID;
0 ignored issues
show
Bug Best Practice introduced by
The property CycleID does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
2486
        $root->write();
2487
2488
        // Our count will have been set while loading our fixtures, let's reset everything back to 0
2489
        TreeNode::singleton()->resetCounts();
2490
        $root = TreeNode::get()->byID($root->ID);
2491
        $child = TreeNode::get()->byID($child->ID);
2492
        $grandchild = TreeNode::get()->byID($grandchild->ID);
2493
        $this->assertEquals(0, $root->WriteCount, 'Root node write count has been reset');
0 ignored issues
show
Bug Best Practice introduced by
The property WriteCount does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
2494
        $this->assertEquals(0, $child->WriteCount, 'Child node write count has been reset');
2495
        $this->assertEquals(0, $grandchild->WriteCount, 'Grand Child node write count has been reset');
2496
2497
        // Trigger a recursive write of the grand children
2498
        $grandchild->write(false, false, false, true);
2499
2500
        // Reload the DataObject from the DB to get the new Write Counts
2501
        $root = TreeNode::get()->byID($root->ID);
2502
        $child = TreeNode::get()->byID($child->ID);
2503
        $grandchild = TreeNode::get()->byID($grandchild->ID);
2504
2505
        $this->assertEquals(
2506
            1,
2507
            $grandchild->WriteCount,
2508
            'Grand child has been written once because write was directly called on it'
2509
        );
2510
        $this->assertEquals(
2511
            1,
2512
            $child->WriteCount,
2513
            'Child should has been written once because it is directly related to grand child'
2514
        );
2515
        $this->assertEquals(
2516
            1,
2517
            $root->WriteCount,
2518
            'Root should have been written once because it is indirectly related to grand child'
2519
        );
2520
    }
2521
2522
    public function testShallowRecursiveWrite()
2523
    {
2524
        $root = $this->objFromFixture(TreeNode::class, 'root');
2525
        $child = $this->objFromFixture(TreeNode::class, 'child');
2526
        $grandchild = $this->objFromFixture(TreeNode::class, 'grandchild');
2527
2528
        // Create a cycle ... this will test that we can't create an infinite loop
2529
        $root->CycleID = $grandchild->ID;
0 ignored issues
show
Bug Best Practice introduced by
The property CycleID does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
2530
        $root->write();
2531
2532
        // Our count will have been set while loading our fixtures, let's reset everything back to 0
2533
        TreeNode::singleton()->resetCounts();
2534
        $root = TreeNode::get()->byID($root->ID);
2535
        $child = TreeNode::get()->byID($child->ID);
2536
        $grandchild = TreeNode::get()->byID($grandchild->ID);
2537
        $this->assertEquals(0, $root->WriteCount);
0 ignored issues
show
Bug Best Practice introduced by
The property WriteCount does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
2538
        $this->assertEquals(0, $child->WriteCount);
2539
        $this->assertEquals(0, $grandchild->WriteCount);
2540
2541
        // Recursively only affect component that have been loaded
2542
        $grandchild->write(false, false, false, ['recursive' => false]);
2543
2544
        // Reload the DataObject from the DB to get the new Write Counts
2545
        $root = TreeNode::get()->byID($root->ID);
2546
        $child = TreeNode::get()->byID($child->ID);
2547
        $grandchild = TreeNode::get()->byID($grandchild->ID);
2548
2549
        $this->assertEquals(
2550
            1,
2551
            $grandchild->WriteCount,
2552
            'Grand child was written once because write was directly called on it'
2553
        );
2554
        $this->assertEquals(
2555
            1,
2556
            $child->WriteCount,
2557
            'Child was written once because it is directly related grand child'
2558
        );
2559
        $this->assertEquals(
2560
            0,
2561
            $root->WriteCount,
2562
            'Root is 2 step remove from grand children. It was not written on a shallow recursive write.'
2563
        );
2564
    }
2565
2566
    /**
2567
     * Test the different methods for creating DataObjects.
2568
     * Note that using anything other than the default option should generally be left to ORM interanls.
2569
     */
2570
    public function testDataObjectCreationTypes()
2571
    {
2572
2573
        // Test the default (DataObject::CREATE_OBJECT)
2574
        // Defaults are used, changes of non-default fields are tracked
2575
        $staff = new DataObjectTest\Staff([
2576
            'Salary' => 50,
2577
        ]);
2578
        $this->assertEquals('Staff', $staff->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...
2579
        $this->assertEquals(['Salary'], array_keys($staff->getChangedFields()));
2580
2581
2582
        // Test hydration (DataObject::CREATE_HYDRATED)
2583
        // Defaults are not used, changes are not tracked
2584
        $staff = new DataObjectTest\Staff([
2585
            'ID' => 5,
2586
            'Salary' => 50,
2587
        ], DataObject::CREATE_HYDRATED);
2588
        $this->assertEquals(null, $staff->EmploymentType);
2589
        $this->assertEquals(DataObjectTest\Staff::class, $staff->ClassName);
2590
        $this->assertEquals([], $staff->getChangedFields());
2591
2592
        // Test hydration (DataObject::CREATE_HYDRATED)
2593
        // Defaults are not used, changes are not tracked
2594
        $staff = new DataObjectTest\Staff([
2595
            'Salary' => 50,
2596
        ], DataObject::CREATE_MEMORY_HYDRATED);
2597
        $this->assertEquals(DataObjectTest\Staff::class, $staff->ClassName);
2598
        $this->assertEquals(null, $staff->EmploymentType);
2599
        $this->assertEquals([], $staff->getChangedFields());
2600
        $this->assertFalse(
2601
            $staff->isInDB(),
2602
            'DataObject hydrated from memory without an ID are assumed to not be in the Database.'
2603
        );
2604
2605
        // Test singleton (DataObject::CREATE_SINGLETON)
2606
        // Values are ingored
2607
        $staff = new DataObjectTest\Staff([
2608
            'Salary' => 50,
2609
        ], DataObject::CREATE_SINGLETON);
2610
        $this->assertEquals(null, $staff->EmploymentType);
2611
        $this->assertEquals(null, $staff->Salary);
0 ignored issues
show
Bug Best Practice introduced by
The property Salary does not exist on SilverStripe\ORM\Tests\DataObjectTest\Staff. Since you implemented __get, consider adding a @property annotation.
Loading history...
2612
        $this->assertEquals([], $staff->getChangedFields());
2613
    }
2614
2615
    public function testDataObjectCreationHydrateWithoutID()
2616
    {
2617
        $this->expectExceptionMessage(
2618
            "Hydrated records must be passed a record array including an ID."
2619
        );
2620
        // Hydrating a record without an ID should throw an exception
2621
        $staff = new DataObjectTest\Staff([
0 ignored issues
show
Unused Code introduced by
The assignment to $staff is dead and can be removed.
Loading history...
2622
            'Salary' => 50,
2623
        ], DataObject::CREATE_HYDRATED);
2624
    }
2625
2626
    public function testDBObjectEnum()
2627
    {
2628
        $obj = new DataObjectTest\Fixture();
2629
        // enums are parsed correctly
2630
        $vals = ['25', '50', '75', '100'];
2631
        $this->assertSame(array_combine($vals, $vals), $obj->dbObject('MyEnum')->enumValues());
0 ignored issues
show
Bug introduced by
The method enumValues() does not exist on SilverStripe\ORM\FieldType\DBField. 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

2631
        $this->assertSame(array_combine($vals, $vals), $obj->dbObject('MyEnum')->/** @scrutinizer ignore-call */ enumValues());
Loading history...
2632
        // enum with dots in their values are also parsed correctly
2633
        $vals = ['25.25', '50.00', '75.00', '100.50'];
2634
        $this->assertSame(array_combine($vals, $vals), $obj->dbObject('MyEnumWithDots')->enumValues());
2635
    }
2636
}
2637