Passed
Pull Request — 4.0 (#8815)
by Maxime
08:50
created

testWriteManipulationWithNonScalarValuesDisallowed()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 0
dl 0
loc 12
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\ORM\Tests;
4
5
use InvalidArgumentException;
6
use SilverStripe\Core\Config\Config;
7
use SilverStripe\Dev\SapphireTest;
8
use SilverStripe\i18n\i18n;
9
use SilverStripe\ORM\Connect\MySQLDatabase;
10
use SilverStripe\ORM\DataObject;
11
use SilverStripe\ORM\DataObjectSchema;
12
use SilverStripe\ORM\DB;
13
use SilverStripe\ORM\FieldType\DBBoolean;
14
use SilverStripe\ORM\FieldType\DBDatetime;
15
use SilverStripe\ORM\FieldType\DBField;
16
use SilverStripe\ORM\FieldType\DBPolymorphicForeignKey;
17
use SilverStripe\ORM\FieldType\DBVarchar;
18
use SilverStripe\ORM\ManyManyList;
19
use SilverStripe\ORM\Tests\DataObjectTest\Company;
20
use SilverStripe\ORM\Tests\DataObjectTest\Player;
21
use SilverStripe\ORM\Tests\DataObjectTest\Team;
22
use SilverStripe\View\ViewableData;
23
use stdClass;
24
25
class DataObjectTest extends SapphireTest
26
{
27
28
    protected static $fixture_file = 'DataObjectTest.yml';
29
30
    /**
31
     * Standard set of dataobject test classes
32
     *
33
     * @var array
34
     */
35
    public static $extra_data_objects = array(
36
        DataObjectTest\Team::class,
37
        DataObjectTest\Fixture::class,
38
        DataObjectTest\SubTeam::class,
39
        DataObjectTest\OtherSubclassWithSameField::class,
40
        DataObjectTest\FieldlessTable::class,
41
        DataObjectTest\FieldlessSubTable::class,
42
        DataObjectTest\ValidatedObject::class,
43
        DataObjectTest\Player::class,
44
        DataObjectTest\TeamComment::class,
45
        DataObjectTest\EquipmentCompany::class,
46
        DataObjectTest\SubEquipmentCompany::class,
47
        DataObjectTest\ExtendedTeamComment::class,
48
        DataObjectTest\Company::class,
49
        DataObjectTest\Staff::class,
50
        DataObjectTest\CEO::class,
51
        DataObjectTest\Fan::class,
52
        DataObjectTest\Play::class,
53
        DataObjectTest\Ploy::class,
54
        DataObjectTest\Bogey::class,
55
        DataObjectTest\Sortable::class,
56
        DataObjectTest\Bracket::class,
57
        DataObjectTest\RelationParent::class,
58
        DataObjectTest\RelationChildFirst::class,
59
        DataObjectTest\RelationChildSecond::class,
60
        DataObjectTest\MockDynamicAssignmentDataObject::class
61
    );
62
63
    public static function getExtraDataObjects()
64
    {
65
        return array_merge(
66
            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...
67
            ManyManyListTest::$extra_data_objects
68
        );
69
    }
70
71
    public function testDb()
72
    {
73
        $schema = DataObject::getSchema();
74
        $dbFields = $schema->fieldSpecs(DataObjectTest\TeamComment::class);
75
76
        // Assert fields are included
77
        $this->assertArrayHasKey('Name', $dbFields);
78
79
        // Assert the base fields are included
80
        $this->assertArrayHasKey('Created', $dbFields);
81
        $this->assertArrayHasKey('LastEdited', $dbFields);
82
        $this->assertArrayHasKey('ClassName', $dbFields);
83
        $this->assertArrayHasKey('ID', $dbFields);
84
85
        // Assert that the correct field type is returned when passing a field
86
        $this->assertEquals('Varchar', $schema->fieldSpec(DataObjectTest\TeamComment::class, 'Name'));
87
        $this->assertEquals('Text', $schema->fieldSpec(DataObjectTest\TeamComment::class, 'Comment'));
88
89
        // Test with table required
90
        $this->assertEquals(
91
            DataObjectTest\TeamComment::class . '.Varchar',
92
            $schema->fieldSpec(DataObjectTest\TeamComment::class, 'Name', DataObjectSchema::INCLUDE_CLASS)
93
        );
94
        $this->assertEquals(
95
            DataObjectTest\TeamComment::class . '.Text',
96
            $schema->fieldSpec(DataObjectTest\TeamComment::class, 'Comment', DataObjectSchema::INCLUDE_CLASS)
97
        );
98
        $dbFields = $schema->fieldSpecs(DataObjectTest\ExtendedTeamComment::class);
99
100
        // fixed fields are still included in extended classes
101
        $this->assertArrayHasKey('Created', $dbFields);
102
        $this->assertArrayHasKey('LastEdited', $dbFields);
103
        $this->assertArrayHasKey('ClassName', $dbFields);
104
        $this->assertArrayHasKey('ID', $dbFields);
105
106
        // Assert overloaded fields have correct data type
107
        $this->assertEquals('HTMLText', $schema->fieldSpec(DataObjectTest\ExtendedTeamComment::class, 'Comment'));
108
        $this->assertEquals(
109
            'HTMLText',
110
            $dbFields['Comment'],
111
            'Calls to DataObject::db without a field specified return correct data types'
112
        );
113
114
        // assertEquals doesn't verify the order of array elements, so access keys manually to check order:
115
        // 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...
116
        $this->assertEquals(
117
            array(
118
                'Name',
119
                'Comment'
120
            ),
121
            array_slice(array_keys($dbFields), 4, 2),
122
            'DataObject::db returns fields in correct order'
123
        );
124
    }
125
126
    public function testConstructAcceptsValues()
127
    {
128
        // Values can be an array...
129
        $player = new DataObjectTest\Player(
130
            array(
131
                'FirstName' => 'James',
132
                'Surname' => 'Smith'
133
            )
134
        );
135
136
        $this->assertEquals('James', $player->FirstName);
137
        $this->assertEquals('Smith', $player->Surname);
138
139
        // ... or a stdClass inst
140
        $data = new stdClass();
141
        $data->FirstName = 'John';
142
        $data->Surname = 'Doe';
143
        $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

143
        $player = new DataObjectTest\Player(/** @scrutinizer ignore-type */ $data);
Loading history...
144
145
        $this->assertEquals('John', $player->FirstName);
146
        $this->assertEquals('Doe', $player->Surname);
147
148
        // IDs should be stored as integers, not strings
149
        $player = new DataObjectTest\Player(array('ID' => '5'));
150
        $this->assertSame(5, $player->ID);
151
    }
152
153
    public function testValidObjectsForBaseFields()
154
    {
155
        $obj = new DataObjectTest\ValidatedObject();
156
157
        foreach (array('Created', 'LastEdited', 'ClassName', 'ID') as $field) {
158
            $helper = $obj->dbObject($field);
159
            $this->assertTrue(
160
                ($helper instanceof DBField),
161
                "for {$field} expected helper to be DBField, but was " . (is_object($helper) ? get_class($helper) : "null")
162
            );
163
        }
164
    }
165
166
    public function testDataIntegrityWhenTwoSubclassesHaveSameField()
167
    {
168
        // Save data into DataObjectTest_SubTeam.SubclassDatabaseField
169
        $obj = new DataObjectTest\SubTeam();
170
        $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...
171
        $obj->write();
172
173
        // Change the class
174
        $obj->ClassName = DataObjectTest\OtherSubclassWithSameField::class;
175
        $obj->write();
176
        $obj->flushCache();
177
178
        // Re-fetch from the database and confirm that the data is sourced from
179
        // OtherSubclassWithSameField.SubclassDatabaseField
180
        $obj = DataObject::get_by_id(DataObjectTest\Team::class, $obj->ID);
181
        $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...
182
183
        // Confirm that save the object in the other direction.
184
        $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...
185
        $obj->write();
186
187
        $obj->ClassName = DataObjectTest\SubTeam::class;
188
        $obj->write();
189
        $obj->flushCache();
190
191
        // If we restore the class, the old value has been lying dormant and will be available again.
192
        // NOTE: This behaviour is volatile; we may change this in the future to clear fields that
193
        // are no longer relevant when changing ClassName
194
        $obj = DataObject::get_by_id(DataObjectTest\Team::class, $obj->ID);
195
        $this->assertEquals('obj-SubTeam', $obj->SubclassDatabaseField);
196
    }
197
198
    /**
199
     * Test deletion of DataObjects
200
     *   - Deleting using delete() on the DataObject
201
     *   - Deleting using DataObject::delete_by_id()
202
     */
203
    public function testDelete()
204
    {
205
        // Test deleting using delete() on the DataObject
206
        // Get the first page
207
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
208
        $objID = $obj->ID;
209
        // Check the page exists before deleting
210
        $this->assertTrue(is_object($obj) && $obj->exists());
211
        // Delete the page
212
        $obj->delete();
213
        // Check that page does not exist after deleting
214
        $obj = DataObject::get_by_id(DataObjectTest\Player::class, $objID);
215
        $this->assertTrue(!$obj || !$obj->exists());
216
217
218
        // Test deleting using DataObject::delete_by_id()
219
        // Get the second page
220
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
221
        $objID = $obj->ID;
222
        // Check the page exists before deleting
223
        $this->assertTrue(is_object($obj) && $obj->exists());
224
        // Delete the page
225
        DataObject::delete_by_id(DataObjectTest\Player::class, $obj->ID);
226
        // Check that page does not exist after deleting
227
        $obj = DataObject::get_by_id(DataObjectTest\Player::class, $objID);
228
        $this->assertTrue(!$obj || !$obj->exists());
229
    }
230
231
    /**
232
     * Test methods that get DataObjects
233
     *   - DataObject::get()
234
     *       - All records of a DataObject
235
     *       - Filtering
236
     *       - Sorting
237
     *       - Joins
238
     *       - Limit
239
     *       - Container class
240
     *   - DataObject::get_by_id()
241
     *   - DataObject::get_one()
242
     *        - With and without caching
243
     *        - With and without ordering
244
     */
245
    public function testGet()
246
    {
247
        // Test getting all records of a DataObject
248
        $comments = DataObject::get(DataObjectTest\TeamComment::class);
249
        $this->assertEquals(3, $comments->count());
250
251
        // Test WHERE clause
252
        $comments = DataObject::get(DataObjectTest\TeamComment::class, "\"Name\"='Bob'");
253
        $this->assertEquals(1, $comments->count());
254
        foreach ($comments as $comment) {
255
            $this->assertEquals('Bob', $comment->Name);
256
        }
257
258
        // Test sorting
259
        $comments = DataObject::get(DataObjectTest\TeamComment::class, '', "\"Name\" ASC");
260
        $this->assertEquals(3, $comments->count());
261
        $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...
262
        $comments = DataObject::get(DataObjectTest\TeamComment::class, '', "\"Name\" DESC");
263
        $this->assertEquals(3, $comments->count());
264
        $this->assertEquals('Phil', $comments->first()->Name);
265
266
        // Test limit
267
        $comments = DataObject::get(DataObjectTest\TeamComment::class, '', "\"Name\" ASC", '', '1,2');
268
        $this->assertEquals(2, $comments->count());
269
        $this->assertEquals('Joe', $comments->first()->Name);
270
        $this->assertEquals('Phil', $comments->last()->Name);
271
272
        // Test get_by_id()
273
        $captain1ID = $this->idFromFixture(DataObjectTest\Player::class, 'captain1');
274
        $captain1 = DataObject::get_by_id(DataObjectTest\Player::class, $captain1ID);
275
        $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...
276
277
        // Test get_one() without caching
278
        $comment1 = DataObject::get_one(
279
            DataObjectTest\TeamComment::class,
280
            array(
281
                '"DataObjectTest_TeamComment"."Name"' => 'Joe'
282
            ),
283
            false
284
        );
285
        $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...
286
287
        $comment2 = DataObject::get_one(
288
            DataObjectTest\TeamComment::class,
289
            array(
290
                '"DataObjectTest_TeamComment"."Name"' => 'Joe'
291
            ),
292
            false
293
        );
294
        $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...
295
296
        // Test get_one() with caching
297
        $comment1 = DataObject::get_one(
298
            DataObjectTest\TeamComment::class,
299
            array(
300
                '"DataObjectTest_TeamComment"."Name"' => 'Bob'
301
            ),
302
            true
303
        );
304
        $comment1->Comment = "Something Else";
305
306
        $comment2 = DataObject::get_one(
307
            DataObjectTest\TeamComment::class,
308
            array(
309
                '"DataObjectTest_TeamComment"."Name"' => 'Bob'
310
            ),
311
            true
312
        );
313
        $this->assertEquals((string)$comment1->Comment, (string)$comment2->Comment);
314
315
        // Test get_one() with order by without caching
316
        $comment = DataObject::get_one(DataObjectTest\TeamComment::class, '', false, "\"Name\" ASC");
317
        $this->assertEquals('Bob', $comment->Name);
318
319
        $comment = DataObject::get_one(DataObjectTest\TeamComment::class, '', false, "\"Name\" DESC");
320
        $this->assertEquals('Phil', $comment->Name);
321
322
        // Test get_one() with order by with caching
323
        $comment = DataObject::get_one(DataObjectTest\TeamComment::class, '', true, '"Name" ASC');
324
        $this->assertEquals('Bob', $comment->Name);
325
        $comment = DataObject::get_one(DataObjectTest\TeamComment::class, '', true, '"Name" DESC');
326
        $this->assertEquals('Phil', $comment->Name);
327
    }
328
329
    public function testGetCaseInsensitive()
330
    {
331
        // Test get_one() with bad case on the classname
332
        // Note: This will succeed only if the underlying DB server supports case-insensitive
333
        // table names (e.g. such as MySQL, but not SQLite3)
334
        if (!(DB::get_conn() instanceof MySQLDatabase)) {
335
            $this->markTestSkipped('MySQL only');
336
        }
337
338
        $subteam1 = DataObject::get_one(
339
            strtolower(DataObjectTest\SubTeam::class),
340
            array(
341
                '"DataObjectTest_Team"."Title"' => 'Subteam 1'
342
            ),
343
            true
344
        );
345
        $this->assertNotEmpty($subteam1);
346
        $this->assertEquals($subteam1->Title, "Subteam 1");
0 ignored issues
show
Bug Best Practice introduced by
The property Title does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
347
    }
348
349
    public function testGetSubclassFields()
350
    {
351
        /* Test that fields / has_one relations from the parent table and the subclass tables are extracted */
352
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, "captain1");
353
        // Base field
354
        $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...
355
        // Subclass field
356
        $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...
357
        // Subclass has_one relation
358
        $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...
359
    }
360
361
    public function testGetRelationClass()
362
    {
363
        $obj = new DataObjectTest\Player();
0 ignored issues
show
Unused Code introduced by
The assignment to $obj is dead and can be removed.
Loading history...
364
        $this->assertEquals(
365
            singleton(DataObjectTest\Player::class)->getRelationClass('FavouriteTeam'),
366
            DataObjectTest\Team::class,
367
            'has_one is properly inspected'
368
        );
369
        $this->assertEquals(
370
            singleton(DataObjectTest\Company::class)->getRelationClass('CurrentStaff'),
371
            DataObjectTest\Staff::class,
372
            'has_many is properly inspected'
373
        );
374
        $this->assertEquals(
375
            singleton(DataObjectTest\Team::class)->getRelationClass('Players'),
376
            DataObjectTest\Player::class,
377
            'many_many is properly inspected'
378
        );
379
        $this->assertEquals(
380
            singleton(DataObjectTest\Player::class)->getRelationClass('Teams'),
381
            DataObjectTest\Team::class,
382
            'belongs_many_many is properly inspected'
383
        );
384
        $this->assertEquals(
385
            singleton(DataObjectTest\CEO::class)->getRelationClass('Company'),
386
            DataObjectTest\Company::class,
387
            'belongs_to is properly inspected'
388
        );
389
        $this->assertEquals(
390
            singleton(DataObjectTest\Fan::class)->getRelationClass('Favourite'),
391
            DataObject::class,
392
            'polymorphic has_one is properly inspected'
393
        );
394
    }
395
396
    /**
397
     * Test that has_one relations can be retrieved
398
     */
399
    public function testGetHasOneRelations()
400
    {
401
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, "captain1");
402
        $team1ID = $this->idFromFixture(DataObjectTest\Team::class, 'team1');
403
404
        // There will be a field called (relname)ID that contains the ID of the
405
        // object linked to via the has_one relation
406
        $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...
407
408
        // There will be a method called $obj->relname() that returns the object itself
409
        $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

409
        $this->assertEquals($team1ID, $captain1->/** @scrutinizer ignore-call */ FavouriteTeam()->ID);
Loading history...
410
411
        // Test that getNonReciprocalComponent can find has_one from the has_many end
412
        $this->assertEquals(
413
            $team1ID,
414
            $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...
415
        );
416
417
        // Check entity with polymorphic has-one
418
        $fan1 = $this->objFromFixture(DataObjectTest\Fan::class, "fan1");
419
        $this->assertTrue((bool)$fan1->hasValue('Favourite'));
420
421
        // There will be fields named (relname)ID and (relname)Class for polymorphic
422
        // entities
423
        $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...
424
        $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...
425
426
        // There will be a method called $obj->relname() that returns the object itself
427
        $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

427
        /** @scrutinizer ignore-call */ 
428
        $favourite = $fan1->Favourite();
Loading history...
428
        $this->assertEquals($team1ID, $favourite->ID);
429
        $this->assertInstanceOf(DataObjectTest\Team::class, $favourite);
430
431
        // check behaviour of dbObject with polymorphic relations
432
        $favouriteDBObject = $fan1->dbObject('Favourite');
433
        $favouriteValue = $favouriteDBObject->getValue();
434
        $this->assertInstanceOf(DBPolymorphicForeignKey::class, $favouriteDBObject);
435
        $this->assertEquals($favourite->ID, $favouriteValue->ID);
436
        $this->assertEquals($favourite->ClassName, $favouriteValue->ClassName);
437
    }
