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

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

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

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

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

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

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

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

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

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

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

1712
            /** @scrutinizer ignore-type */ $team->inferReciprocalComponent(DataObjectTest\EquipmentCompany::class, 'SponsoredTeams')
Loading history...
1713
        );
1714
1715
        // Test that many_many can be infered from getNonReciprocalComponent
1716
        $this->assertListEquals(
1717
            [
1718
                ['Title' => 'Team 1'],
1719
                ['Title' => 'Team 2'],
1720
                ['Title' => 'Subteam 1'],
1721
            ],
1722
            $company2->inferReciprocalComponent(DataObjectTest\Team::class, 'Sponsors')
1723
        );
1724
1725
        // Check many_many_extraFields still works
1726
        $equipmentCompany = $this->objFromFixture(DataObjectTest\EquipmentCompany::class, 'equipmentcompany1');
1727
        $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

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

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

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

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

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

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

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

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