Passed
Push — 4 ( 02c973...b5c3b6 )
by Steve
06:49 queued 12s
created

DataObjectTest::testIsChanged()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 53
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 42
nc 1
nop 0
dl 0
loc 53
rs 9.248
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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