Passed
Push — master ( 32a37c...72e173 )
by Sam
09:54
created

DataObjectTest::testGetFieldMap()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 8
nc 1
nop 0
dl 0
loc 14
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\ORM\Tests;
4
5
use InvalidArgumentException;
6
use LogicException;
7
use Page;
0 ignored issues
show
Bug introduced by
The type Page was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use SilverStripe\CMS\Model\SiteTree;
0 ignored issues
show
Bug introduced by
The type SilverStripe\CMS\Model\SiteTree was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

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

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

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

692
            $team1->/** @scrutinizer ignore-call */ 
693
                    Captain()->ID,
Loading history...
693
            'The captain exists for team 1'
694
        );
695
        $this->assertEquals(
696
            $player1->ID,
697
            $team1->getComponent('Captain')->ID,
698
            'The captain exists through the component getter'
699
        );
700
701
        $this->assertEquals(
702
            $team1->Captain()->FirstName,
703
            'Player 1',
704
            'Player 1 is the captain'
705
        );
706
        $this->assertEquals(
707
            $team1->getComponent('Captain')->FirstName,
708
            'Player 1',
709
            'Player 1 is the captain'
710
        );
711
712
        $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...
713
        $team1->write();
714
715
        $this->assertEquals($player2->ID, $team1->Captain()->ID);
716
        $this->assertEquals($player2->ID, $team1->getComponent('Captain')->ID);
717
        $this->assertEquals('Player 2', $team1->Captain()->FirstName);
718
        $this->assertEquals('Player 2', $team1->getComponent('Captain')->FirstName);
719
720
721
        // Set the favourite team for fan1
722
        $fan1->setField('FavouriteID', $team1->ID);
723
        $fan1->setField('FavouriteClass', get_class($team1));
724
725
        $this->assertEquals($team1->ID, $fan1->Favourite()->ID, 'The team is assigned to fan 1');
726
        $this->assertInstanceOf(get_class($team1), $fan1->Favourite(), 'The team is assigned to fan 1');
727
        $this->assertEquals(
728
            $team1->ID,
729
            $fan1->getComponent('Favourite')->ID,
730
            'The team exists through the component getter'
731
        );
732
        $this->assertInstanceOf(
733
            get_class($team1),
734
            $fan1->getComponent('Favourite'),
735
            'The team exists through the component getter'
736
        );
737
738
        $this->assertEquals(
739
            $fan1->Favourite()->Title,
740
            'Team 1',
741
            'Team 1 is the favourite'
742
        );
743
        $this->assertEquals(
744
            $fan1->getComponent('Favourite')->Title,
745
            'Team 1',
746
            'Team 1 is the favourite'
747
        );
748
    }
749
750
    /**
751
     * Test has_one used as field getter/setter
752
     */
753
    public function testHasOneAsField()
754
    {
755
        /** @var DataObjectTest\Team $team1 */
756
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
757
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
758
        $captain2 = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
759
760
        // Setter: By RelationID
761
        $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...
762
        $team1->write();
763
        $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...
764
765
        // Setter: New object
766
        $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...
767
        $team1->write();
768
        $this->assertEquals($captain2->ID, $team1->Captain->ID);
769
770
        // Setter: Custom data (required by DataDifferencer)
771
        $team1->Captain = DBField::create_field('HTMLFragment', '<p>No captain</p>');
772
        $this->assertEquals('<p>No captain</p>', $team1->Captain);
773
    }
774
775
    /**
776
     * @todo Extend type change tests (e.g. '0'==NULL)
777
     */
778
    public function testChangedFields()
779
    {
780
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
781
        $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...
782
        $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...
783
784
        $this->assertEquals(
785
            $obj->getChangedFields(true, DataObject::CHANGE_STRICT),
786
            array(
787
                'FirstName' => array(
788
                    'before' => 'Captain',
789
                    'after' => 'Captain-changed',
790
                    'level' => DataObject::CHANGE_VALUE
791
                ),
792
                'IsRetired' => array(
793
                    'before' => 1,
794
                    'after' => true,
795
                    'level' => DataObject::CHANGE_STRICT
796
                )
797
            ),
798
            'Changed fields are correctly detected with strict type changes (level=1)'
799
        );
800
801
        $this->assertEquals(
802
            $obj->getChangedFields(true, DataObject::CHANGE_VALUE),
803
            array(
804
                'FirstName' => array(
805
                    'before' => 'Captain',
806
                    'after' => 'Captain-changed',
807
                    'level' => DataObject::CHANGE_VALUE
808
                )
809
            ),
810
            'Changed fields are correctly detected while ignoring type changes (level=2)'
811
        );
812
813
        $newObj = new DataObjectTest\Player();
814
        $newObj->FirstName = "New Player";
815
        $this->assertEquals(
816
            array(
817
                'FirstName' => array(
818
                    'before' => null,
819
                    'after' => 'New Player',
820
                    'level' => DataObject::CHANGE_VALUE
821
                )
822
            ),
823
            $newObj->getChangedFields(true, DataObject::CHANGE_VALUE),
824
            'Initialised fields are correctly detected as full changes'
825
        );
826
    }
