Passed
Push — 4.0 ( a104b5...3f3a18 )
by
unknown
14:17 queued 05:58
created

DataObjectTest::testConstructAcceptsValues()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 14
nc 1
nop 0
dl 0
loc 25
rs 9.7998
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
    );
61
62
    public static function getExtraDataObjects()
63
    {
64
        return array_merge(
65
            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...
66
            ManyManyListTest::$extra_data_objects
67
        );
68
    }
69
70
    public function testDb()
71
    {
72
        $schema = DataObject::getSchema();
73
        $dbFields = $schema->fieldSpecs(DataObjectTest\TeamComment::class);
74
75
        // Assert fields are included
76
        $this->assertArrayHasKey('Name', $dbFields);
77
78
        // Assert the base fields are included
79
        $this->assertArrayHasKey('Created', $dbFields);
80
        $this->assertArrayHasKey('LastEdited', $dbFields);
81
        $this->assertArrayHasKey('ClassName', $dbFields);
82
        $this->assertArrayHasKey('ID', $dbFields);
83
84
        // Assert that the correct field type is returned when passing a field
85
        $this->assertEquals('Varchar', $schema->fieldSpec(DataObjectTest\TeamComment::class, 'Name'));
86
        $this->assertEquals('Text', $schema->fieldSpec(DataObjectTest\TeamComment::class, 'Comment'));
87
88
        // Test with table required
89
        $this->assertEquals(
90
            DataObjectTest\TeamComment::class . '.Varchar',
91
            $schema->fieldSpec(DataObjectTest\TeamComment::class, 'Name', DataObjectSchema::INCLUDE_CLASS)
92
        );
93
        $this->assertEquals(
94
            DataObjectTest\TeamComment::class . '.Text',
95
            $schema->fieldSpec(DataObjectTest\TeamComment::class, 'Comment', DataObjectSchema::INCLUDE_CLASS)
96
        );
97
        $dbFields = $schema->fieldSpecs(DataObjectTest\ExtendedTeamComment::class);
98
99
        // fixed fields are still included in extended classes
100
        $this->assertArrayHasKey('Created', $dbFields);
101
        $this->assertArrayHasKey('LastEdited', $dbFields);
102
        $this->assertArrayHasKey('ClassName', $dbFields);
103
        $this->assertArrayHasKey('ID', $dbFields);
104
105
        // Assert overloaded fields have correct data type
106
        $this->assertEquals('HTMLText', $schema->fieldSpec(DataObjectTest\ExtendedTeamComment::class, 'Comment'));
107
        $this->assertEquals(
108
            'HTMLText',
109
            $dbFields['Comment'],
110
            'Calls to DataObject::db without a field specified return correct data types'
111
        );
112
113
        // assertEquals doesn't verify the order of array elements, so access keys manually to check order:
114
        // 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...
115
        $this->assertEquals(
116
            array(
117
                'Name',
118
                'Comment'
119
            ),
120
            array_slice(array_keys($dbFields), 4, 2),
121
            'DataObject::db returns fields in correct order'
122
        );
123
    }
124
125
    public function testConstructAcceptsValues()
126
    {
127
        // Values can be an array...
128
        $player = new DataObjectTest\Player(
129
            array(
130
                'FirstName' => 'James',
131
                'Surname' => 'Smith'
132
            )
133
        );
134
135
        $this->assertEquals('James', $player->FirstName);
136
        $this->assertEquals('Smith', $player->Surname);
137
138
        // ... or a stdClass inst
139
        $data = new stdClass();
140
        $data->FirstName = 'John';
141
        $data->Surname = 'Doe';
142
        $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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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