438
439
    public function testLimitAndCount()
440
    {
441
        $players = DataObject::get(DataObjectTest\Player::class);
442
443
        // There's 4 records in total
444
        $this->assertEquals(4, $players->count());
445
446
        // Testing "##, ##" syntax
447
        $this->assertEquals(4, $players->limit(20)->count());
448
        $this->assertEquals(4, $players->limit(20, 0)->count());
449
        $this->assertEquals(0, $players->limit(20, 20)->count());
450
        $this->assertEquals(2, $players->limit(2, 0)->count());
451
        $this->assertEquals(1, $players->limit(5, 3)->count());
452
    }
453
454
    public function testWriteNoChangesDoesntUpdateLastEdited()
455
    {
456
        // set mock now so we can be certain of LastEdited time for our test
457
        DBDatetime::set_mock_now('2017-01-01 00:00:00');
458
        $obj = new Player();
459
        $obj->FirstName = 'Test';
460
        $obj->Surname = 'Plater';
461
        $obj->Email = '[email protected]';
462
        $obj->write();
463
        $this->assertEquals('2017-01-01 00:00:00', $obj->LastEdited);
464
        $writtenObj = Player::get()->byID($obj->ID);
465
        $this->assertEquals('2017-01-01 00:00:00', $writtenObj->LastEdited);
466
467
        // set mock now so we get a new LastEdited if, for some reason, it's updated
468
        DBDatetime::set_mock_now('2017-02-01 00:00:00');
469
        $writtenObj->write();
470
        $this->assertEquals('2017-01-01 00:00:00', $writtenObj->LastEdited);
471
        $this->assertEquals($obj->ID, $writtenObj->ID);
472
473
        $reWrittenObj = Player::get()->byID($writtenObj->ID);
474
        $this->assertEquals('2017-01-01 00:00:00', $reWrittenObj->LastEdited);
475
    }
476
477
    /**
478
     * Test writing of database columns which don't correlate to a DBField,
479
     * e.g. all relation fields on has_one/has_many like "ParentID".
480
     */
481
    public function testWritePropertyWithoutDBField()
482
    {
483
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
484
        $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...
485
        $obj->write();
486
487
        // reload the page from the database
488
        $savedObj = DataObject::get_by_id(DataObjectTest\Player::class, $obj->ID);
489
        $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...
490
491
        // Test with porymorphic relation
492
        $obj2 = $this->objFromFixture(DataObjectTest\Fan::class, "fan1");
493
        $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...
494
        $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...
495
        $obj2->write();
496
497
        $savedObj2 = DataObject::get_by_id(DataObjectTest\Fan::class, $obj2->ID);
498
        $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...
499
        $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...
500
    }
501
502
    /**
503
     * Test has many relationships
504
     *   - Test getComponents() gets the ComponentSet of the other side of the relation
505
     *   - Test the IDs on the DataObjects are set correctly
506
     */
507
    public function testHasManyRelationships()
508
    {
509
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
510
511
        // Test getComponents() gets the ComponentSet of the other side of the relation
512
        $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

512
        $this->assertTrue($team1->/** @scrutinizer ignore-call */ Comments()->count() == 2);
Loading history...
513
514
        $team1Comments = [
515
            ['Comment' => 'This is a team comment by Joe'],
516
            ['Comment' => 'This is a team comment by Bob'],
517
        ];
518
519
        // Test the IDs on the DataObjects are set correctly
520
        $this->assertListEquals($team1Comments, $team1->Comments());
521
522
        // Test that has_many can be infered from the has_one via getNonReciprocalComponent
523
        $this->assertListEquals(
524
            $team1Comments,
525
            $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

525
            /** @scrutinizer ignore-type */ $team1->inferReciprocalComponent(DataObjectTest\TeamComment::class, 'Team')
Loading history...
526
        );
527
528
        // Test that we can add and remove items that already exist in the database
529
        $newComment = new DataObjectTest\TeamComment();
530
        $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...
531
        $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...
532
        $newComment->write();
533
        $team1->Comments()->add($newComment);
534
        $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...
535
536
        $comment1 = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment1');
537
        $comment2 = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment2');
538
        $team1->Comments()->remove($comment2);
539
540
        $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

540
        $team1CommentIDs = $team1->Comments()->/** @scrutinizer ignore-call */ sort('ID')->column('ID');
Loading history...
541
        $this->assertEquals(array($comment1->ID, $newComment->ID), $team1CommentIDs);
542
543
        // Test that removing an item from a list doesn't remove it from the same
544
        // relation belonging to a different object
545
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
546
        $team2 = $this->objFromFixture(DataObjectTest\Team::class, 'team2');
547
        $team2->Comments()->remove($comment1);
548
        $team1CommentIDs = $team1->Comments()->sort('ID')->column('ID');
549
        $this->assertEquals(array($comment1->ID, $newComment->ID), $team1CommentIDs);
550
    }