827
828
    public function testChangedFieldsWhenRestoringData()
829
    {
830
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
831
        $obj->FirstName = 'Captain-changed';
0 ignored issues
show
Bug Best Practice introduced by
The property FirstName does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
832
        $obj->FirstName = 'Captain';
833
834
        $this->assertEquals(
835
            [],
836
            $obj->getChangedFields(true, DataObject::CHANGE_STRICT)
837
        );
838
    }
839
840
    public function testChangedFieldsAfterWrite()
841
    {
842
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
843
        $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...
844
        $obj->write();
845
        $obj->FirstName = 'Captain';
846
847
        $this->assertEquals(
848
            array(
849
                'FirstName' => array(
850
                    'before' => 'Captain-changed',
851
                    'after' => 'Captain',
852
                    'level' => DataObject::CHANGE_VALUE,
853
                ),
854
            ),
855
            $obj->getChangedFields(true, DataObject::CHANGE_VALUE)
856
        );
857
    }
858
859
    public function testForceChangeCantBeCancelledUntilWrite()
860
    {
861
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
862
        $this->assertFalse($obj->isChanged('FirstName'));
863
        $this->assertFalse($obj->isChanged('Surname'));
864
865
        // Force change marks the records as changed
866
        $obj->forceChange();
867
        $this->assertTrue($obj->isChanged('FirstName'));
868
        $this->assertTrue($obj->isChanged('Surname'));
869
870
        // ...but not if we explicitly ask if the value has changed
871
        $this->assertFalse($obj->isChanged('FirstName', DataObject::CHANGE_VALUE));
872
        $this->assertFalse($obj->isChanged('Surname', DataObject::CHANGE_VALUE));
873
874
        // Not overwritten by setting the value to is original value
875
        $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...
876
        $this->assertTrue($obj->isChanged('FirstName'));
877
        $this->assertTrue($obj->isChanged('Surname'));
878
879
        // Not overwritten by changing it to something else and back again
880
        $obj->FirstName = 'Captain-changed';
881
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_VALUE));
882
883
        $obj->FirstName = 'Captain';
884
        $this->assertFalse($obj->isChanged('FirstName', DataObject::CHANGE_VALUE));
885
        $this->assertTrue($obj->isChanged('FirstName'));
886
        $this->assertTrue($obj->isChanged('Surname'));
887
888
        // Cleared after write
889
        $obj->write();
890
        $this->assertFalse($obj->isChanged('FirstName'));
891
        $this->assertFalse($obj->isChanged('Surname'));
892
893
        $obj->FirstName = 'Captain';
894
        $this->assertFalse($obj->isChanged('FirstName'));
895
    }
896
897
    /**
898
     * @skipUpgrade
899
     */
900
    public function testIsChanged()
901
    {
902
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
903
        $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...
904
        $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...
905
        $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...
906
907
        // Now that DB fields are changed, isChanged is true
908
        $this->assertTrue($obj->isChanged('NonDBField'));
909
        $this->assertFalse($obj->isChanged('NonField'));
910
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_STRICT));
911
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_VALUE));
912
        $this->assertTrue($obj->isChanged('IsRetired', DataObject::CHANGE_STRICT));
913
        $this->assertFalse($obj->isChanged('IsRetired', DataObject::CHANGE_VALUE));
914
        $this->assertFalse($obj->isChanged('Email', 1), 'Doesnt change mark unchanged property');
915
        $this->assertFalse($obj->isChanged('Email', 2), 'Doesnt change mark unchanged property');
916
917
        $newObj = new DataObjectTest\Player();
918
        $newObj->FirstName = "New Player";
919
        $this->assertTrue($newObj->isChanged('FirstName', DataObject::CHANGE_STRICT));
920
        $this->assertTrue($newObj->isChanged('FirstName', DataObject::CHANGE_VALUE));
921
        $this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_STRICT));
922
        $this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_VALUE));
923
924
        $newObj->write();
925
        $this->assertFalse($newObj->ischanged());
926
        $this->assertFalse($newObj->isChanged('FirstName', DataObject::CHANGE_STRICT));
927
        $this->assertFalse($newObj->isChanged('FirstName', DataObject::CHANGE_VALUE));
