Passed
Pull Request — 4 (#10047)
by Matt
06:31
created

testConstructHydratesAugmentedValues()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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

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

508
        $this->assertEquals($team1ID, $captain1->/** @scrutinizer ignore-call */ FavouriteTeam()->ID);
Loading history...
509
510
        // Test that getNonReciprocalComponent can find has_one from the has_many end
511
        $this->assertEquals(
512
            $team1ID,
513
            $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...
514
        );
515
516
        // Check entity with polymorphic has-one
517
        $fan1 = $this->objFromFixture(DataObjectTest\Fan::class, "fan1");
518
        $this->assertTrue((bool)$fan1->hasValue('Favourite'));
519
520
        // There will be fields named (relname)ID and (relname)Class for polymorphic
521
        // entities
522
        $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...
523
        $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...
524
525
        // There will be a method called $obj->relname() that returns the object itself
526
        $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

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

611
        $this->assertTrue($team1->/** @scrutinizer ignore-call */ Comments()->count() == 2);
Loading history...
612
613
        $team1Comments = [
614
            ['Comment' => 'This is a team comment by Joe'],
615
            ['Comment' => 'This is a team comment by Bob'],
616
        ];
617
618
        // Test the IDs on the DataObjects are set correctly
619
        $this->assertListEquals($team1Comments, $team1->Comments());
620
621
        // Test that has_many can be inferred from the has_one via getNonReciprocalComponent
622
        $this->assertListEquals(
623
            $team1Comments,
624
            $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

624
            /** @scrutinizer ignore-type */ $team1->inferReciprocalComponent(DataObjectTest\TeamComment::class, 'Team')
Loading history...
625
        );
626
627
        // Test that we can add and remove items that already exist in the database
628
        $newComment = new DataObjectTest\TeamComment();
629
        $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...
630
        $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...
631
        $newComment->write();
632
        $team1->Comments()->add($newComment);
633
        $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...
634
635
        $comment1 = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment1');
636
        $comment2 = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment2');
637
        $team1->Comments()->remove($comment2);
638
639
        $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

639
        $team1CommentIDs = $team1->Comments()->/** @scrutinizer ignore-call */ sort('ID')->column('ID');
Loading history...
640
        $this->assertEquals([$comment1->ID, $newComment->ID], $team1CommentIDs);
641
642
        // Test that removing an item from a list doesn't remove it from the same
643
        // relation belonging to a different object
644
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
645
        $team2 = $this->objFromFixture(DataObjectTest\Team::class, 'team2');
646
        $team2->Comments()->remove($comment1);
647
        $team1CommentIDs = $team1->Comments()->sort('ID')->column('ID');
648
        $this->assertEquals([$comment1->ID, $newComment->ID], $team1CommentIDs);
649
    }
650
651
652
    /**
653
     * Test has many relationships against polymorphic has_one fields
654
     *   - Test getComponents() gets the ComponentSet of the other side of the relation
655
     *   - Test the IDs on the DataObjects are set correctly
656
     */
657
    public function testHasManyPolymorphicRelationships()
658
    {
659
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
660
661
        // Test getComponents() gets the ComponentSet of the other side of the relation
662
        $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

662
        $this->assertTrue($team1->/** @scrutinizer ignore-call */ Fans()->count() == 2);
Loading history...
663
664
        // Test the IDs/Classes on the DataObjects are set correctly
665
        foreach ($team1->Fans() as $fan) {
666
            $this->assertEquals($team1->ID, $fan->FavouriteID, 'Fan has the correct FavouriteID');
667
            $this->assertEquals(DataObjectTest\Team::class, $fan->FavouriteClass, 'Fan has the correct FavouriteClass');
668
        }
669
670
        // Test that we can add and remove items that already exist in the database
671
        $newFan = new DataObjectTest\Fan();
672
        $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...
673
        $newFan->write();
674
        $team1->Fans()->add($newFan);
675
        $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...
676
        $this->assertEquals(
677
            DataObjectTest\Team::class,
678
            $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...
679
            'Newly created fan has the correct FavouriteClass'
680
        );
681
682
        $fan1 = $this->objFromFixture(DataObjectTest\Fan::class, 'fan1');
683
        $fan3 = $this->objFromFixture(DataObjectTest\Fan::class, 'fan3');
684
        $team1->Fans()->remove($fan3);
685
686
        $team1FanIDs = $team1->Fans()->sort('ID')->column('ID');
687
        $this->assertEquals([$fan1->ID, $newFan->ID], $team1FanIDs);
688
689
        // Test that removing an item from a list doesn't remove it from the same
690
        // relation belonging to a different object
691
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
692
        $player1 = $this->objFromFixture(DataObjectTest\Player::class, 'player1');
693
        $player1->Fans()->remove($fan1);
694
        $team1FanIDs = $team1->Fans()->sort('ID')->column('ID');
695
        $this->assertEquals([$fan1->ID, $newFan->ID], $team1FanIDs);
696
    }