551
552
553
    /**
554
     * Test has many relationships against polymorphic has_one fields
555
     *   - Test getComponents() gets the ComponentSet of the other side of the relation
556
     *   - Test the IDs on the DataObjects are set correctly
557
     */
558
    public function testHasManyPolymorphicRelationships()
559
    {
560
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
561
562
        // Test getComponents() gets the ComponentSet of the other side of the relation
563
        $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

563
        $this->assertTrue($team1->/** @scrutinizer ignore-call */ Fans()->count() == 2);
Loading history...
564
565
        // Test the IDs/Classes on the DataObjects are set correctly
566
        foreach ($team1->Fans() as $fan) {
567
            $this->assertEquals($team1->ID, $fan->FavouriteID, 'Fan has the correct FavouriteID');
568
            $this->assertEquals(DataObjectTest\Team::class, $fan->FavouriteClass, 'Fan has the correct FavouriteClass');
569
        }
570
571
        // Test that we can add and remove items that already exist in the database
572
        $newFan = new DataObjectTest\Fan();
573
        $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...
574
        $newFan->write();
575
        $team1->Fans()->add($newFan);
576
        $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...
577
        $this->assertEquals(
578
            DataObjectTest\Team::class,
579
            $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...
580
            'Newly created fan has the correct FavouriteClass'
581
        );
582
583
        $fan1 = $this->objFromFixture(DataObjectTest\Fan::class, 'fan1');
584
        $fan3 = $this->objFromFixture(DataObjectTest\Fan::class, 'fan3');
585
        $team1->Fans()->remove($fan3);
586
587
        $team1FanIDs = $team1->Fans()->sort('ID')->column('ID');
588
        $this->assertEquals(array($fan1->ID, $newFan->ID), $team1FanIDs);
589
590
        // Test that removing an item from a list doesn't remove it from the same
591
        // relation belonging to a different object
592
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
593
        $player1 = $this->objFromFixture(DataObjectTest\Player::class, 'player1');
594
        $player1->Fans()->remove($fan1);
595
        $team1FanIDs = $team1->Fans()->sort('ID')->column('ID');
596
        $this->assertEquals(array($fan1->ID, $newFan->ID), $team1FanIDs);
597
    }
598
599
600
    public function testHasOneRelationship()
601
    {
602
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
603
        $player1 = $this->objFromFixture(DataObjectTest\Player::class, 'player1');
604
        $player2 = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
605
        $fan1 = $this->objFromFixture(DataObjectTest\Fan::class, 'fan1');
606
607
        // Test relation probing
608
        $this->assertFalse((bool)$team1->hasValue('Captain', null, false));
609
        $this->assertFalse((bool)$team1->hasValue('CaptainID', null, false));
610
611
        // Add a captain to team 1
612
        $team1->setField('CaptainID', $player1->ID);
613
        $team1->write();
614
615
        $this->assertTrue((bool)$team1->hasValue('Captain', null, false));
616
        $this->assertTrue((bool)$team1->hasValue('CaptainID', null, false));
617
618
        $this->assertEquals(
619
            $player1->ID,
620
            $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

620
            $team1->/** @scrutinizer ignore-call */ 
621
                    Captain()->ID,
Loading history...
621
            'The captain exists for team 1'
622
        );
623
        $this->assertEquals(
624
            $player1->ID,
625
            $team1->getComponent('Captain')->ID,
626
            'The captain exists through the component getter'
627
        );
628
629
        $this->assertEquals(
630
            $team1->Captain()->FirstName,
631
            'Player 1',
632
            'Player 1 is the captain'
633
        );
634
        $this->assertEquals(
635
            $team1->getComponent('Captain')->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...
636
            'Player 1',
637
            'Player 1 is the captain'
638
        );
639
640
        $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...
641
        $team1->write();
642
643
        $this->assertEquals($player2->ID, $team1->Captain()->ID);
644
        $this->assertEquals($player2->ID, $team1->getComponent('Captain')->ID);
645
        $this->assertEquals('Player 2', $team1->Captain()->FirstName);
646
        $this->assertEquals('Player 2', $team1->getComponent('Captain')->FirstName);
647
648
649
        // Set the favourite team for fan1
650
        $fan1->setField('FavouriteID', $team1->ID);
651
        $fan1->setField('FavouriteClass', get_class($team1));
652
653
        $this->assertEquals($team1->ID, $fan1->Favourite()->ID, 'The team is assigned to fan 1');
654
        $this->assertInstanceOf(get_class($team1), $fan1->Favourite(), 'The team is assigned to fan 1');
655
        $this->assertEquals(
656
            $team1->ID,
657
            $fan1->getComponent('Favourite')->ID,
658
            'The team exists through the component getter'
659
        );
660
        $this->assertInstanceOf(
661
            get_class($team1),
662
            $fan1->getComponent('Favourite'),
663
            'The team exists through the component getter'
664
        );
665
666
        $this->assertEquals(
667
            $fan1->Favourite()->Title,
668
            'Team 1',
669
            'Team 1 is the favourite'
670
        );
671
        $this->assertEquals(
672
            $fan1->getComponent('Favourite')->Title,
0 ignored issues
show
Bug Best Practice introduced by
The property Title does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
673
            'Team 1',
674
            'Team 1 is the favourite'
675
        );
676
    }
677
678
    /**
679
     * @todo Extend type change tests (e.g. '0'==NULL)
680
     */
681
    public function testChangedFields()
682
    {
683
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
684
        $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...
685
        $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...
686
687
        $this->assertEquals(
688
            $obj->getChangedFields(true, DataObject::CHANGE_STRICT),
689
            array(
690
                'FirstName' => array(
691
                    'before' => 'Captain',
692
                    'after' => 'Captain-changed',
693
                    'level' => DataObject::CHANGE_VALUE
694
                ),
695
                'IsRetired' => array(
696
                    'before' => 1,
697
                    'after' => true,
698
                    'level' => DataObject::CHANGE_STRICT
699
                )
700
            ),
701
            'Changed fields are correctly detected with strict type changes (level=1)'
702
        );
703
704
        $this->assertEquals(
705
            $obj->getChangedFields(true, DataObject::CHANGE_VALUE),
706
            array(
707
                'FirstName' => array(
708
                    'before' => 'Captain',
709
                    'after' => 'Captain-changed',
710
                    'level' => DataObject::CHANGE_VALUE
711
                )
712
            ),
713
            'Changed fields are correctly detected while ignoring type changes (level=2)'
714
        );
715
716
        $newObj = new DataObjectTest\Player();
717
        $newObj->FirstName = "New Player";
718
        $this->assertEquals(
719
            array(
720
                'FirstName' => array(
721
                    'before' => null,
722
                    'after' => 'New Player',
723
                    'level' => DataObject::CHANGE_VALUE
724
                )
725
            ),
726
            $newObj->getChangedFields(true, DataObject::CHANGE_VALUE),
727
            'Initialised fields are correctly detected as full changes'
728
        );
729
    }
730
731
    /**
732
     * @skipUpgrade
733
     */
734
    public function testIsChanged()
735
    {
736
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
737
        $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...
738
        $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...
739
        $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...
740
741
        // Now that DB fields are changed, isChanged is true
742
        $this->assertTrue($obj->isChanged('NonDBField'));
743
        $this->assertFalse($obj->isChanged('NonField'));
744
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_STRICT));
745
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_VALUE));
746
        $this->assertTrue($obj->isChanged('IsRetired', DataObject::CHANGE_STRICT));
747
        $this->assertFalse($obj->isChanged('IsRetired', DataObject::CHANGE_VALUE));
748
        $this->assertFalse($obj->isChanged('Email', 1), 'Doesnt change mark unchanged property');
749
        $this->assertFalse($obj->isChanged('Email', 2), 'Doesnt change mark unchanged property');
750
751
        $newObj = new DataObjectTest\Player();
752
        $newObj->FirstName = "New Player";
753
        $this->assertTrue($newObj->isChanged('FirstName', DataObject::CHANGE_STRICT));
754
        $this->assertTrue($newObj->isChanged('FirstName', DataObject::CHANGE_VALUE));
755
        $this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_STRICT));
756
        $this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_VALUE));
757
758
        $newObj->write();
759
        $this->assertFalse($newObj->ischanged());
760
        $this->assertFalse($newObj->isChanged('FirstName', DataObject::CHANGE_STRICT));
761
        $this->assertFalse($newObj->isChanged('FirstName', DataObject::CHANGE_VALUE));
762
        $this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_STRICT));
763
        $this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_VALUE));
764
765
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
766
        $obj->FirstName = null;
767
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_STRICT));
768
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_VALUE));
769
770
        /* Test when there's not field provided */
771
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
772
        $this->assertFalse($obj->isChanged());
773
        $obj->NonDBField = 'new value';
774
        $this->assertFalse($obj->isChanged());
775
        $obj->FirstName = "New Player";
776
        $this->assertTrue($obj->isChanged());
777
778
        $obj->write();
779
        $this->assertFalse($obj->isChanged());
780
    }
781
782
    public function testRandomSort()
783
    {
784
        /* If we perform the same regularly sorted query twice, it should return the same results */
785
        $itemsA = DataObject::get(DataObjectTest\TeamComment::class, "", "ID");
786
        foreach ($itemsA as $item) {
787
            $keysA[] = $item->ID;
788
        }
789
790
        $itemsB = DataObject::get(DataObjectTest\TeamComment::class, "", "ID");
791
        foreach ($itemsB as $item) {
792
            $keysB[] = $item->ID;
793
        }
794
795
        /* Test when there's not field provided */
796
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
797
        $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...
798
        $this->assertTrue($obj->isChanged());
799
800
        $obj->write();
801
        $this->assertFalse($obj->isChanged());
802
803
        /* If we perform the same random query twice, it shouldn't return the same results */
804
        $itemsA = DataObject::get(DataObjectTest\TeamComment::class, "", DB::get_conn()->random());
805
        $itemsB = DataObject::get(DataObjectTest\TeamComment::class, "", DB::get_conn()->random());
806
        $itemsC = DataObject::get(DataObjectTest\TeamComment::class, "", DB::get_conn()->random());
807
        $itemsD = DataObject::get(DataObjectTest\TeamComment::class, "", DB::get_conn()->random());
808
        foreach ($itemsA as $item) {
809
            $keysA[] = $item->ID;
810
        }
811
        foreach ($itemsB as $item) {
812
            $keysB[] = $item->ID;
813
        }
814
        foreach ($itemsC as $item) {
815
            $keysC[] = $item->ID;
816
        }
817
        foreach ($itemsD as $item) {
818
            $keysD[] = $item->ID;
819
        }
820
821
        // These shouldn't all be the same (run it 4 times to minimise chance of an accidental collision)
822
        // There's about a 1 in a billion chance of an accidental collision
823
        $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 817. 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 786. 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 791. 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 814. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
824
    }