928
        $this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_STRICT));
929
        $this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_VALUE));
930
931
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
932
        $obj->FirstName = null;
933
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_STRICT));
934
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_VALUE));
935
936
        $obj->write();
937
        $obj->FirstName = null;
938
        $this->assertFalse(
939
            $obj->isChanged('FirstName', DataObject::CHANGE_STRICT),
940
            'Unchanged property was marked as changed'
941
        );
942
        $obj->FirstName = 0;
943
        $this->assertTrue(
944
            $obj->isChanged('FirstName', DataObject::CHANGE_STRICT),
945
            'Strict (type) change was not detected'
946
        );
947
        $this->assertFalse(
948
            $obj->isChanged('FirstName', DataObject::CHANGE_VALUE),
949
            'Type-only change was marked as a value change'
950
        );
951
952
        /* Test when there's not field provided */
953
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
954
        $this->assertFalse($obj->isChanged());
955
        $obj->NonDBField = 'new value';
956
        $this->assertFalse($obj->isChanged());
957
        $obj->FirstName = "New Player";
958
        $this->assertTrue($obj->isChanged());
959
960
        $obj->write();
961
        $this->assertFalse($obj->isChanged());
962
    }
963
964
    public function testRandomSort()
965
    {
966
        /* If we perform the same regularly sorted query twice, it should return the same results */
967
        $itemsA = DataObject::get(DataObjectTest\TeamComment::class, "", "ID");
968
        foreach ($itemsA as $item) {
969
            $keysA[] = $item->ID;
970
        }
971
972
        $itemsB = DataObject::get(DataObjectTest\TeamComment::class, "", "ID");
973
        foreach ($itemsB as $item) {
974
            $keysB[] = $item->ID;
975
        }
976
977
        /* Test when there's not field provided */
978
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
979
        $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...
980
        $this->assertTrue($obj->isChanged());
981
982
        $obj->write();
983
        $this->assertFalse($obj->isChanged());
984
985
        /* If we perform the same random query twice, it shouldn't return the same results */
986
        $itemsA = DataObject::get(DataObjectTest\TeamComment::class, "", DB::get_conn()->random());
987
        $itemsB = DataObject::get(DataObjectTest\TeamComment::class, "", DB::get_conn()->random());
988
        $itemsC = DataObject::get(DataObjectTest\TeamComment::class, "", DB::get_conn()->random());
989
        $itemsD = DataObject::get(DataObjectTest\TeamComment::class, "", DB::get_conn()->random());
990
        foreach ($itemsA as $item) {
991
            $keysA[] = $item->ID;
992
        }
993
        foreach ($itemsB as $item) {
994
            $keysB[] = $item->ID;
995
        }
996
        foreach ($itemsC as $item) {
997
            $keysC[] = $item->ID;
998
        }
999
        foreach ($itemsD as $item) {
1000
            $keysD[] = $item->ID;
1001
        }
1002
1003
        // These shouldn't all be the same (run it 4 times to minimise chance of an accidental collision)
1004
        // There's about a 1 in a billion chance of an accidental collision
1005
        $this->assertTrue($keysA != $keysB || $keysB != $keysC || $keysC != $keysD);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $keysB seems to be defined by a foreach iteration on line 973. 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 999. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
Comprehensibility Best Practice introduced by
The variable $keysA seems to be defined by a foreach iteration on line 968. 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 996. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
1006
    }
1007
1008
    public function testWriteSavesToHasOneRelations()
1009
    {
1010
        /* DataObject::write() should save to a has_one relationship if you set a field called (relname)ID */
1011
        $team = new DataObjectTest\Team();
1012
        $captainID = $this->idFromFixture(DataObjectTest\Player::class, 'player1');
1013
        $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...
1014
        $team->write();
1015
        $this->assertEquals(
1016
            $captainID,
1017
            DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $team->ID")->value()
1018
        );
1019
1020
        /* After giving it a value, you should also be able to set it back to null */
1021
        $team->CaptainID = '';
1022
        $team->write();
1023
        $this->assertEquals(
1024
            0,
1025
            DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $team->ID")->value()
1026
        );
1027
1028
        /* You should also be able to save a blank to it when it's first created */
1029
        $team = new DataObjectTest\Team();
1030
        $team->CaptainID = '';
1031
        $team->write();
1032
        $this->assertEquals(
1033
            0,
1034
            DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $team->ID")->value()
1035
        );
1036
1037
        /* Ditto for existing records without a value */
1038
        $existingTeam = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1039
        $existingTeam->CaptainID = '';
0 ignored issues
show
Bug Best Practice introduced by
The property CaptainID does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
1040
        $existingTeam->write();
1041
        $this->assertEquals(
1042
            0,
1043
            DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $existingTeam->ID")->value()
1044
        );
1045
    }