697
698
699
    public function testHasOneRelationship()
700
    {
701
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
702
        $player1 = $this->objFromFixture(DataObjectTest\Player::class, 'player1');
703
        $player2 = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
704
        $fan1 = $this->objFromFixture(DataObjectTest\Fan::class, 'fan1');
705
706
        // Test relation probing
707
        $this->assertFalse((bool)$team1->hasValue('Captain', null, false));
708
        $this->assertFalse((bool)$team1->hasValue('CaptainID', null, false));
709
710
        // Add a captain to team 1
711
        $team1->setField('CaptainID', $player1->ID);
712
        $team1->write();
713
714
        $this->assertTrue((bool)$team1->hasValue('Captain', null, false));
715
        $this->assertTrue((bool)$team1->hasValue('CaptainID', null, false));
716
717
        $this->assertEquals(
718
            $player1->ID,
719
            $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

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

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

1726
        /** @scrutinizer ignore-call */ 
1727
        $equipmentSuppliers = $team->EquipmentSuppliers();
Loading history...
1727
1728
        // Check that DataObject::many_many() works as expected
1729
        $manyManyComponent = DataObject::getSchema()->manyManyComponent(DataObjectTest\Team::class, 'Sponsors');
1730
        $this->assertEquals(ManyManyList::class, $manyManyComponent['relationClass']);
1731
        $this->assertEquals(
1732
            DataObjectTest\Team::class,
1733
            $manyManyComponent['parentClass'],
1734
            'DataObject::many_many() didn\'t find the correct base class'
1735
        );
1736
        $this->assertEquals(
1737
            DataObjectTest\EquipmentCompany::class,
1738
            $manyManyComponent['childClass'],
1739
            'DataObject::many_many() didn\'t find the correct target class for the relation'
1740
        );
1741
        $this->assertEquals(
1742
            'DataObjectTest_EquipmentCompany_SponsoredTeams',
1743
            $manyManyComponent['join'],
1744
            'DataObject::many_many() didn\'t find the correct relation table'
1745
        );
1746
        $this->assertEquals('DataObjectTest_TeamID', $manyManyComponent['parentField']);
1747
        $this->assertEquals('DataObjectTest_EquipmentCompanyID', $manyManyComponent['childField']);
1748
1749
        // Check that ManyManyList still works
1750
        $this->assertEquals(2, $sponsors->count(), 'Rows are missing from relation');
1751
        $this->assertEquals(1, $equipmentSuppliers->count(), 'Rows are missing from relation');
1752
1753
        // Check everything works when no relation is present
1754
        $teamWithoutSponsor = $this->objFromFixture(DataObjectTest\Team::class, 'team3');
1755
        $this->assertInstanceOf(ManyManyList::class, $teamWithoutSponsor->Sponsors());
1756
        $this->assertEquals(0, $teamWithoutSponsor->Sponsors()->count());
1757
1758
        // Test that belongs_many_many can be inferred from with getNonReciprocalComponent
1759
        $this->assertListEquals(
1760
            [
1761
                ['Name' => 'Company corp'],
1762
                ['Name' => 'Team co.'],
1763
            ],
1764
            $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

1764
            /** @scrutinizer ignore-type */ $team->inferReciprocalComponent(DataObjectTest\EquipmentCompany::class, 'SponsoredTeams')
Loading history...
1765
        );
1766
1767
        // Test that many_many can be inferred from getNonReciprocalComponent
1768
        $this->assertListEquals(
1769
            [
1770
                ['Title' => 'Team 1'],
1771
                ['Title' => 'Team 2'],
1772
                ['Title' => 'Subteam 1'],
1773
            ],
1774
            $company2->inferReciprocalComponent(DataObjectTest\Team::class, 'Sponsors')
1775
        );
1776
1777
        // Check many_many_extraFields still works
1778
        $equipmentCompany = $this->objFromFixture(DataObjectTest\EquipmentCompany::class, 'equipmentcompany1');
1779
        $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

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

1887
        $player = $newTeam->/** @scrutinizer ignore-call */ Players()->first();
Loading history...
1888
        $this->assertEquals('Sam', $player->FirstName);
1889
        $this->assertEquals("Prop", $player->Position);
1890
1891
        // Check that ordering a many-many relation by an aggregate column doesn't fail
1892
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
1893
        $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

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

2201
        $this->assertEquals($company->ID, $ceo->/** @scrutinizer ignore-call */ Company()->ID, 'belongs_to returns the right results.');
Loading history...
2202
2203
        // Test belongs_to can be inferred via getNonReciprocalComponent
2204
        // Note: Will be returned as has_many since the belongs_to is ignored.
2205
        $this->assertListEquals(
2206
            [['Name' => 'New Company']],
2207
            $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

2207
            /** @scrutinizer ignore-type */ $ceo->inferReciprocalComponent(DataObjectTest\Company::class, 'CEO')
Loading history...
2208
        );
2209
2210
        // Test has_one to a belongs_to can be inferred via getNonReciprocalComponent
2211
        $this->assertEquals(
2212
            $ceo->ID,
2213
            $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...
2214
        );
2215
2216
        // Test automatic creation of class where no assignment exists
2217
        $ceo = new DataObjectTest\CEO();
2218
        $ceo->write();
2219
2220
        $this->assertTrue(
2221
            $ceo->Company() instanceof DataObjectTest\Company,
2222
            'DataObjects across belongs_to relations are automatically created.'
2223
        );
2224
        $this->assertEquals($ceo->ID, $ceo->Company()->CEOID, 'Remote IDs are automatically set.');
2225
2226
        // Write object with components
2227
        $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...
2228
        $ceo->write(false, false, false, true);
2229
        $this->assertFalse(
2230
            $ceo->Company()->isInDB(),
2231
            'write() does not write belongs_to components to the database that do not already exist.'
2232
        );
2233
2234
        $newCEO = DataObject::get_by_id(DataObjectTest\CEO::class, $ceo->ID);
2235
        $this->assertEquals(
2236
            $ceo->Company()->ID,
2237
            $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

2237
            $newCEO->/** @scrutinizer ignore-call */ 
2238
                     Company()->ID,
Loading history...
2238
            'belongs_to can be retrieved from the database.'
2239
        );
2240
    }
2241
2242
    public function testBelongsToPolymorphic()
2243
    {
2244
        $company = new DataObjectTest\Company();
2245
        $ceo = new DataObjectTest\CEO();
2246
2247
        $company->write();
2248
        $ceo->write();
2249
2250
        // Test belongs_to assignment
2251
        $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...
2252
        $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...
2253
        $company->write();
2254
2255
        $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

2255
        $this->assertEquals($company->ID, $ceo->/** @scrutinizer ignore-call */ CompanyOwned()->ID, 'belongs_to returns the right results.');
Loading history...
2256
        $this->assertInstanceOf(
2257
            DataObjectTest\Company::class,
2258
            $ceo->CompanyOwned(),
2259
            'belongs_to returns the right results.'
2260
        );
2261
2262
        // Test automatic creation of class where no assignment exists
2263
        $ceo = new DataObjectTest\CEO();
2264
        $ceo->write();
2265
2266
        $this->assertTrue(
2267
            $ceo->CompanyOwned() instanceof DataObjectTest\Company,
2268
            'DataObjects across polymorphic belongs_to relations are automatically created.'
2269
        );
2270
        $this->assertEquals($ceo->ID, $ceo->CompanyOwned()->OwnerID, 'Remote IDs are automatically set.');
2271
        $this->assertInstanceOf($ceo->CompanyOwned()->OwnerClass, $ceo, 'Remote class is automatically set.');
2272
2273
        // Skip writing components that do not exist
2274
        $ceo->write(false, false, false, true);
2275
        $this->assertFalse(
2276
            $ceo->CompanyOwned()->isInDB(),
2277
            'write() does not write belongs_to components to the database that do not already exist.'
2278
        );
2279
2280
        $newCEO = DataObject::get_by_id(DataObjectTest\CEO::class, $ceo->ID);
2281
        $this->assertEquals(
2282
            $ceo->CompanyOwned()->ID,
2283
            $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

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

2655
        $this->assertSame(array_combine($vals, $vals), $obj->dbObject('MyEnum')->/** @scrutinizer ignore-call */ enumValues());
Loading history...
2656
        // enum with dots in their values are also parsed correctly
2657
        $vals = ['25.25', '50.00', '75.00', '100.50'];
2658
        $this->assertSame(array_combine($vals, $vals), $obj->dbObject('MyEnumWithDots')->enumValues());
2659
    }
2660
}
2661