825
826
    public function testWriteSavesToHasOneRelations()
827
    {
828
        /* DataObject::write() should save to a has_one relationship if you set a field called (relname)ID */
829
        $team = new DataObjectTest\Team();
830
        $captainID = $this->idFromFixture(DataObjectTest\Player::class, 'player1');
831
        $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...
832
        $team->write();
833
        $this->assertEquals(
834
            $captainID,
835
            DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $team->ID")->value()
836
        );
837
838
        /* After giving it a value, you should also be able to set it back to null */
839
        $team->CaptainID = '';
840
        $team->write();
841
        $this->assertEquals(
842
            0,
843
            DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $team->ID")->value()
844
        );
845
846
        /* You should also be able to save a blank to it when it's first created */
847
        $team = new DataObjectTest\Team();
848
        $team->CaptainID = '';
849
        $team->write();
850
        $this->assertEquals(
851
            0,
852
            DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $team->ID")->value()
853
        );
854
855
        /* Ditto for existing records without a value */
856
        $existingTeam = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
857
        $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...
858
        $existingTeam->write();
859
        $this->assertEquals(
860
            0,
861
            DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $existingTeam->ID")->value()
862
        );
863
    }
864
865
    public function testCanAccessHasOneObjectsAsMethods()
866
    {
867
        /* If you have a has_one relation 'Captain' on $obj, and you set the $obj->CaptainID = (ID), then the
868
        * object itself should be accessible as $obj->Captain() */
869
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
870
        $captainID = $this->idFromFixture(DataObjectTest\Player::class, 'captain1');
871
872
        $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...
873
        $this->assertNotNull($team->Captain());
874
        $this->assertEquals($captainID, $team->Captain()->ID);
875
876
        // Test for polymorphic has_one relations
877
        $fan = $this->objFromFixture(DataObjectTest\Fan::class, 'fan1');
878
        $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...
879
        $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...
880
        $this->assertNotNull($fan->Favourite());
881
        $this->assertEquals($team->ID, $fan->Favourite()->ID);
882
        $this->assertInstanceOf(DataObjectTest\Team::class, $fan->Favourite());
883
    }
884
885
    public function testFieldNamesThatMatchMethodNamesWork()
886
    {
887
        /* Check that a field name that corresponds to a method on DataObject will still work */
888
        $obj = new DataObjectTest\Fixture();
889
        $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...
890
        $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...
891
        $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...
892
        $obj->write();
893
894
        $this->assertNotNull($obj->ID);
895
        $this->assertEquals(
896
            'value1',
897
            DB::query("SELECT \"Data\" FROM \"DataObjectTest_Fixture\" WHERE \"ID\" = $obj->ID")->value()
898
        );
899
        $this->assertEquals(
900
            'value2',
901
            DB::query("SELECT \"DbObject\" FROM \"DataObjectTest_Fixture\" WHERE \"ID\" = $obj->ID")->value()
902
        );
903
        $this->assertEquals(
904
            'value3',
905
            DB::query("SELECT \"Duplicate\" FROM \"DataObjectTest_Fixture\" WHERE \"ID\" = $obj->ID")->value()
906
        );
907
    }
908
909
    /**
910
     * @todo Re-enable all test cases for field existence after behaviour has been fixed
911
     */
912
    public function testFieldExistence()
913
    {
914
        $teamInstance = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
915
        $teamSingleton = singleton(DataObjectTest\Team::class);
916
917
        $subteamInstance = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
918
        $schema = DataObject::getSchema();
919
920
        /* hasField() singleton checks */
921
        $this->assertTrue(
922
            $teamSingleton->hasField('ID'),
923
            'hasField() finds built-in fields in singletons'
924
        );
925
        $this->assertTrue(
926
            $teamSingleton->hasField('Title'),
927
            'hasField() finds custom fields in singletons'
928
        );
929
930
        /* hasField() instance checks */
931
        $this->assertFalse(
932
            $teamInstance->hasField('NonExistingField'),
933
            'hasField() doesnt find non-existing fields in instances'
934
        );
935
        $this->assertTrue(
936
            $teamInstance->hasField('ID'),
937
            'hasField() finds built-in fields in instances'
938
        );
939
        $this->assertTrue(
940
            $teamInstance->hasField('Created'),
941
            'hasField() finds built-in fields in instances'
942
        );
943
        $this->assertTrue(
944
            $teamInstance->hasField('DatabaseField'),
945
            'hasField() finds custom fields in instances'
946
        );
947
        //$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...
948
        //'hasField() doesnt find subclass fields in parentclass instances');
949
        $this->assertTrue(
950
            $teamInstance->hasField('DynamicField'),
951
            'hasField() finds dynamic getters in instances'
952
        );
953
        $this->assertTrue(
954
            $teamInstance->hasField('HasOneRelationshipID'),
955
            'hasField() finds foreign keys in instances'
956
        );
957
        $this->assertTrue(
958
            $teamInstance->hasField('ExtendedDatabaseField'),
959
            'hasField() finds extended fields in instances'
960
        );
961
        $this->assertTrue(
962
            $teamInstance->hasField('ExtendedHasOneRelationshipID'),
963
            'hasField() finds extended foreign keys in instances'
964
        );
965
        //$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...
966
        //'hasField() includes extended dynamic getters in instances');
967
968
        /* hasField() subclass checks */
969
        $this->assertTrue(
970
            $subteamInstance->hasField('ID'),
971
            'hasField() finds built-in fields in subclass instances'
972
        );
973
        $this->assertTrue(
974
            $subteamInstance->hasField('Created'),
975
            'hasField() finds built-in fields in subclass instances'
976
        );
977
        $this->assertTrue(
978
            $subteamInstance->hasField('DatabaseField'),
979
            'hasField() finds custom fields in subclass instances'
980
        );
981
        $this->assertTrue(
982
            $subteamInstance->hasField('SubclassDatabaseField'),
983
            'hasField() finds custom fields in subclass instances'
984
        );
985
        $this->assertTrue(
986
            $subteamInstance->hasField('DynamicField'),
987
            'hasField() finds dynamic getters in subclass instances'
988
        );
989
        $this->assertTrue(
990
            $subteamInstance->hasField('HasOneRelationshipID'),
991
            'hasField() finds foreign keys in subclass instances'
992
        );
993
        $this->assertTrue(
994
            $subteamInstance->hasField('ExtendedDatabaseField'),
995
            'hasField() finds extended fields in subclass instances'
996
        );
997
        $this->assertTrue(
998
            $subteamInstance->hasField('ExtendedHasOneRelationshipID'),
999
            'hasField() finds extended foreign keys in subclass instances'
1000
        );
1001
1002
        /* hasDatabaseField() singleton checks */
1003
        //$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...
1004
        //'hasDatabaseField() finds built-in fields in singletons');
1005
        $this->assertNotEmpty(
1006
            $schema->fieldSpec(DataObjectTest\Team::class, 'Title'),
1007
            'hasDatabaseField() finds custom fields in singletons'
1008
        );
1009
1010
        /* hasDatabaseField() instance checks */
1011
        $this->assertNull(
1012
            $schema->fieldSpec(DataObjectTest\Team::class, 'NonExistingField'),
1013
            'hasDatabaseField() doesnt find non-existing fields in instances'
1014
        );
1015
        //$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...
1016
        //'hasDatabaseField() finds built-in fields in instances');
1017
        $this->assertNotEmpty(
1018
            $schema->fieldSpec(DataObjectTest\Team::class, 'Created'),
1019
            'hasDatabaseField() finds built-in fields in instances'
1020
        );
1021
        $this->assertNotEmpty(
1022
            $schema->fieldSpec(DataObjectTest\Team::class, 'DatabaseField'),
1023
            'hasDatabaseField() finds custom fields in instances'
1024
        );
1025
        $this->assertNull(
1026
            $schema->fieldSpec(DataObjectTest\Team::class, 'SubclassDatabaseField'),
1027
            'hasDatabaseField() doesnt find subclass fields in parentclass instances'
1028
        );
1029
        //$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...
1030
        //'hasDatabaseField() doesnt dynamic getters in instances');
1031
        $this->assertNotEmpty(
1032
            $schema->fieldSpec(DataObjectTest\Team::class, 'HasOneRelationshipID'),
1033
            'hasDatabaseField() finds foreign keys in instances'
1034
        );
1035
        $this->assertNotEmpty(
1036
            $schema->fieldSpec(DataObjectTest\Team::class, 'ExtendedDatabaseField'),
1037
            'hasDatabaseField() finds extended fields in instances'
1038
        );
1039
        $this->assertNotEmpty(
1040
            $schema->fieldSpec(DataObjectTest\Team::class, 'ExtendedHasOneRelationshipID'),
1041
            'hasDatabaseField() finds extended foreign keys in instances'
1042
        );
1043
        $this->assertNull(
1044
            $schema->fieldSpec(DataObjectTest\Team::class, 'ExtendedDynamicField'),
1045
            'hasDatabaseField() doesnt include extended dynamic getters in instances'
1046
        );
1047
1048
        /* hasDatabaseField() subclass checks */
1049
        $this->assertNotEmpty(
1050
            $schema->fieldSpec(DataObjectTest\SubTeam::class, 'DatabaseField'),
1051
            'hasField() finds custom fields in subclass instances'
1052
        );
1053
        $this->assertNotEmpty(
1054
            $schema->fieldSpec(DataObjectTest\SubTeam::class, 'SubclassDatabaseField'),
1055
            'hasField() finds custom fields in subclass instances'
1056
        );
1057
    }
1058
1059
    /**
1060
     * @todo Re-enable all test cases for field inheritance aggregation after behaviour has been fixed
1061
     */
1062
    public function testFieldInheritance()