1046
1047
    public function testCanAccessHasOneObjectsAsMethods()
1048
    {
1049
        /* If you have a has_one relation 'Captain' on $obj, and you set the $obj->CaptainID = (ID), then the
1050
        * object itself should be accessible as $obj->Captain() */
1051
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1052
        $captainID = $this->idFromFixture(DataObjectTest\Player::class, 'captain1');
1053
1054
        $team->CaptainID = $captainID;
0 ignored issues
show
Bug Best Practice introduced by
The property CaptainID does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
1055
        $this->assertNotNull($team->Captain());
1056
        $this->assertEquals($captainID, $team->Captain()->ID);
1057
1058
        // Test for polymorphic has_one relations
1059
        $fan = $this->objFromFixture(DataObjectTest\Fan::class, 'fan1');
1060
        $fan->FavouriteID = $team->ID;
0 ignored issues
show
Bug Best Practice introduced by
The property FavouriteID does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
1061
        $fan->FavouriteClass = DataObjectTest\Team::class;
0 ignored issues
show
Bug Best Practice introduced by
The property FavouriteClass does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
1062
        $this->assertNotNull($fan->Favourite());
1063
        $this->assertEquals($team->ID, $fan->Favourite()->ID);
1064
        $this->assertInstanceOf(DataObjectTest\Team::class, $fan->Favourite());
1065
    }
1066
1067
    public function testFieldNamesThatMatchMethodNamesWork()
1068
    {
1069
        /* Check that a field name that corresponds to a method on DataObject will still work */
1070
        $obj = new DataObjectTest\Fixture();
1071
        $obj->Data = "value1";
0 ignored issues
show
Bug Best Practice introduced by
The property Data does not exist on SilverStripe\ORM\Tests\DataObjectTest\Fixture. Since you implemented __set, consider adding a @property annotation.
Loading history...
1072
        $obj->DbObject = "value2";
0 ignored issues
show
Bug Best Practice introduced by
The property DbObject does not exist on SilverStripe\ORM\Tests\DataObjectTest\Fixture. Since you implemented __set, consider adding a @property annotation.
Loading history...
1073
        $obj->Duplicate = "value3";
0 ignored issues
show
Bug Best Practice introduced by
The property Duplicate does not exist on SilverStripe\ORM\Tests\DataObjectTest\Fixture. Since you implemented __set, consider adding a @property annotation.
Loading history...
1074
        $obj->write();
1075
1076
        $this->assertNotNull($obj->ID);
1077
        $this->assertEquals(
1078
            'value1',
1079
            DB::query("SELECT \"Data\" FROM \"DataObjectTest_Fixture\" WHERE \"ID\" = $obj->ID")->value()
1080
        );
1081
        $this->assertEquals(
1082
            'value2',
1083
            DB::query("SELECT \"DbObject\" FROM \"DataObjectTest_Fixture\" WHERE \"ID\" = $obj->ID")->value()
1084
        );
1085
        $this->assertEquals(
1086
            'value3',
1087
            DB::query("SELECT \"Duplicate\" FROM \"DataObjectTest_Fixture\" WHERE \"ID\" = $obj->ID")->value()
1088
        );
1089
    }
1090
1091
    /**
1092
     * @todo Re-enable all test cases for field existence after behaviour has been fixed
1093
     */
1094
    public function testFieldExistence()
