Passed
Pull Request — 4.4 (#8952)
by Guy
07:00
created

DataObjectTest::testToMap()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 28
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

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

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

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

1658
        /** @scrutinizer ignore-call */ 
1659
        $equipmentSuppliers = $team->EquipmentSuppliers();
Loading history...
1659
1660
        // Check that DataObject::many_many() works as expected
1661
        $manyManyComponent = DataObject::getSchema()->manyManyComponent(DataObjectTest\Team::class, 'Sponsors');
1662
        $this->assertEquals(ManyManyList::class, $manyManyComponent['relationClass']);
1663
        $this->assertEquals(
1664
            DataObjectTest\Team::class,
1665
            $manyManyComponent['parentClass'],
1666
            'DataObject::many_many() didn\'t find the correct base class'
1667
        );
1668
        $this->assertEquals(
1669
            DataObjectTest\EquipmentCompany::class,
1670
            $manyManyComponent['childClass'],
1671
            'DataObject::many_many() didn\'t find the correct target class for the relation'
1672
        );
1673
        $this->assertEquals(
1674
            'DataObjectTest_EquipmentCompany_SponsoredTeams',
1675
            $manyManyComponent['join'],
1676
            'DataObject::many_many() didn\'t find the correct relation table'
1677
        );
1678
        $this->assertEquals('DataObjectTest_TeamID', $manyManyComponent['parentField']);
1679
        $this->assertEquals('DataObjectTest_EquipmentCompanyID', $manyManyComponent['childField']);
1680
1681
        // Check that ManyManyList still works
1682
        $this->assertEquals(2, $sponsors->count(), 'Rows are missing from relation');
1683
        $this->assertEquals(1, $equipmentSuppliers->count(), 'Rows are missing from relation');
1684
1685
        // Check everything works when no relation is present
1686
        $teamWithoutSponsor = $this->objFromFixture(DataObjectTest\Team::class, 'team3');
1687
        $this->assertInstanceOf(ManyManyList::class, $teamWithoutSponsor->Sponsors());
1688
        $this->assertEquals(0, $teamWithoutSponsor->Sponsors()->count());
1689
1690
        // Test that belongs_many_many can be infered from with getNonReciprocalComponent
1691
        $this->assertListEquals(
1692
            [
1693
                ['Name' => 'Company corp'],
1694
                ['Name' => 'Team co.'],
1695
            ],
1696
            $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

1696
            /** @scrutinizer ignore-type */ $team->inferReciprocalComponent(DataObjectTest\EquipmentCompany::class, 'SponsoredTeams')
Loading history...
1697
        );
1698
1699
        // Test that many_many can be infered from getNonReciprocalComponent
1700
        $this->assertListEquals(
1701
            [
1702
                ['Title' => 'Team 1'],
1703
                ['Title' => 'Team 2'],
1704
                ['Title' => 'Subteam 1'],
1705
            ],
1706
            $company2->inferReciprocalComponent(DataObjectTest\Team::class, 'Sponsors')
1707
        );
1708
1709
        // Check many_many_extraFields still works
1710
        $equipmentCompany = $this->objFromFixture(DataObjectTest\EquipmentCompany::class, 'equipmentcompany1');
1711
        $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

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

1819
        $player = $newTeam->/** @scrutinizer ignore-call */ Players()->first();
Loading history...
1820
        $this->assertEquals('Sam', $player->FirstName);
1821
        $this->assertEquals("Prop", $player->Position);
1822
1823
        // Check that ordering a many-many relation by an aggregate column doesn't fail
1824
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
1825
        $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

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

2133
        $this->assertEquals($company->ID, $ceo->/** @scrutinizer ignore-call */ Company()->ID, 'belongs_to returns the right results.');
Loading history...
2134
2135
        // Test belongs_to can be infered via getNonReciprocalComponent
2136
        // Note: Will be returned as has_many since the belongs_to is ignored.
2137
        $this->assertListEquals(
2138
            [['Name' => 'New Company']],
2139
            $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

2139
            /** @scrutinizer ignore-type */ $ceo->inferReciprocalComponent(DataObjectTest\Company::class, 'CEO')
Loading history...
2140
        );
2141
2142
        // Test has_one to a belongs_to can be infered via getNonReciprocalComponent
2143
        $this->assertEquals(
2144
            $ceo->ID,
2145
            $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...
2146
        );
2147
2148
        // Test automatic creation of class where no assigment exists
2149
        $ceo = new DataObjectTest\CEO();
2150
        $ceo->write();
2151
2152
        $this->assertTrue(
2153
            $ceo->Company() instanceof DataObjectTest\Company,
2154
            'DataObjects across belongs_to relations are automatically created.'
2155
        );
2156
        $this->assertEquals($ceo->ID, $ceo->Company()->CEOID, 'Remote IDs are automatically set.');
2157
2158
        // Write object with components
2159
        $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...
2160
        $ceo->write(false, false, false, true);
2161
        $this->assertTrue($ceo->Company()->isInDB(), 'write() writes belongs_to components to the database.');
2162
2163
        $newCEO = DataObject::get_by_id(DataObjectTest\CEO::class, $ceo->ID);
2164
        $this->assertEquals(
2165
            $ceo->Company()->ID,
2166
            $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

2166
            $newCEO->/** @scrutinizer ignore-call */ 
2167
                     Company()->ID,
Loading history...
2167
            'belongs_to can be retrieved from the database.'
2168
        );
2169
    }
2170
2171
    public function testBelongsToPolymorphic()
2172
    {
2173
        $company = new DataObjectTest\Company();
2174
        $ceo = new DataObjectTest\CEO();
2175
2176
        $company->write();
2177
        $ceo->write();
2178
2179
        // Test belongs_to assignment
2180
        $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...
2181
        $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...
2182
        $company->write();
2183
2184
        $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

2184
        $this->assertEquals($company->ID, $ceo->/** @scrutinizer ignore-call */ CompanyOwned()->ID, 'belongs_to returns the right results.');
Loading history...
2185
        $this->assertInstanceOf(
2186
            DataObjectTest\Company::class,
2187
            $ceo->CompanyOwned(),
2188
            'belongs_to returns the right results.'
2189
        );
2190
2191
        // Test automatic creation of class where no assigment exists
2192
        $ceo = new DataObjectTest\CEO();
2193
        $ceo->write();
2194
2195
        $this->assertTrue(
2196
            $ceo->CompanyOwned() instanceof DataObjectTest\Company,
2197
            'DataObjects across polymorphic belongs_to relations are automatically created.'
2198
        );
2199
        $this->assertEquals($ceo->ID, $ceo->CompanyOwned()->OwnerID, 'Remote IDs are automatically set.');
2200
        $this->assertInstanceOf($ceo->CompanyOwned()->OwnerClass, $ceo, 'Remote class is automatically  set');
2201
2202
        // Write object with components
2203
        $ceo->write(false, false, false, true);
2204
        $this->assertTrue($ceo->CompanyOwned()->isInDB(), 'write() writes belongs_to components to the database.');
2205
2206
        $newCEO = DataObject::get_by_id(DataObjectTest\CEO::class, $ceo->ID);
2207
        $this->assertEquals(
2208
            $ceo->CompanyOwned()->ID,
2209
            $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

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