1063
    {
1064
        $schema = DataObject::getSchema();
1065
1066
        // Test logical fields (including composite)
1067
        $teamSpecifications = $schema->fieldSpecs(DataObjectTest\Team::class);
1068
        $expected = array(
1069
            'ID',
1070
            'ClassName',
1071
            'LastEdited',
1072
            'Created',
1073
            'Title',
1074
            'DatabaseField',
1075
            'ExtendedDatabaseField',
1076
            'CaptainID',
1077
            'FounderID',
1078
            'HasOneRelationshipID',
1079
            'ExtendedHasOneRelationshipID'
1080
        );
1081
        $actual = array_keys($teamSpecifications);
1082
        sort($expected);
1083
        sort($actual);
1084
        $this->assertEquals(
1085
            $expected,
1086
            $actual,
1087
            'fieldSpecifications() contains all fields defined on instance: base, extended and foreign keys'
1088
        );
1089
1090
        $teamFields = $schema->databaseFields(DataObjectTest\Team::class, false);
1091
        $expected = array(
1092
            'ID',
1093
            'ClassName',
1094
            'LastEdited',
1095
            'Created',
1096
            'Title',
1097
            'DatabaseField',
1098
            'ExtendedDatabaseField',
1099
            'CaptainID',
1100
            'FounderID',
1101
            'HasOneRelationshipID',
1102
            'ExtendedHasOneRelationshipID'
1103
        );
1104
        $actual = array_keys($teamFields);
1105
        sort($expected);
1106
        sort($actual);
1107
        $this->assertEquals(
1108
            $expected,
1109
            $actual,
1110
            'databaseFields() contains only fields defined on instance, including base, extended and foreign keys'
1111
        );
1112
1113
        $subteamSpecifications = $schema->fieldSpecs(DataObjectTest\SubTeam::class);
1114
        $expected = array(
1115
            'ID',
1116
            'ClassName',
1117
            'LastEdited',
1118
            'Created',
1119
            'Title',
1120
            'DatabaseField',
1121
            'ExtendedDatabaseField',
1122
            'CaptainID',
1123
            'FounderID',
1124
            'HasOneRelationshipID',
1125
            'ExtendedHasOneRelationshipID',
1126
            'SubclassDatabaseField',
1127
            'ParentTeamID',
1128
        );
1129
        $actual = array_keys($subteamSpecifications);
1130
        sort($expected);
1131
        sort($actual);
1132
        $this->assertEquals(
1133
            $expected,
1134
            $actual,
1135
            'fieldSpecifications() on subclass contains all fields, including base, extended  and foreign keys'
1136
        );
1137
1138
        $subteamFields = $schema->databaseFields(DataObjectTest\SubTeam::class, false);
1139
        $expected = array(
1140
            'ID',
1141
            'SubclassDatabaseField',
1142
            'ParentTeamID',
1143
        );
1144
        $actual = array_keys($subteamFields);
1145
        sort($expected);
1146
        sort($actual);
1147
        $this->assertEquals(
1148
            $expected,
1149
            $actual,
1150
            'databaseFields() on subclass contains only fields defined on instance'
1151
        );
1152
    }
1153
1154
    public function testSearchableFields()
1155
    {
1156
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
1157
        $fields = $player->searchableFields();
1158
        $this->assertArrayHasKey(
1159
            'IsRetired',
1160
            $fields,
1161
            'Fields defined by $searchable_fields static are correctly detected'
1162
        );
1163
        $this->assertArrayHasKey(
1164
            'ShirtNumber',
1165
            $fields,
1166
            'Fields defined by $searchable_fields static are correctly detected'
1167
        );
1168
1169
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1170
        $fields = $team->searchableFields();
1171
        $this->assertArrayHasKey(
1172
            'Title',
1173
            $fields,
1174
            'Fields can be inherited from the $summary_fields static, including methods called on fields'
1175
        );
1176
        $this->assertArrayHasKey(
1177
            'Captain.ShirtNumber',
1178
            $fields,
1179
            'Fields on related objects can be inherited from the $summary_fields static'
1180
        );
1181
        $this->assertArrayHasKey(
1182
            'Captain.FavouriteTeam.Title',
1183
            $fields,
1184
            'Fields on related objects can be inherited from the $summary_fields static'
1185
        );
1186
1187
        $testObj = new DataObjectTest\Fixture();
1188
        $fields = $testObj->searchableFields();
1189
        $this->assertEmpty($fields);
1190
    }
1191
1192
    public function testCastingHelper()
1193
    {
1194
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1195
1196
        $this->assertEquals('Varchar', $team->castingHelper('Title'), 'db field wasn\'t casted correctly');
1197
        $this->assertEquals('HTMLVarchar', $team->castingHelper('DatabaseField'), 'db field wasn\'t casted correctly');
1198
1199
        $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

1199
        $sponsor = $team->/** @scrutinizer ignore-call */ Sponsors()->first();
Loading history...
1200
        $this->assertEquals('Int', $sponsor->castingHelper('SponsorFee'), 'many_many_extraFields not casted correctly');
1201
    }
1202
1203
    public function testSummaryFieldsCustomLabels()
1204
    {
1205
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1206
        $summaryFields = $team->summaryFields();
1207
1208
        $this->assertEquals(
1209
            [
1210
                'Title' => 'Custom Title',
1211
                'Title.UpperCase' => 'Title',
1212
                'Captain.ShirtNumber' => 'Captain\'s shirt number',
1213
                'Captain.FavouriteTeam.Title' => 'Captain\'s favourite team',
1214
            ],
1215
            $summaryFields
1216
        );
1217
    }
1218
1219
    public function testDataObjectUpdate()
1220
    {
1221
        /* update() calls can use the dot syntax to reference has_one relations and other methods that return
1222
        * objects */
1223
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1224
        $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...
1225
1226
        $team1->update(
1227
            array(
1228
                'DatabaseField' => 'Something',
1229
                'Captain.FirstName' => 'Jim',
1230
                'Captain.Email' => '[email protected]',
1231
                'Captain.FavouriteTeam.Title' => 'New and improved team 1',
1232
            )
1233
        );
1234
1235
        /* Test the simple case of updating fields on the object itself */
1236
        $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...
1237
1238
        /* Setting Captain.Email and Captain.FirstName will have updated DataObjectTest_Captain.captain1 in
1239
        * the database.  Although update() doesn't usually write, it does write related records automatically. */
1240
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
1241
        $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...
1242
        $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...
1243
1244
        /* Jim's favourite team is team 1; we need to reload the object to the the change that setting Captain.
1245
        * FavouriteTeam.Title made */
1246
        $reloadedTeam1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1247
        $this->assertEquals('New and improved team 1', $reloadedTeam1->Title);
0 ignored issues
show
Bug Best Practice introduced by
The property Title does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1248
    }
1249
1250
    public function testDataObjectUpdateNew()
1251
    {
1252
        /* update() calls can use the dot syntax to reference has_one relations and other methods that return
1253
        * objects */
1254
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1255
        $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...
1256
1257
        $team1->update(
1258
            array(
1259
                'Captain.FirstName' => 'Jim',
1260
                'Captain.FavouriteTeam.Title' => 'New and improved team 1',
1261
            )
1262
        );
1263
        /* Test that the captain ID has been updated */
1264
        $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...
1265
1266
        /* Fetch the newly created captain */
1267
        $captain1 = DataObjectTest\Player::get()->byID($team1->CaptainID);
1268
        $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...
1269
1270
        /* Grab the favourite team and make sure it has the correct values */
1271
        $reloadedTeam1 = $captain1->FavouriteTeam();
1272
        $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...
1273
        $this->assertEquals('New and improved team 1', $reloadedTeam1->Title);
1274
    }
1275
1276
1277
    /**
1278
     * @expectedException \SilverStripe\ORM\ValidationException
1279
     */
1280
    public function testWritingInvalidDataObjectThrowsException()
1281
    {
1282
        $validatedObject = new DataObjectTest\ValidatedObject();
1283
        $validatedObject->write();
1284
    }
1285
1286
    public function testWritingValidDataObjectDoesntThrowException()
1287
    {
1288
        $validatedObject = new DataObjectTest\ValidatedObject();
1289
        $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...
1290
1291
        $validatedObject->write();
1292
        $this->assertTrue($validatedObject->isInDB(), "Validated object was not saved to database");
1293
    }
1294
1295
    public function testSubclassCreation()
1296
    {
1297
        /* Creating a new object of a subclass should set the ClassName field correctly */
1298
        $obj = new DataObjectTest\SubTeam();
1299
        $obj->write();
1300
        $this->assertEquals(
1301
            DataObjectTest\SubTeam::class,
1302
            DB::query("SELECT \"ClassName\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $obj->ID")->value()
1303
        );
1304
    }
1305
1306
    public function testForceInsert()
1307
    {
1308
        /* If you set an ID on an object and pass forceInsert = true, then the object should be correctly created */
1309
        $conn = DB::get_conn();
1310
        if (method_exists($conn, 'allowPrimaryKeyEditing')) {
1311
            $conn->allowPrimaryKeyEditing(DataObjectTest\Team::class, true);
1312
        }
1313
        $obj = new DataObjectTest\SubTeam();
1314
        $obj->ID = 1001;
1315
        $obj->Title = 'asdfasdf';
1316
        $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...
1317
        $obj->write(false, true);
1318
        if (method_exists($conn, 'allowPrimaryKeyEditing')) {
1319
            $conn->allowPrimaryKeyEditing(DataObjectTest\Team::class, false);
1320
        }
1321
1322
        $this->assertEquals(
1323
            DataObjectTest\SubTeam::class,
1324
            DB::query("SELECT \"ClassName\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $obj->ID")->value()
1325
        );
1326
1327
        /* Check that it actually saves to the database with the correct ID */
1328
        $this->assertEquals(
1329
            "1001",
1330
            DB::query(
1331
                "SELECT \"ID\" FROM \"DataObjectTest_SubTeam\" WHERE \"SubclassDatabaseField\" = 'asdfasdf'"
1332
            )->value()
1333
        );
1334
        $this->assertEquals(
1335
            "1001",
1336
            DB::query("SELECT \"ID\" FROM \"DataObjectTest_Team\" WHERE \"Title\" = 'asdfasdf'")->value()
1337
        );
1338
    }
1339
1340
    public function testHasOwnTable()
1341
    {
1342
        $schema = DataObject::getSchema();
1343
        /* Test DataObject::has_own_table() returns true if the object has $has_one or $db values */
1344
        $this->assertTrue($schema->classHasTable(DataObjectTest\Player::class));
1345
        $this->assertTrue($schema->classHasTable(DataObjectTest\Team::class));
1346
        $this->assertTrue($schema->classHasTable(DataObjectTest\Fixture::class));
1347
1348
        /* Root DataObject that always have a table, even if they lack both $db and $has_one */
1349
        $this->assertTrue($schema->classHasTable(DataObjectTest\FieldlessTable::class));
1350
1351
        /* Subclasses without $db or $has_one don't have a table */
1352
        $this->assertFalse($schema->classHasTable(DataObjectTest\FieldlessSubTable::class));
1353
1354
        /* Return false if you don't pass it a subclass of DataObject */
1355
        $this->assertFalse($schema->classHasTable(DataObject::class));
1356
        $this->assertFalse($schema->classHasTable(ViewableData::class));
1357
1358
        /* Invalid class name */
1359
        $this->assertFalse($schema->classHasTable("ThisIsntADataObject"));
1360
    }
1361
1362
    public function testMerge()
1363
    {
1364
        // test right merge of subclasses
1365
        $left = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
1366
        $right = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam2_with_player_relation');
1367
        $leftOrigID = $left->ID;
1368
        $left->merge($right, 'right', false, false);
1369
        $this->assertEquals(
1370
            $left->Title,
0 ignored issues
show
Bug Best Practice introduced by
The property Title does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1371
            'Subteam 2',
1372
            'merge() with "right" priority overwrites fields with existing values on subclasses'
1373
        );
1374
        $this->assertEquals(
1375
            $left->ID,
1376
            $leftOrigID,
1377
            'merge() with "right" priority doesnt overwrite database ID'
1378
        );
1379
1380
        // test overwriteWithEmpty flag on existing left values
1381
        $left = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam2_with_player_relation');
1382
        $right = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam3_with_empty_fields');
1383
        $left->merge($right, 'right', false, true);
1384
        $this->assertEquals(
1385
            $left->Title,
1386
            'Subteam 3',
1387
            'merge() with $overwriteWithEmpty overwrites non-empty fields on left object'
1388
        );
1389
1390
        // test overwriteWithEmpty flag on empty left values
1391
        $left = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
1392
        // $SubclassDatabaseField is empty on here
1393
        $right = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam2_with_player_relation');
1394
        $left->merge($right, 'right', false, true);
1395
        $this->assertEquals(
1396
            $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...
1397
            null,
1398
            'merge() with $overwriteWithEmpty overwrites empty fields on left object'
1399
        );
1400
1401
        // @todo test "left" priority flag
1402
        // @todo test includeRelations flag
1403
        // @todo test includeRelations in combination with overwriteWithEmpty
1404
        // @todo test has_one relations
1405
        // @todo test has_many and many_many relations
1406
    }