1095
    {
1096
        $teamInstance = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1097
        $teamSingleton = singleton(DataObjectTest\Team::class);
1098
1099
        $subteamInstance = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
1100
        $schema = DataObject::getSchema();
1101
1102
        /* hasField() singleton checks */
1103
        $this->assertTrue(
1104
            $teamSingleton->hasField('ID'),
1105
            'hasField() finds built-in fields in singletons'
1106
        );
1107
        $this->assertTrue(
1108
            $teamSingleton->hasField('Title'),
1109
            'hasField() finds custom fields in singletons'
1110
        );
1111
1112
        /* hasField() instance checks */
1113
        $this->assertFalse(
1114
            $teamInstance->hasField('NonExistingField'),
1115
            'hasField() doesnt find non-existing fields in instances'
1116
        );
1117
        $this->assertTrue(
1118
            $teamInstance->hasField('ID'),
1119
            'hasField() finds built-in fields in instances'
1120
        );
1121
        $this->assertTrue(
1122
            $teamInstance->hasField('Created'),
1123
            'hasField() finds built-in fields in instances'
1124
        );
1125
        $this->assertTrue(
1126
            $teamInstance->hasField('DatabaseField'),
1127
            'hasField() finds custom fields in instances'
1128
        );
1129
        //$this->assertFalse($teamInstance->hasField('SubclassDatabaseField'),
0 ignored issues
show
Unused Code Comprehensibility introduced by
82% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1130
        //'hasField() doesnt find subclass fields in parentclass instances');
1131
        $this->assertTrue(
1132
            $teamInstance->hasField('DynamicField'),
1133
            'hasField() finds dynamic getters in instances'
1134
        );
1135
        $this->assertTrue(
1136
            $teamInstance->hasField('HasOneRelationshipID'),
1137
            'hasField() finds foreign keys in instances'
1138
        );
1139
        $this->assertTrue(
1140
            $teamInstance->hasField('ExtendedDatabaseField'),
1141
            'hasField() finds extended fields in instances'
1142
        );
1143
        $this->assertTrue(
1144
            $teamInstance->hasField('ExtendedHasOneRelationshipID'),
1145
            'hasField() finds extended foreign keys in instances'
1146
        );
1147
        //$this->assertTrue($teamInstance->hasField('ExtendedDynamicField'),
0 ignored issues
show
Unused Code Comprehensibility introduced by
82% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1148
        //'hasField() includes extended dynamic getters in instances');
1149
1150
        /* hasField() subclass checks */
1151
        $this->assertTrue(
1152
            $subteamInstance->hasField('ID'),
1153
            'hasField() finds built-in fields in subclass instances'
1154
        );
1155
        $this->assertTrue(
1156
            $subteamInstance->hasField('Created'),
1157
            'hasField() finds built-in fields in subclass instances'
1158
        );
1159
        $this->assertTrue(
1160
            $subteamInstance->hasField('DatabaseField'),
1161
            'hasField() finds custom fields in subclass instances'
1162
        );
1163
        $this->assertTrue(
1164
            $subteamInstance->hasField('SubclassDatabaseField'),
1165
            'hasField() finds custom fields in subclass instances'
1166
        );
1167
        $this->assertTrue(
1168
            $subteamInstance->hasField('DynamicField'),
1169
            'hasField() finds dynamic getters in subclass instances'
1170
        );
1171
        $this->assertTrue(
1172
            $subteamInstance->hasField('HasOneRelationshipID'),
1173
            'hasField() finds foreign keys in subclass instances'
1174
        );
1175
        $this->assertTrue(
1176
            $subteamInstance->hasField('ExtendedDatabaseField'),
1177
            'hasField() finds extended fields in subclass instances'
1178
        );
1179
        $this->assertTrue(
1180
            $subteamInstance->hasField('ExtendedHasOneRelationshipID'),
1181
            'hasField() finds extended foreign keys in subclass instances'
1182
        );
1183
1184
        /* hasDatabaseField() singleton checks */
1185
        //$this->assertTrue($teamSingleton->hasDatabaseField('ID'),
0 ignored issues
show
Unused Code Comprehensibility introduced by
82% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1186
        //'hasDatabaseField() finds built-in fields in singletons');
1187
        $this->assertNotEmpty(
1188
            $schema->fieldSpec(DataObjectTest\Team::class, 'Title'),
1189
            'hasDatabaseField() finds custom fields in singletons'
1190
        );
1191
1192
        /* hasDatabaseField() instance checks */
1193
        $this->assertNull(
1194
            $schema->fieldSpec(DataObjectTest\Team::class, 'NonExistingField'),
1195
            'hasDatabaseField() doesnt find non-existing fields in instances'
1196
        );
1197
        //$this->assertNotEmpty($schema->fieldSpec(DataObjectTest_Team::class, 'ID'),
0 ignored issues
show
Unused Code Comprehensibility introduced by
69% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1198
        //'hasDatabaseField() finds built-in fields in instances');
1199
        $this->assertNotEmpty(
1200
            $schema->fieldSpec(DataObjectTest\Team::class, 'Created'),
1201
            'hasDatabaseField() finds built-in fields in instances'
1202
        );
1203
        $this->assertNotEmpty(
1204
            $schema->fieldSpec(DataObjectTest\Team::class, 'DatabaseField'),
1205
            'hasDatabaseField() finds custom fields in instances'
1206
        );
1207
        $this->assertNull(
1208
            $schema->fieldSpec(DataObjectTest\Team::class, 'SubclassDatabaseField'),
1209
            'hasDatabaseField() doesnt find subclass fields in parentclass instances'
1210
        );
1211
        //$this->assertNull($schema->fieldSpec(DataObjectTest_Team::class, 'DynamicField'),
0 ignored issues
show
Unused Code Comprehensibility introduced by
69% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1212
        //'hasDatabaseField() doesnt dynamic getters in instances');
1213
        $this->assertNotEmpty(
1214
            $schema->fieldSpec(DataObjectTest\Team::class, 'HasOneRelationshipID'),
1215
            'hasDatabaseField() finds foreign keys in instances'
1216
        );
1217
        $this->assertNotEmpty(
1218
            $schema->fieldSpec(DataObjectTest\Team::class, 'ExtendedDatabaseField'),
1219
            'hasDatabaseField() finds extended fields in instances'
1220
        );
1221
        $this->assertNotEmpty(
1222
            $schema->fieldSpec(DataObjectTest\Team::class, 'ExtendedHasOneRelationshipID'),
1223
            'hasDatabaseField() finds extended foreign keys in instances'
1224
        );
1225
        $this->assertNull(
1226
            $schema->fieldSpec(DataObjectTest\Team::class, 'ExtendedDynamicField'),
1227
            'hasDatabaseField() doesnt include extended dynamic getters in instances'
1228
        );
1229
1230
        /* hasDatabaseField() subclass checks */
1231
        $this->assertNotEmpty(
1232
            $schema->fieldSpec(DataObjectTest\SubTeam::class, 'DatabaseField'),
1233
            'hasField() finds custom fields in subclass instances'
1234
        );
1235
        $this->assertNotEmpty(
1236
            $schema->fieldSpec(DataObjectTest\SubTeam::class, 'SubclassDatabaseField'),
1237
            'hasField() finds custom fields in subclass instances'
1238
        );
1239
    }
