Passed
Push — 4.4 ( 692295...8ee50d )
by Ingo
07:38
created

DataObjectTest::testGetFieldMap()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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

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

filter:
    dependency_paths: ["lib/*"]

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

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

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

filter:
    dependency_paths: ["lib/*"]

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

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

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

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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