1407
1408
    public function testPopulateDefaults()
1409
    {
1410
        $obj = new DataObjectTest\Fixture();
1411
        $this->assertEquals(
1412
            $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...
1413
            'Default Value',
1414
            'Defaults are populated for in-memory object from $defaults array'
1415
        );
1416
1417
        $this->assertEquals(
1418
            $obj->MyFieldWithAltDefault,
1419
            'Default Value',
1420
            'Defaults are populated from overloaded populateDefaults() method'
1421
        );
1422
    }
1423
1424
    /**
1425
     * @expectedException \InvalidArgumentException
1426
     */
1427
    public function testValidateModelDefinitionsFailsWithArray()
1428
    {
1429
        Config::modify()->merge(DataObjectTest\Team::class, 'has_one', array('NotValid' => array('NoArraysAllowed')));
1430
        DataObject::getSchema()->hasOneComponent(DataObjectTest\Team::class, 'NotValid');
1431
    }
1432
1433
    /**
1434
     * @expectedException \InvalidArgumentException
1435
     */
1436
    public function testValidateModelDefinitionsFailsWithIntKey()
1437
    {
1438
        Config::modify()->set(DataObjectTest\Team::class, 'has_many', array(0 => DataObjectTest\Player::class));
1439
        DataObject::getSchema()->hasManyComponent(DataObjectTest\Team::class, 0);
1440
    }
1441
1442
    /**
1443
     * @expectedException \InvalidArgumentException
1444
     */
1445
    public function testValidateModelDefinitionsFailsWithIntValue()
1446
    {
1447
        Config::modify()->merge(DataObjectTest\Team::class, 'many_many', array('Players' => 12));
1448
        DataObject::getSchema()->manyManyComponent(DataObjectTest\Team::class, 'Players');
1449
    }
1450
1451
    public function testNewClassInstance()
1452
    {
1453
        $dataObject = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1454
        $changedDO = $dataObject->newClassInstance(DataObjectTest\SubTeam::class);
1455
        $changedFields = $changedDO->getChangedFields();
1456
1457
        // Don't write the record, it will reset changed fields
1458
        $this->assertInstanceOf(DataObjectTest\SubTeam::class, $changedDO);
1459
        $this->assertEquals($changedDO->ClassName, DataObjectTest\SubTeam::class);
1460
        $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...
1461
        $this->assertContains('ClassName', array_keys($changedFields));
1462
        $this->assertEquals($changedFields['ClassName']['before'], DataObjectTest\Team::class);
1463
        $this->assertEquals($changedFields['ClassName']['after'], DataObjectTest\SubTeam::class);
1464
        $this->assertEquals($changedFields['RecordClassName']['before'], DataObjectTest\Team::class);
1465
        $this->assertEquals($changedFields['RecordClassName']['after'], DataObjectTest\SubTeam::class);
1466
1467
        $changedDO->write();
1468
1469
        $this->assertInstanceOf(DataObjectTest\SubTeam::class, $changedDO);
1470
        $this->assertEquals($changedDO->ClassName, DataObjectTest\SubTeam::class);
1471
1472
        // Test invalid classes fail
1473
        $this->expectException(InvalidArgumentException::class);
1474
        $this->expectExceptionMessage('Controller is not a valid subclass of DataObject');
1475
        /**
1476
         * @skipUpgrade
1477
         */
1478
        $dataObject->newClassInstance('Controller');
1479
    }
1480
1481
    public function testMultipleManyManyWithSameClass()
1482
    {
1483
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1484
        $company2 = $this->objFromFixture(DataObjectTest\EquipmentCompany::class, 'equipmentcompany2');
1485
        $sponsors = $team->Sponsors();
1486
        $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

1486
        /** @scrutinizer ignore-call */ 
1487
        $equipmentSuppliers = $team->EquipmentSuppliers();
Loading history...
1487
1488
        // Check that DataObject::many_many() works as expected
1489
        $manyManyComponent = DataObject::getSchema()->manyManyComponent(DataObjectTest\Team::class, 'Sponsors');
1490
        $this->assertEquals(ManyManyList::class, $manyManyComponent['relationClass']);
1491
        $this->assertEquals(
1492
            DataObjectTest\Team::class,
1493
            $manyManyComponent['parentClass'],
1494
            'DataObject::many_many() didn\'t find the correct base class'
1495
        );
1496
        $this->assertEquals(
1497
            DataObjectTest\EquipmentCompany::class,
1498
            $manyManyComponent['childClass'],
1499
            'DataObject::many_many() didn\'t find the correct target class for the relation'
1500
        );
1501
        $this->assertEquals(
1502
            'DataObjectTest_EquipmentCompany_SponsoredTeams',
1503
            $manyManyComponent['join'],
1504
            'DataObject::many_many() didn\'t find the correct relation table'
1505
        );
1506
        $this->assertEquals('DataObjectTest_TeamID', $manyManyComponent['parentField']);
1507
        $this->assertEquals('DataObjectTest_EquipmentCompanyID', $manyManyComponent['childField']);
1508
1509
        // Check that ManyManyList still works
1510
        $this->assertEquals(2, $sponsors->count(), 'Rows are missing from relation');
1511
        $this->assertEquals(1, $equipmentSuppliers->count(), 'Rows are missing from relation');
1512
1513
        // Check everything works when no relation is present
1514
        $teamWithoutSponsor = $this->objFromFixture(DataObjectTest\Team::class, 'team3');
1515
        $this->assertInstanceOf(ManyManyList::class, $teamWithoutSponsor->Sponsors());
1516
        $this->assertEquals(0, $teamWithoutSponsor->Sponsors()->count());
1517
1518
        // Test that belongs_many_many can be infered from with getNonReciprocalComponent
1519
        $this->assertListEquals(
1520
            [
1521
                ['Name' => 'Company corp'],
1522
                ['Name' => 'Team co.'],
1523
            ],
1524
            $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

1524
            /** @scrutinizer ignore-type */ $team->inferReciprocalComponent(DataObjectTest\EquipmentCompany::class, 'SponsoredTeams')
Loading history...
1525
        );
1526
1527
        // Test that many_many can be infered from getNonReciprocalComponent
1528
        $this->assertListEquals(
1529
            [
1530
                ['Title' => 'Team 1'],
1531
                ['Title' => 'Team 2'],
1532
                ['Title' => 'Subteam 1'],
1533
            ],
1534
            $company2->inferReciprocalComponent(DataObjectTest\Team::class, 'Sponsors')
1535
        );
1536
1537
        // Check many_many_extraFields still works
1538
        $equipmentCompany = $this->objFromFixture(DataObjectTest\EquipmentCompany::class, 'equipmentcompany1');
1539
        $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

1539
        $equipmentCompany->/** @scrutinizer ignore-call */ 
1540
                           SponsoredTeams()->add($teamWithoutSponsor, array('SponsorFee' => 1000));
Loading history...
1540
        $sponsoredTeams = $equipmentCompany->SponsoredTeams();
1541
        $this->assertEquals(
1542
            1000,
1543
            $sponsoredTeams->byID($teamWithoutSponsor->ID)->SponsorFee,
1544
            'Data from many_many_extraFields was not stored/extracted correctly'
1545
        );
1546
1547
        // Check subclasses correctly inherit multiple many_manys
1548
        $subTeam = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
1549
        $this->assertEquals(
1550
            2,
1551
            $subTeam->Sponsors()->count(),
1552
            'Child class did not inherit multiple many_manys'
1553
        );
1554
        $this->assertEquals(
1555
            1,
1556
            $subTeam->EquipmentSuppliers()->count(),
1557
            'Child class did not inherit multiple many_manys'
1558
        );
1559
        // Team 2 has one EquipmentCompany sponsor and one SubEquipmentCompany
1560
        $team2 = $this->objFromFixture(DataObjectTest\Team::class, 'team2');
1561
        $this->assertEquals(
1562
            2,
1563
            $team2->Sponsors()->count(),
1564
            'Child class did not inherit multiple belongs_many_manys'
1565
        );
1566
1567
        // Check many_many_extraFields also works from the belongs_many_many side
1568
        $sponsors = $team2->Sponsors();
1569
        $sponsors->add($equipmentCompany, array('SponsorFee' => 750));
1570
        $this->assertEquals(
1571
            750,
1572
            $sponsors->byID($equipmentCompany->ID)->SponsorFee,
1573
            'Data from many_many_extraFields was not stored/extracted correctly'
1574
        );
1575
1576
        $subEquipmentCompany = $this->objFromFixture(DataObjectTest\SubEquipmentCompany::class, 'subequipmentcompany1');
1577
        $subTeam->Sponsors()->add($subEquipmentCompany, array('SponsorFee' => 1200));
1578
        $this->assertEquals(
1579
            1200,
1580
            $subTeam->Sponsors()->byID($subEquipmentCompany->ID)->SponsorFee,
1581
            'Data from inherited many_many_extraFields was not stored/extracted correctly'
1582
        );
1583
    }
1584
1585
    public function testManyManyExtraFields()
1586
    {
1587
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1588
        $schema = DataObject::getSchema();
1589
1590
        // Get all extra fields
1591
        $teamExtraFields = $team->manyManyExtraFields();
1592
        $this->assertEquals(
1593
            array(
1594
                'Players' => array('Position' => 'Varchar(100)')
1595
            ),
1596
            $teamExtraFields
1597
        );
1598
1599
        // Ensure fields from parent classes are included
1600
        $subTeam = singleton(DataObjectTest\SubTeam::class);
1601
        $teamExtraFields = $subTeam->manyManyExtraFields();
1602
        $this->assertEquals(
1603
            array(
1604
                'Players' => array('Position' => 'Varchar(100)'),
1605
                'FormerPlayers' => array('Position' => 'Varchar(100)')
1606
            ),
1607
            $teamExtraFields
1608
        );
1609
1610
        // Extra fields are immediately available on the Team class (defined in $many_many_extraFields)
1611
        $teamExtraFields = $schema->manyManyExtraFieldsForComponent(DataObjectTest\Team::class, 'Players');
1612
        $this->assertEquals(
1613
            $teamExtraFields,
1614
            array(
1615
                'Position' => 'Varchar(100)'
1616
            )
1617
        );
1618
1619
        // We'll have to go through the relation to get the extra fields on Player
1620
        $playerExtraFields = $schema->manyManyExtraFieldsForComponent(DataObjectTest\Player::class, 'Teams');
1621
        $this->assertEquals(
1622
            $playerExtraFields,
1623
            array(
1624
                'Position' => 'Varchar(100)'
1625
            )
1626
        );
1627
1628
        // Iterate through a many-many relationship and confirm that extra fields are included
1629
        $newTeam = new DataObjectTest\Team();
1630
        $newTeam->Title = "New team";
1631
        $newTeam->write();
1632
        $newTeamID = $newTeam->ID;
1633
1634
        $newPlayer = new DataObjectTest\Player();
1635
        $newPlayer->FirstName = "Sam";
1636
        $newPlayer->Surname = "Minnee";
1637
        $newPlayer->write();
1638
1639
        // The idea of Sam as a prop is essentially humourous.
1640
        $newTeam->Players()->add($newPlayer, array("Position" => "Prop"));
1641
1642
        // Requery and uncache everything
1643
        $newTeam->flushCache();
1644
        $newTeam = DataObject::get_by_id(DataObjectTest\Team::class, $newTeamID);
1645
1646
        // Check that the Position many_many_extraField is extracted.
1647
        $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

1647
        $player = $newTeam->/** @scrutinizer ignore-call */ Players()->first();
Loading history...
1648
        $this->assertEquals('Sam', $player->FirstName);
1649
        $this->assertEquals("Prop", $player->Position);
1650
1651
        // Check that ordering a many-many relation by an aggregate column doesn't fail
1652
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
1653
        $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

1653
        $player->/** @scrutinizer ignore-call */ 
1654
                 Teams()->sort("count(DISTINCT \"DataObjectTest_Team_Players\".\"DataObjectTest_PlayerID\") DESC");
Loading history...
1654
    }