1240
1241
    /**
1242
     * @todo Re-enable all test cases for field inheritance aggregation after behaviour has been fixed
1243
     */
1244
    public function testFieldInheritance()
1245
    {
1246
        $schema = DataObject::getSchema();
1247
1248
        // Test logical fields (including composite)
1249
        $teamSpecifications = $schema->fieldSpecs(DataObjectTest\Team::class);
1250
        $expected = array(
1251
            'ID',
1252
            'ClassName',
1253
            'LastEdited',
1254
            'Created',
1255
            'Title',
1256
            'DatabaseField',
1257
            'ExtendedDatabaseField',
1258
            'CaptainID',
1259
            'FounderID',
1260
            'HasOneRelationshipID',
1261
            'ExtendedHasOneRelationshipID'
1262
        );
1263
        $actual = array_keys($teamSpecifications);
1264
        sort($expected);
1265
        sort($actual);
1266
        $this->assertEquals(
1267
            $expected,
1268
            $actual,
1269
            'fieldSpecifications() contains all fields defined on instance: base, extended and foreign keys'
1270
        );
1271
1272
        $teamFields = $schema->databaseFields(DataObjectTest\Team::class, false);
1273
        $expected = array(
1274
            'ID',
1275
            'ClassName',
1276
            'LastEdited',
1277
            'Created',
1278
            'Title',
1279
            'DatabaseField',
1280
            'ExtendedDatabaseField',
1281
            'CaptainID',
1282
            'FounderID',
1283
            'HasOneRelationshipID',
1284
            'ExtendedHasOneRelationshipID'
1285
        );
1286
        $actual = array_keys($teamFields);
1287
        sort($expected);
1288
        sort($actual);
1289
        $this->assertEquals(
1290
            $expected,
1291
            $actual,
1292
            'databaseFields() contains only fields defined on instance, including base, extended and foreign keys'
1293
        );
1294
1295
        $subteamSpecifications = $schema->fieldSpecs(DataObjectTest\SubTeam::class);
1296
        $expected = array(
1297
            'ID',
1298
            'ClassName',
1299
            'LastEdited',
1300
            'Created',
1301
            'Title',
1302
            'DatabaseField',
1303
            'ExtendedDatabaseField',
1304
            'CaptainID',
1305
            'FounderID',
1306
            'HasOneRelationshipID',
1307
            'ExtendedHasOneRelationshipID',
1308
            'SubclassDatabaseField',
1309
            'ParentTeamID',
1310
        );
1311
        $actual = array_keys($subteamSpecifications);
1312
        sort($expected);
1313
        sort($actual);
1314
        $this->assertEquals(
1315
            $expected,
1316
            $actual,
1317
            'fieldSpecifications() on subclass contains all fields, including base, extended  and foreign keys'
1318
        );
1319
1320
        $subteamFields = $schema->databaseFields(DataObjectTest\SubTeam::class, false);
1321
        $expected = array(
1322
            'ID',
1323
            'SubclassDatabaseField',
1324
            'ParentTeamID',
1325
        );
1326
        $actual = array_keys($subteamFields);
1327
        sort($expected);
1328
        sort($actual);
1329
        $this->assertEquals(
1330
            $expected,
1331
            $actual,
1332
            'databaseFields() on subclass contains only fields defined on instance'
1333
        );
1334
    }
1335
1336
    public function testSearchableFields()
1337
    {
1338
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
1339
        $fields = $player->searchableFields();
1340
        $this->assertArrayHasKey(
1341
            'IsRetired',
1342
            $fields,
1343
            'Fields defined by $searchable_fields static are correctly detected'
1344
        );
1345
        $this->assertArrayHasKey(
1346
            'ShirtNumber',
1347
            $fields,
1348
            'Fields defined by $searchable_fields static are correctly detected'
1349
        );
1350
1351
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1352
        $fields = $team->searchableFields();
1353
        $this->assertArrayHasKey(
1354
            'Title',
1355
            $fields,
1356
            'Fields can be inherited from the $summary_fields static, including methods called on fields'
1357
        );
1358
        $this->assertArrayHasKey(
1359
            'Captain.ShirtNumber',
1360
            $fields,
1361
            'Fields on related objects can be inherited from the $summary_fields static'
1362
        );
1363
        $this->assertArrayHasKey(
1364
            'Captain.FavouriteTeam.Title',
1365
            $fields,
1366
            'Fields on related objects can be inherited from the $summary_fields static'
1367
        );
1368
1369
        $testObj = new DataObjectTest\Fixture();
1370
        $fields = $testObj->searchableFields();
1371
        $this->assertEmpty($fields);
1372
    }
1373
1374
    public function testCastingHelper()
1375
    {
1376
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1377
1378
        $this->assertEquals('Varchar', $team->castingHelper('Title'), 'db field wasn\'t casted correctly');
1379
        $this->assertEquals('HTMLVarchar', $team->castingHelper('DatabaseField'), 'db field wasn\'t casted correctly');
1380
1381
        $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

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

1675
        /** @scrutinizer ignore-call */ 
1676
        $equipmentSuppliers = $team->EquipmentSuppliers();
Loading history...
1676
1677
        // Check that DataObject::many_many() works as expected
1678
        $manyManyComponent = DataObject::getSchema()->manyManyComponent(DataObjectTest\Team::class, 'Sponsors');
1679
        $this->assertEquals(ManyManyList::class, $manyManyComponent['relationClass']);
1680
        $this->assertEquals(
1681
            DataObjectTest\Team::class,
1682
            $manyManyComponent['parentClass'],
1683
            'DataObject::many_many() didn\'t find the correct base class'
1684
        );
1685
        $this->assertEquals(
1686
            DataObjectTest\EquipmentCompany::class,
1687
            $manyManyComponent['childClass'],
1688
            'DataObject::many_many() didn\'t find the correct target class for the relation'
1689
        );
1690
        $this->assertEquals(
1691
            'DataObjectTest_EquipmentCompany_SponsoredTeams',
1692
            $manyManyComponent['join'],
1693
            'DataObject::many_many() didn\'t find the correct relation table'
1694
        );
1695
        $this->assertEquals('DataObjectTest_TeamID', $manyManyComponent['parentField']);
1696
        $this->assertEquals('DataObjectTest_EquipmentCompanyID', $manyManyComponent['childField']);
1697
1698
        // Check that ManyManyList still works
1699
        $this->assertEquals(2, $sponsors->count(), 'Rows are missing from relation');
1700
        $this->assertEquals(1, $equipmentSuppliers->count(), 'Rows are missing from relation');
1701
1702
        // Check everything works when no relation is present
1703
        $teamWithoutSponsor = $this->objFromFixture(DataObjectTest\Team::class, 'team3');
1704
        $this->assertInstanceOf(ManyManyList::class, $teamWithoutSponsor->Sponsors());
1705
        $this->assertEquals(0, $teamWithoutSponsor->Sponsors()->count());
1706
1707
        // Test that belongs_many_many can be infered from with getNonReciprocalComponent
1708
        $this->assertListEquals(
1709
            [
1710
                ['Name' => 'Company corp'],
1711
                ['Name' => 'Team co.'],
1712
            ],
1713
            $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

1713
            /** @scrutinizer ignore-type */ $team->inferReciprocalComponent(DataObjectTest\EquipmentCompany::class, 'SponsoredTeams')
Loading history...
1714
        );
1715
1716
        // Test that many_many can be infered from getNonReciprocalComponent
1717
        $this->assertListEquals(
1718
            [
1719
                ['Title' => 'Team 1'],
1720
                ['Title' => 'Team 2'],
1721
                ['Title' => 'Subteam 1'],
1722
            ],
1723
            $company2->inferReciprocalComponent(DataObjectTest\Team::class, 'Sponsors')
1724
        );
1725
1726
        // Check many_many_extraFields still works
1727
        $equipmentCompany = $this->objFromFixture(DataObjectTest\EquipmentCompany::class, 'equipmentcompany1');
1728
        $equipmentCompany->SponsoredTeams()->add($teamWithoutSponsor, array('SponsorFee' => 1000));
0 ignored issues
show
Bug introduced by
The method SponsoredTeams() does not exist on SilverStripe\ORM\DataObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

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

1836
        $player = $newTeam->/** @scrutinizer ignore-call */ Players()->first();
Loading history...
1837
        $this->assertEquals('Sam', $player->FirstName);
1838
        $this->assertEquals("Prop", $player->Position);
1839
1840
        // Check that ordering a many-many relation by an aggregate column doesn't fail
1841
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
1842
        $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

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

2150
        $this->assertEquals($company->ID, $ceo->/** @scrutinizer ignore-call */ Company()->ID, 'belongs_to returns the right results.');
Loading history...
2151
2152
        // Test belongs_to can be infered via getNonReciprocalComponent
2153
        // Note: Will be returned as has_many since the belongs_to is ignored.
2154
        $this->assertListEquals(
2155
            [['Name' => 'New Company']],
2156
            $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

2156
            /** @scrutinizer ignore-type */ $ceo->inferReciprocalComponent(DataObjectTest\Company::class, 'CEO')
Loading history...
2157
        );
2158
2159
        // Test has_one to a belongs_to can be infered via getNonReciprocalComponent
2160
        $this->assertEquals(
2161
            $ceo->ID,
2162
            $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...
2163
        );
2164
2165
        // Test automatic creation of class where no assigment exists
2166
        $ceo = new DataObjectTest\CEO();
2167
        $ceo->write();
2168
2169
        $this->assertTrue(
2170
            $ceo->Company() instanceof DataObjectTest\Company,
2171
            'DataObjects across belongs_to relations are automatically created.'
2172
        );
2173
        $this->assertEquals($ceo->ID, $ceo->Company()->CEOID, 'Remote IDs are automatically set.');
2174
2175
        // Write object with components
2176
        $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...
2177
        $ceo->write(false, false, false, true);
2178
        $this->assertTrue($ceo->Company()->isInDB(), 'write() writes belongs_to components to the database.');
2179
2180
        $newCEO = DataObject::get_by_id(DataObjectTest\CEO::class, $ceo->ID);
2181
        $this->assertEquals(
2182
            $ceo->Company()->ID,
2183
            $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

2183
            $newCEO->/** @scrutinizer ignore-call */ 
2184
                     Company()->ID,
Loading history...
2184
            'belongs_to can be retrieved from the database.'
2185
        );
2186
    }