1655
1656
    /**
1657
     * Check that the queries generated for many-many relation queries can have unlimitedRowCount
1658
     * called on them.
1659
     */
1660
    public function testManyManyUnlimitedRowCount()
1661
    {
1662
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
1663
        // TODO: What's going on here?
1664
        $this->assertEquals(2, $player->Teams()->dataQuery()->query()->unlimitedRowCount());
1665
    }
1666
1667
    /**
1668
     * Tests that singular_name() generates sensible defaults.
1669
     */
1670
    public function testSingularName()
1671
    {
1672
        $assertions = array(
1673
            DataObjectTest\Player::class => 'Player',
1674
            DataObjectTest\Team::class => 'Team',
1675
            DataObjectTest\Fixture::class => 'Fixture',
1676
        );
1677
1678
        foreach ($assertions as $class => $expectedSingularName) {
1679
            $this->assertEquals(
1680
                $expectedSingularName,
1681
                singleton($class)->singular_name(),
1682
                "Assert that the singular_name for '$class' is correct."
1683
            );
1684
        }
1685
    }
1686
1687
    /**
1688
     * Tests that plural_name() generates sensible defaults.
1689
     */
1690
    public function testPluralName()
1691
    {
1692
        $assertions = array(
1693
            DataObjectTest\Player::class => 'Players',
1694
            DataObjectTest\Team::class => 'Teams',
1695
            DataObjectTest\Fixture::class => 'Fixtures',
1696
            DataObjectTest\Play::class => 'Plays',
1697
            DataObjectTest\Bogey::class => 'Bogeys',
1698
            DataObjectTest\Ploy::class => 'Ploys',
1699
        );
1700
        i18n::set_locale('en_NZ');
1701
        foreach ($assertions as $class => $expectedPluralName) {
1702
            $this->assertEquals(
1703
                $expectedPluralName,
1704
                DataObject::singleton($class)->plural_name(),
1705
                "Assert that the plural_name for '$class' is correct."
1706
            );
1707
            $this->assertEquals(
1708
                $expectedPluralName,
1709
                DataObject::singleton($class)->i18n_plural_name(),
1710
                "Assert that the i18n_plural_name for '$class' is correct."
1711
            );
1712
        }
1713
    }
1714
1715
    public function testHasDatabaseField()
1716
    {
1717
        $team = singleton(DataObjectTest\Team::class);
1718
        $subteam = singleton(DataObjectTest\SubTeam::class);
1719
1720
        $this->assertTrue(
1721
            $team->hasDatabaseField('Title'),
1722
            "hasOwnDatabaseField() works with \$db fields"
1723
        );
1724
        $this->assertTrue(
1725
            $team->hasDatabaseField('CaptainID'),
1726
            "hasOwnDatabaseField() works with \$has_one fields"
1727
        );
1728
        $this->assertFalse(
1729
            $team->hasDatabaseField('NonExistentField'),
1730
            "hasOwnDatabaseField() doesn't detect non-existend fields"
1731
        );
1732
        $this->assertTrue(
1733
            $team->hasDatabaseField('ExtendedDatabaseField'),
1734
            "hasOwnDatabaseField() works with extended fields"
1735
        );
1736
        $this->assertFalse(
1737
            $team->hasDatabaseField('SubclassDatabaseField'),
1738
            "hasOwnDatabaseField() doesn't pick up fields in subclasses on parent class"
1739
        );
1740
1741
        $this->assertTrue(
1742
            $subteam->hasDatabaseField('SubclassDatabaseField'),
1743
            "hasOwnDatabaseField() picks up fields in subclasses"
1744
        );
1745
    }
1746
1747
    public function testFieldTypes()
1748
    {
1749
        $obj = new DataObjectTest\Fixture();
1750
        $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...
1751
        $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...
1752
        $obj->write();
1753
        $obj->flushCache();
1754
1755
        $obj = DataObject::get_by_id(DataObjectTest\Fixture::class, $obj->ID);
1756
        $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...
1757
        $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...
1758
    }
1759
1760
    public function testTwoSubclassesWithTheSameFieldNameWork()
1761
    {
1762
        // Create two objects of different subclasses, setting the values of fields that are
1763
        // defined separately in each subclass
1764
        $obj1 = new DataObjectTest\SubTeam();
1765
        $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...
1766
        $obj2 = new DataObjectTest\OtherSubclassWithSameField();
1767
        $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...
1768
1769
        // Write them to the database
1770
        $obj1->write();
1771
        $obj2->write();
1772
1773
        // Check that the values of those fields are properly read from the database
1774
        $values = DataObject::get(
1775
            DataObjectTest\Team::class,
1776
            "\"DataObjectTest_Team\".\"ID\" IN
1777
			($obj1->ID, $obj2->ID)"
1778
        )->column("SubclassDatabaseField");
1779
        $this->assertEquals(array_intersect($values, array('obj1', 'obj2')), $values);
1780
    }
1781
1782
    public function testClassNameSetForNewObjects()
1783
    {
1784
        $d = new DataObjectTest\Player();
1785
        $this->assertEquals(DataObjectTest\Player::class, $d->ClassName);
1786
    }
1787
1788
    public function testHasValue()
1789
    {
1790
        $team = new DataObjectTest\Team();
1791
        $this->assertFalse($team->hasValue('Title', null, false));
1792
        $this->assertFalse($team->hasValue('DatabaseField', null, false));
1793
1794
        $team->Title = 'hasValue';
1795
        $this->assertTrue($team->hasValue('Title', null, false));
1796
        $this->assertFalse($team->hasValue('DatabaseField', null, false));
1797
1798
        $team->Title = '<p></p>';
1799
        $this->assertTrue(
1800
            $team->hasValue('Title', null, false),
1801
            'Test that an empty paragraph is a value for non-HTML fields.'
1802
        );
1803
1804
        $team->DatabaseField = 'hasValue';
1805
        $this->assertTrue($team->hasValue('Title', null, false));
1806
        $this->assertTrue($team->hasValue('DatabaseField', null, false));
1807
    }
1808
1809
    public function testHasMany()
1810
    {
1811
        $company = new DataObjectTest\Company();
1812
1813
        $this->assertEquals(
1814
            array(
1815
                'CurrentStaff' => DataObjectTest\Staff::class,
1816
                'PreviousStaff' => DataObjectTest\Staff::class
1817
            ),
1818
            $company->hasMany(),
1819
            'has_many strips field name data by default.'
1820
        );
1821
1822
        $this->assertEquals(
1823
            DataObjectTest\Staff::class,
1824
            DataObject::getSchema()->hasManyComponent(DataObjectTest\Company::class, 'CurrentStaff'),
1825
            'has_many strips field name data by default on single relationships.'
1826
        );
1827
1828
        $this->assertEquals(
1829
            array(
1830
                'CurrentStaff' => DataObjectTest\Staff::class . '.CurrentCompany',
1831
                'PreviousStaff' => DataObjectTest\Staff::class . '.PreviousCompany'
1832
            ),
1833
            $company->hasMany(false),
1834
            'has_many returns field name data when $classOnly is false.'
1835
        );
1836
1837
        $this->assertEquals(
1838
            DataObjectTest\Staff::class . '.CurrentCompany',
1839
            DataObject::getSchema()->hasManyComponent(DataObjectTest\Company::class, 'CurrentStaff', false),
1840
            'has_many returns field name data on single records when $classOnly is false.'
1841
        );
1842
    }
1843
1844
    public function testGetRemoteJoinField()
1845
    {
1846
        $schema = DataObject::getSchema();
1847
1848
        // Company schema
1849
        $staffJoinField = $schema->getRemoteJoinField(
1850
            DataObjectTest\Company::class,
1851
            'CurrentStaff',
1852
            'has_many',
1853
            $polymorphic
1854
        );
1855
        $this->assertEquals('CurrentCompanyID', $staffJoinField);
1856
        $this->assertFalse($polymorphic, 'DataObjectTest_Company->CurrentStaff is not polymorphic');
1857
        $previousStaffJoinField = $schema->getRemoteJoinField(
1858
            DataObjectTest\Company::class,
1859
            'PreviousStaff',
1860
            'has_many',
1861
            $polymorphic
1862
        );
1863
        $this->assertEquals('PreviousCompanyID', $previousStaffJoinField);
1864
        $this->assertFalse($polymorphic, 'DataObjectTest_Company->PreviousStaff is not polymorphic');
1865
1866
        // CEO Schema
1867
        $this->assertEquals(
1868
            'CEOID',
1869
            $schema->getRemoteJoinField(
1870
                DataObjectTest\CEO::class,
1871
                'Company',
1872
                'belongs_to',
1873
                $polymorphic
1874
            )
1875
        );
1876
        $this->assertFalse($polymorphic, 'DataObjectTest_CEO->Company is not polymorphic');
1877
        $this->assertEquals(
1878
            'PreviousCEOID',
1879
            $schema->getRemoteJoinField(
1880
                DataObjectTest\CEO::class,
1881
                'PreviousCompany',
1882
                'belongs_to',
1883
                $polymorphic
1884
            )
1885
        );
1886
        $this->assertFalse($polymorphic, 'DataObjectTest_CEO->PreviousCompany is not polymorphic');
1887
1888
        // Team schema
1889
        $this->assertEquals(
1890
            'Favourite',
1891
            $schema->getRemoteJoinField(
1892
                DataObjectTest\Team::class,
1893
                'Fans',
1894
                'has_many',
1895
                $polymorphic
1896
            )
1897
        );
1898
        $this->assertTrue($polymorphic, 'DataObjectTest_Team->Fans is polymorphic');
1899
        $this->assertEquals(
1900
            'TeamID',
1901
            $schema->getRemoteJoinField(
1902
                DataObjectTest\Team::class,
1903
                'Comments',
1904
                'has_many',
1905
                $polymorphic
1906
            )
1907
        );
1908
        $this->assertFalse($polymorphic, 'DataObjectTest_Team->Comments is not polymorphic');
1909
    }
1910
1911
    public function testBelongsTo()
1912
    {
1913
        $company = new DataObjectTest\Company();
1914
        $ceo = new DataObjectTest\CEO();
1915
1916
        $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...
1917
        $company->write();
1918
        $ceo->write();
1919
1920
        // Test belongs_to assignment
1921
        $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...
1922
        $company->write();
1923
1924
        $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

1924
        $this->assertEquals($company->ID, $ceo->/** @scrutinizer ignore-call */ Company()->ID, 'belongs_to returns the right results.');
Loading history...
1925
1926
        // Test belongs_to can be infered via getNonReciprocalComponent
1927
        // Note: Will be returned as has_many since the belongs_to is ignored.
1928
        $this->assertListEquals(
1929
            [['Name' => 'New Company']],
1930
            $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

1930
            /** @scrutinizer ignore-type */ $ceo->inferReciprocalComponent(DataObjectTest\Company::class, 'CEO')
Loading history...
1931
        );
1932
1933
        // Test has_one to a belongs_to can be infered via getNonReciprocalComponent
1934
        $this->assertEquals(
1935
            $ceo->ID,
1936
            $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...
1937
        );
1938
1939
        // Test automatic creation of class where no assigment exists
1940
        $ceo = new DataObjectTest\CEO();
1941
        $ceo->write();
1942
1943
        $this->assertTrue(
1944
            $ceo->Company() instanceof DataObjectTest\Company,
1945
            'DataObjects across belongs_to relations are automatically created.'
1946
        );
1947
        $this->assertEquals($ceo->ID, $ceo->Company()->CEOID, 'Remote IDs are automatically set.');
1948
1949
        // Write object with components
1950
        $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...
1951
        $ceo->write(false, false, false, true);
1952
        $this->assertTrue($ceo->Company()->isInDB(), 'write() writes belongs_to components to the database.');
1953
1954
        $newCEO = DataObject::get_by_id(DataObjectTest\CEO::class, $ceo->ID);
1955
        $this->assertEquals(
1956
            $ceo->Company()->ID,
1957
            $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

1957
            $newCEO->/** @scrutinizer ignore-call */ 
1958
                     Company()->ID,
Loading history...
1958
            'belongs_to can be retrieved from the database.'
1959
        );
1960
    }
1961
1962
    public function testBelongsToPolymorphic()
1963
    {
1964
        $company = new DataObjectTest\Company();
1965
        $ceo = new DataObjectTest\CEO();
1966
1967
        $company->write();
1968
        $ceo->write();
1969
1970
        // Test belongs_to assignment
1971
        $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...
1972
        $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...
1973
        $company->write();
1974
1975
        $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

1975
        $this->assertEquals($company->ID, $ceo->/** @scrutinizer ignore-call */ CompanyOwned()->ID, 'belongs_to returns the right results.');
Loading history...
1976
        $this->assertInstanceOf(
1977
            DataObjectTest\Company::class,
1978
            $ceo->CompanyOwned(),
1979
            'belongs_to returns the right results.'
1980
        );
1981
1982
        // Test automatic creation of class where no assigment exists
1983
        $ceo = new DataObjectTest\CEO();
1984
        $ceo->write();
1985
1986
        $this->assertTrue(
1987
            $ceo->CompanyOwned() instanceof DataObjectTest\Company,
1988
            'DataObjects across polymorphic belongs_to relations are automatically created.'
1989
        );
1990
        $this->assertEquals($ceo->ID, $ceo->CompanyOwned()->OwnerID, 'Remote IDs are automatically set.');
1991
        $this->assertInstanceOf($ceo->CompanyOwned()->OwnerClass, $ceo, 'Remote class is automatically  set');
1992
1993
        // Write object with components
1994
        $ceo->write(false, false, false, true);
1995
        $this->assertTrue($ceo->CompanyOwned()->isInDB(), 'write() writes belongs_to components to the database.');
1996
1997
        $newCEO = DataObject::get_by_id(DataObjectTest\CEO::class, $ceo->ID);
1998
        $this->assertEquals(
1999
            $ceo->CompanyOwned()->ID,
2000
            $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

2000
            $newCEO->/** @scrutinizer ignore-call */ 
2001
                     CompanyOwned()->ID,
Loading history...
2001
            'polymorphic belongs_to can be retrieved from the database.'
2002
        );
2003
    }
2004
2005
    /**
2006
     * @expectedException \LogicException
2007
     */
2008
    public function testInvalidate()
2009
    {
2010
        $do = new DataObjectTest\Fixture();
2011
        $do->write();
2012
2013
        $do->delete();
2014
2015
        $do->delete(); // Prohibit invalid object manipulation
2016
        $do->write();
2017
        $do->duplicate();
2018
    }
2019
2020
    public function testToMap()
2021
    {
2022
        $obj = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
2023
2024
        $map = $obj->toMap();
2025
2026
        $this->assertArrayHasKey('ID', $map, 'Contains base fields');
2027
        $this->assertArrayHasKey('Title', $map, 'Contains fields from parent class');
2028
        $this->assertArrayHasKey('SubclassDatabaseField', $map, 'Contains fields from concrete class');
2029
2030
        $this->assertEquals(
2031
            $obj->ID,
2032
            $map['ID'],
2033
            'Contains values from base fields'
2034
        );
2035
        $this->assertEquals(
2036
            $obj->Title,
0 ignored issues
show
Bug Best Practice introduced by
The property Title does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
2037
            $map['Title'],
2038
            'Contains values from parent class fields'
2039
        );
2040
        $this->assertEquals(
2041
            $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...
2042
            $map['SubclassDatabaseField'],
2043
            'Contains values from concrete class fields'
2044
        );
2045
2046
        $newObj = new DataObjectTest\SubTeam();
0 ignored issues
show
Unused Code introduced by
The assignment to $newObj is dead and can be removed.
Loading history...
2047
        $this->assertArrayHasKey('Title', $map, 'Contains null fields');
2048
    }
2049
2050
    public function testIsEmpty()
2051
    {
2052
        $objEmpty = new DataObjectTest\Team();
2053
        $this->assertTrue($objEmpty->isEmpty(), 'New instance without populated defaults is empty');
2054
2055
        $objEmpty->Title = '0'; //
2056
        $this->assertFalse($objEmpty->isEmpty(), 'Zero value in attribute considered non-empty');
2057
    }
2058
2059
    public function testRelField()
2060
    {
2061
        $captain = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
2062
        // Test traversal of a single has_one
2063
        $this->assertEquals("Team 1", $captain->relField('FavouriteTeam.Title'));
2064
        // Test direct field access
2065
        $this->assertEquals("Captain", $captain->relField('FirstName'));
2066
2067
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
2068
        // Test that we can traverse more than once, and that arbitrary methods are okay
2069
        $this->assertEquals("Team 1", $player->relField('Teams.First.Title'));
2070
2071
        $newPlayer = new DataObjectTest\Player();
2072
        $this->assertNull($newPlayer->relField('Teams.First.Title'));
2073
2074
        // Test that relField works on db field manipulations
2075
        $comment = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment3');
2076
        $this->assertEquals("PHIL IS A UNIQUE GUY, AND COMMENTS ON TEAM2", $comment->relField('Comment.UpperCase'));
2077
    }
2078
2079
    public function testRelObject()
2080
    {
2081
        $captain = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
2082
2083
        // Test traversal of a single has_one
2084
        $this->assertInstanceOf(DBVarchar::class, $captain->relObject('FavouriteTeam.Title'));
2085
        $this->assertEquals("Team 1", $captain->relObject('FavouriteTeam.Title')->getValue());
2086
2087
        // Test direct field access
2088
        $this->assertInstanceOf(DBBoolean::class, $captain->relObject('IsRetired'));
2089
        $this->assertEquals(1, $captain->relObject('IsRetired')->getValue());
2090
2091
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
2092
        // Test that we can traverse more than once, and that arbitrary methods are okay
2093
        $this->assertInstanceOf(DBVarchar::class, $player->relObject('Teams.First.Title'));
2094
        $this->assertEquals("Team 1", $player->relObject('Teams.First.Title')->getValue());
2095
    }
2096
2097
    public function testLateStaticBindingStyle()
2098
    {
2099
        // Confirm that DataObjectTest_Player::get() operates as excepted
2100
        $this->assertEquals(4, DataObjectTest\Player::get()->count());
2101
        $this->assertInstanceOf(DataObjectTest\Player::class, DataObjectTest\Player::get()->first());
2102
2103
        // You can't pass arguments to LSB syntax - use the DataList methods instead.
2104
        $this->expectException(InvalidArgumentException::class);
2105
2106
        DataObjectTest\Player::get(null, "\"ID\" = 1");
2107
    }
2108
2109
    /**
2110
     * @expectedException \InvalidArgumentException
2111
     */
2112
    public function testBrokenLateStaticBindingStyle()
2113
    {
2114
        // If you call DataObject::get() you have to pass a first argument
2115
        DataObject::get();
2116
    }
2117
2118
    public function testBigIntField()
2119
    {
2120
        $staff = new DataObjectTest\Staff();
2121
        $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...
2122
        $staff->write();
2123
        $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...
2124
    }
2125
2126
    public function testGetOneMissingValueReturnsNull()
2127
    {
2128
2129
        // Test that missing values return null
2130
        $this->assertEquals(null, DataObject::get_one(
2131
            DataObjectTest\TeamComment::class,
2132
            ['"DataObjectTest_TeamComment"."Name"' => 'does not exists']
2133
        ));
2134
    }
2135
2136
    public function testSetFieldWithArrayOnScalarOnlyField()
2137
    {
2138
        $this->expectException(InvalidArgumentException::class);
2139
        $do = Company::singleton();
2140
        $do->FoundationYear = '1984';
2141
        $do->FoundationYear = array('Amount' => 123, 'Currency' => 'CAD');
2142
        $this->assertEmpty($do->FoundationYear);
2143
    }
2144
2145
    public function testSetFieldWithArrayOnCompositeField()
2146
    {
2147
        $do = Company::singleton();
2148
        $do->SalaryCap = array('Amount' => 123456, 'Currency' => 'CAD');
2149
        $this->assertNotEmpty($do->SalaryCap);
2150
    }
2151
2152
    public function testWriteManipulationWithNonScalarValuesAllowed()
2153
    {
2154
        $do = DataObjectTest\MockDynamicAssignmentDataObject::create();
2155
        $do->write();
2156
2157
        $do->StaticScalarOnlyField = true;
2158
        $do->DynamicScalarOnlyField = false;
2159
        $do->DynamicField = true;
2160
2161
        $do->write();
2162
2163
        $this->assertTrue($do->StaticScalarOnlyField);
2164
        $this->assertFalse($do->DynamicScalarOnlyField);
2165
        $this->assertTrue($do->DynamicField);
2166
    }
2167
2168
    public function testWriteManipulationWithNonScalarValuesDisallowed()
2169
    {
2170
        $this->expectException(InvalidArgumentException::class);
2171
2172
        $do = DataObjectTest\MockDynamicAssignmentDataObject::create();
2173
        $do->write();
2174
2175
        $do->StaticScalarOnlyField = false;
2176
        $do->DynamicScalarOnlyField = true;
2177
        $do->DynamicField = false;
2178
2179
        $do->write();
2180
    }
2181
}
2182