2187
2188
    public function testBelongsToPolymorphic()
2189
    {
2190
        $company = new DataObjectTest\Company();
2191
        $ceo = new DataObjectTest\CEO();
2192
2193
        $company->write();
2194
        $ceo->write();
2195
2196
        // Test belongs_to assignment
2197
        $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...
2198
        $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...
2199
        $company->write();
2200
2201
        $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

2201
        $this->assertEquals($company->ID, $ceo->/** @scrutinizer ignore-call */ CompanyOwned()->ID, 'belongs_to returns the right results.');
Loading history...
2202
        $this->assertInstanceOf(
2203
            DataObjectTest\Company::class,
2204
            $ceo->CompanyOwned(),
2205
            'belongs_to returns the right results.'
2206
        );
2207
2208
        // Test automatic creation of class where no assigment exists
2209
        $ceo = new DataObjectTest\CEO();
2210
        $ceo->write();
2211
2212
        $this->assertTrue(
2213
            $ceo->CompanyOwned() instanceof DataObjectTest\Company,
2214
            'DataObjects across polymorphic belongs_to relations are automatically created.'
2215
        );
2216
        $this->assertEquals($ceo->ID, $ceo->CompanyOwned()->OwnerID, 'Remote IDs are automatically set.');
2217
        $this->assertInstanceOf($ceo->CompanyOwned()->OwnerClass, $ceo, 'Remote class is automatically  set');
2218
2219
        // Write object with components
2220
        $ceo->write(false, false, false, true);
2221
        $this->assertTrue($ceo->CompanyOwned()->isInDB(), 'write() writes belongs_to components to the database.');
2222
2223
        $newCEO = DataObject::get_by_id(DataObjectTest\CEO::class, $ceo->ID);
2224
        $this->assertEquals(
2225
            $ceo->CompanyOwned()->ID,
2226
            $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

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