Completed
Push — master ( a2aaa6...ee05c5 )
by Damian
07:58
created

testGetOneMissingValueReturnsNull()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
169
        $obj->write();
170
171
        // Change the class
172
        $obj->ClassName = DataObjectTest\OtherSubclassWithSameField::class;
173
        $obj->write();
174
        $obj->flushCache();
175
176
        // Re-fetch from the database and confirm that the data is sourced from
177
        // OtherSubclassWithSameField.SubclassDatabaseField
178
        $obj = DataObject::get_by_id(DataObjectTest\Team::class, $obj->ID);
179
        $this->assertNull($obj->SubclassDatabaseField);
180
181
        // Confirm that save the object in the other direction.
182
        $obj->SubclassDatabaseField = 'obj-Other';
0 ignored issues
show
Documentation introduced by
The property SubclassDatabaseField does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
183
        $obj->write();
184
185
        $obj->ClassName = DataObjectTest\SubTeam::class;
186
        $obj->write();
187
        $obj->flushCache();
188
189
        // If we restore the class, the old value has been lying dormant and will be available again.
190
        // NOTE: This behaviour is volatile; we may change this in the future to clear fields that
191
        // are no longer relevant when changing ClassName
192
        $obj = DataObject::get_by_id(DataObjectTest\Team::class, $obj->ID);
193
        $this->assertEquals('obj-SubTeam', $obj->SubclassDatabaseField);
194
    }
195
196
    /**
197
     * Test deletion of DataObjects
198
     *   - Deleting using delete() on the DataObject
199
     *   - Deleting using DataObject::delete_by_id()
200
     */
201
    public function testDelete()
202
    {
203
        // Test deleting using delete() on the DataObject
204
        // Get the first page
205
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
206
        $objID = $obj->ID;
207
        // Check the page exists before deleting
208
        $this->assertTrue(is_object($obj) && $obj->exists());
209
        // Delete the page
210
        $obj->delete();
211
        // Check that page does not exist after deleting
212
        $obj = DataObject::get_by_id(DataObjectTest\Player::class, $objID);
213
        $this->assertTrue(!$obj || !$obj->exists());
214
215
216
        // Test deleting using DataObject::delete_by_id()
217
        // Get the second page
218
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
219
        $objID = $obj->ID;
220
        // Check the page exists before deleting
221
        $this->assertTrue(is_object($obj) && $obj->exists());
222
        // Delete the page
223
        DataObject::delete_by_id(DataObjectTest\Player::class, $obj->ID);
224
        // Check that page does not exist after deleting
225
        $obj = DataObject::get_by_id(DataObjectTest\Player::class, $objID);
226
        $this->assertTrue(!$obj || !$obj->exists());
227
    }
228
229
    /**
230
     * Test methods that get DataObjects
231
     *   - DataObject::get()
232
     *       - All records of a DataObject
233
     *       - Filtering
234
     *       - Sorting
235
     *       - Joins
236
     *       - Limit
237
     *       - Container class
238
     *   - DataObject::get_by_id()
239
     *   - DataObject::get_one()
240
     *        - With and without caching
241
     *        - With and without ordering
242
     */
243
    public function testGet()
244
    {
245
        // Test getting all records of a DataObject
246
        $comments = DataObject::get(DataObjectTest\TeamComment::class);
247
        $this->assertEquals(3, $comments->count());
248
249
        // Test WHERE clause
250
        $comments = DataObject::get(DataObjectTest\TeamComment::class, "\"Name\"='Bob'");
251
        $this->assertEquals(1, $comments->count());
252
        foreach ($comments as $comment) {
253
            $this->assertEquals('Bob', $comment->Name);
254
        }
255
256
        // Test sorting
257
        $comments = DataObject::get(DataObjectTest\TeamComment::class, '', "\"Name\" ASC");
258
        $this->assertEquals(3, $comments->count());
259
        $this->assertEquals('Bob', $comments->first()->Name);
260
        $comments = DataObject::get(DataObjectTest\TeamComment::class, '', "\"Name\" DESC");
261
        $this->assertEquals(3, $comments->count());
262
        $this->assertEquals('Phil', $comments->first()->Name);
263
264
        // Test limit
265
        $comments = DataObject::get(DataObjectTest\TeamComment::class, '', "\"Name\" ASC", '', '1,2');
266
        $this->assertEquals(2, $comments->count());
267
        $this->assertEquals('Joe', $comments->first()->Name);
268
        $this->assertEquals('Phil', $comments->last()->Name);
269
270
        // Test get_by_id()
271
        $captain1ID = $this->idFromFixture(DataObjectTest\Player::class, 'captain1');
272
        $captain1 = DataObject::get_by_id(DataObjectTest\Player::class, $captain1ID);
273
        $this->assertEquals('Captain', $captain1->FirstName);
274
275
        // Test get_one() without caching
276
        $comment1 = DataObject::get_one(
277
            DataObjectTest\TeamComment::class,
278
            array(
279
            '"DataObjectTest_TeamComment"."Name"' => 'Joe'
280
            ),
281
            false
282
        );
283
        $comment1->Comment = "Something Else";
0 ignored issues
show
Documentation introduced by
The property Comment does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
284
285
        $comment2 = DataObject::get_one(
286
            DataObjectTest\TeamComment::class,
287
            array(
288
            '"DataObjectTest_TeamComment"."Name"' => 'Joe'
289
            ),
290
            false
291
        );
292
        $this->assertNotEquals($comment1->Comment, $comment2->Comment);
293
294
        // Test get_one() with caching
295
        $comment1 = DataObject::get_one(
296
            DataObjectTest\TeamComment::class,
297
            array(
298
            '"DataObjectTest_TeamComment"."Name"' => 'Bob'
299
            ),
300
            true
301
        );
302
        $comment1->Comment = "Something Else";
0 ignored issues
show
Documentation introduced by
The property Comment does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
303
304
        $comment2 = DataObject::get_one(
305
            DataObjectTest\TeamComment::class,
306
            array(
307
            '"DataObjectTest_TeamComment"."Name"' => 'Bob'
308
            ),
309
            true
310
        );
311
        $this->assertEquals((string)$comment1->Comment, (string)$comment2->Comment);
312
313
        // Test get_one() with order by without caching
314
        $comment = DataObject::get_one(DataObjectTest\TeamComment::class, '', false, "\"Name\" ASC");
315
        $this->assertEquals('Bob', $comment->Name);
316
317
        $comment = DataObject::get_one(DataObjectTest\TeamComment::class, '', false, "\"Name\" DESC");
318
        $this->assertEquals('Phil', $comment->Name);
319
320
        // Test get_one() with order by with caching
321
        $comment = DataObject::get_one(DataObjectTest\TeamComment::class, '', true, '"Name" ASC');
322
        $this->assertEquals('Bob', $comment->Name);
323
        $comment = DataObject::get_one(DataObjectTest\TeamComment::class, '', true, '"Name" DESC');
324
        $this->assertEquals('Phil', $comment->Name);
325
    }
326
327
    public function testGetCaseInsensitive()
328
    {
329
        // Test get_one() with bad case on the classname
330
        // Note: This will succeed only if the underlying DB server supports case-insensitive
331
        // table names (e.g. such as MySQL, but not SQLite3)
332
        if (!(DB::get_conn() instanceof MySQLDatabase)) {
333
            $this->markTestSkipped('MySQL only');
334
        }
335
336
        $subteam1 = DataObject::get_one(
337
            strtolower(DataObjectTest\SubTeam::class),
338
            array(
339
            '"DataObjectTest_Team"."Title"' => 'Subteam 1'
340
            ),
341
            true
342
        );
343
        $this->assertNotEmpty($subteam1);
344
        $this->assertEquals($subteam1->Title, "Subteam 1");
345
    }
346
347
    public function testGetSubclassFields()
348
    {
349
        /* Test that fields / has_one relations from the parent table and the subclass tables are extracted */
350
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, "captain1");
351
        // Base field
352
        $this->assertEquals('Captain', $captain1->FirstName);
353
        // Subclass field
354
        $this->assertEquals('007', $captain1->ShirtNumber);
355
        // Subclass has_one relation
356
        $this->assertEquals($this->idFromFixture(DataObjectTest\Team::class, 'team1'), $captain1->FavouriteTeamID);
357
    }
358
359
    public function testGetRelationClass()
360
    {
361
        $obj = new DataObjectTest\Player();
362
        $this->assertEquals(
363
            singleton(DataObjectTest\Player::class)->getRelationClass('FavouriteTeam'),
364
            DataObjectTest\Team::class,
365
            'has_one is properly inspected'
366
        );
367
        $this->assertEquals(
368
            singleton(DataObjectTest\Company::class)->getRelationClass('CurrentStaff'),
369
            DataObjectTest\Staff::class,
370
            'has_many is properly inspected'
371
        );
372
        $this->assertEquals(
373
            singleton(DataObjectTest\Team::class)->getRelationClass('Players'),
374
            DataObjectTest\Player::class,
375
            'many_many is properly inspected'
376
        );
377
        $this->assertEquals(
378
            singleton(DataObjectTest\Player::class)->getRelationClass('Teams'),
379
            DataObjectTest\Team::class,
380
            'belongs_many_many is properly inspected'
381
        );
382
        $this->assertEquals(
383
            singleton(DataObjectTest\CEO::class)->getRelationClass('Company'),
384
            DataObjectTest\Company::class,
385
            'belongs_to is properly inspected'
386
        );
387
        $this->assertEquals(
388
            singleton(DataObjectTest\Fan::class)->getRelationClass('Favourite'),
389
            DataObject::class,
390
            'polymorphic has_one is properly inspected'
391
        );
392
    }
393
394
    /**
395
     * Test that has_one relations can be retrieved
396
     */
397
    public function testGetHasOneRelations()
398
    {
399
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, "captain1");
400
        $team1ID = $this->idFromFixture(DataObjectTest\Team::class, 'team1');
401
402
        // There will be a field called (relname)ID that contains the ID of the
403
        // object linked to via the has_one relation
404
        $this->assertEquals($team1ID, $captain1->FavouriteTeamID);
405
406
        // There will be a method called $obj->relname() that returns the object itself
407
        $this->assertEquals($team1ID, $captain1->FavouriteTeam()->ID);
408
409
        // Test that getNonReciprocalComponent can find has_one from the has_many end
410
        $this->assertEquals(
411
            $team1ID,
412
            $captain1->inferReciprocalComponent(DataObjectTest\Team::class, 'PlayerFans')->ID
413
        );
414
415
        // Check entity with polymorphic has-one
416
        $fan1 = $this->objFromFixture(DataObjectTest\Fan::class, "fan1");
417
        $this->assertTrue((bool)$fan1->hasValue('Favourite'));
418
419
        // There will be fields named (relname)ID and (relname)Class for polymorphic
420
        // entities
421
        $this->assertEquals($team1ID, $fan1->FavouriteID);
422
        $this->assertEquals(DataObjectTest\Team::class, $fan1->FavouriteClass);
423
424
        // There will be a method called $obj->relname() that returns the object itself
425
        $favourite = $fan1->Favourite();
426
        $this->assertEquals($team1ID, $favourite->ID);
427
        $this->assertInstanceOf(DataObjectTest\Team::class, $favourite);
428
429
        // check behaviour of dbObject with polymorphic relations
430
        $favouriteDBObject = $fan1->dbObject('Favourite');
431
        $favouriteValue = $favouriteDBObject->getValue();
432
        $this->assertInstanceOf(DBPolymorphicForeignKey::class, $favouriteDBObject);
433
        $this->assertEquals($favourite->ID, $favouriteValue->ID);
434
        $this->assertEquals($favourite->ClassName, $favouriteValue->ClassName);
435
    }
436
437
    public function testLimitAndCount()
438
    {
439
        $players = DataObject::get(DataObjectTest\Player::class);
440
441
        // There's 4 records in total
442
        $this->assertEquals(4, $players->count());
443
444
        // Testing "##, ##" syntax
445
        $this->assertEquals(4, $players->limit(20)->count());
446
        $this->assertEquals(4, $players->limit(20, 0)->count());
447
        $this->assertEquals(0, $players->limit(20, 20)->count());
448
        $this->assertEquals(2, $players->limit(2, 0)->count());
449
        $this->assertEquals(1, $players->limit(5, 3)->count());
450
    }
451
452
    /**
453
     * Test writing of database columns which don't correlate to a DBField,
454
     * e.g. all relation fields on has_one/has_many like "ParentID".
455
     */
456
    public function testWritePropertyWithoutDBField()
457
    {
458
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
459
        $obj->FavouriteTeamID = 99;
0 ignored issues
show
Documentation introduced by
The property FavouriteTeamID does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
460
        $obj->write();
461
462
        // reload the page from the database
463
        $savedObj = DataObject::get_by_id(DataObjectTest\Player::class, $obj->ID);
464
        $this->assertTrue($savedObj->FavouriteTeamID == 99);
465
466
        // Test with porymorphic relation
467
        $obj2 = $this->objFromFixture(DataObjectTest\Fan::class, "fan1");
468
        $obj2->FavouriteID = 99;
0 ignored issues
show
Documentation introduced by
The property FavouriteID does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
469
        $obj2->FavouriteClass = DataObjectTest\Player::class;
0 ignored issues
show
Documentation introduced by
The property FavouriteClass does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
470
        $obj2->write();
471
472
        $savedObj2 = DataObject::get_by_id(DataObjectTest\Fan::class, $obj2->ID);
473
        $this->assertTrue($savedObj2->FavouriteID == 99);
474
        $this->assertTrue($savedObj2->FavouriteClass == DataObjectTest\Player::class);
475
    }
476
477
    /**
478
     * Test has many relationships
479
     *   - Test getComponents() gets the ComponentSet of the other side of the relation
480
     *   - Test the IDs on the DataObjects are set correctly
481
     */
482
    public function testHasManyRelationships()
483
    {
484
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
485
486
        // Test getComponents() gets the ComponentSet of the other side of the relation
487
        $this->assertTrue($team1->Comments()->count() == 2);
488
489
        $team1Comments = [
490
            ['Comment' => 'This is a team comment by Joe'],
491
            ['Comment' => 'This is a team comment by Bob'],
492
        ];
493
494
        // Test the IDs on the DataObjects are set correctly
495
        $this->assertDOSEquals($team1Comments, $team1->Comments());
496
497
        // Test that has_many can be infered from the has_one via getNonReciprocalComponent
498
        $this->assertDOSEquals(
499
            $team1Comments,
500
            $team1->inferReciprocalComponent(DataObjectTest\TeamComment::class, 'Team')
501
        );
502
503
        // Test that we can add and remove items that already exist in the database
504
        $newComment = new DataObjectTest\TeamComment();
505
        $newComment->Name = "Automated commenter";
0 ignored issues
show
Documentation introduced by
The property Name does not exist on object<SilverStripe\ORM\...ObjectTest\TeamComment>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
506
        $newComment->Comment = "This is a new comment";
0 ignored issues
show
Documentation introduced by
The property Comment does not exist on object<SilverStripe\ORM\...ObjectTest\TeamComment>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
507
        $newComment->write();
508
        $team1->Comments()->add($newComment);
509
        $this->assertEquals($team1->ID, $newComment->TeamID);
510
511
        $comment1 = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment1');
512
        $comment2 = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment2');
513
        $team1->Comments()->remove($comment2);
514
515
        $team1CommentIDs = $team1->Comments()->sort('ID')->column('ID');
516
        $this->assertEquals(array($comment1->ID, $newComment->ID), $team1CommentIDs);
517
518
        // Test that removing an item from a list doesn't remove it from the same
519
        // relation belonging to a different object
520
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
521
        $team2 = $this->objFromFixture(DataObjectTest\Team::class, 'team2');
522
        $team2->Comments()->remove($comment1);
523
        $team1CommentIDs = $team1->Comments()->sort('ID')->column('ID');
524
        $this->assertEquals(array($comment1->ID, $newComment->ID), $team1CommentIDs);
525
    }
526
527
528
    /**
529
     * Test has many relationships against polymorphic has_one fields
530
     *   - Test getComponents() gets the ComponentSet of the other side of the relation
531
     *   - Test the IDs on the DataObjects are set correctly
532
     */
533
    public function testHasManyPolymorphicRelationships()
534
    {
535
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
536
537
        // Test getComponents() gets the ComponentSet of the other side of the relation
538
        $this->assertTrue($team1->Fans()->count() == 2);
539
540
        // Test the IDs/Classes on the DataObjects are set correctly
541
        foreach ($team1->Fans() as $fan) {
542
            $this->assertEquals($team1->ID, $fan->FavouriteID, 'Fan has the correct FavouriteID');
543
            $this->assertEquals(DataObjectTest\Team::class, $fan->FavouriteClass, 'Fan has the correct FavouriteClass');
544
        }
545
546
        // Test that we can add and remove items that already exist in the database
547
        $newFan = new DataObjectTest\Fan();
548
        $newFan->Name = "New fan";
0 ignored issues
show
Documentation introduced by
The property Name does not exist on object<SilverStripe\ORM\Tests\DataObjectTest\Fan>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
549
        $newFan->write();
550
        $team1->Fans()->add($newFan);
551
        $this->assertEquals($team1->ID, $newFan->FavouriteID, 'Newly created fan has the correct FavouriteID');
552
        $this->assertEquals(
553
            DataObjectTest\Team::class,
554
            $newFan->FavouriteClass,
555
            'Newly created fan has the correct FavouriteClass'
556
        );
557
558
        $fan1 = $this->objFromFixture(DataObjectTest\Fan::class, 'fan1');
559
        $fan3 = $this->objFromFixture(DataObjectTest\Fan::class, 'fan3');
560
        $team1->Fans()->remove($fan3);
561
562
        $team1FanIDs = $team1->Fans()->sort('ID')->column('ID');
563
        $this->assertEquals(array($fan1->ID, $newFan->ID), $team1FanIDs);
564
565
        // Test that removing an item from a list doesn't remove it from the same
566
        // relation belonging to a different object
567
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
568
        $player1 = $this->objFromFixture(DataObjectTest\Player::class, 'player1');
569
        $player1->Fans()->remove($fan1);
570
        $team1FanIDs = $team1->Fans()->sort('ID')->column('ID');
571
        $this->assertEquals(array($fan1->ID, $newFan->ID), $team1FanIDs);
572
    }
573
574
575
    public function testHasOneRelationship()
576
    {
577
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
578
        $player1 = $this->objFromFixture(DataObjectTest\Player::class, 'player1');
579
        $player2 = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
580
        $fan1 = $this->objFromFixture(DataObjectTest\Fan::class, 'fan1');
581
582
        // Test relation probing
583
        $this->assertFalse((bool)$team1->hasValue('Captain', null, false));
584
        $this->assertFalse((bool)$team1->hasValue('CaptainID', null, false));
585
586
        // Add a captain to team 1
587
        $team1->setField('CaptainID', $player1->ID);
588
        $team1->write();
589
590
        $this->assertTrue((bool)$team1->hasValue('Captain', null, false));
591
        $this->assertTrue((bool)$team1->hasValue('CaptainID', null, false));
592
593
        $this->assertEquals(
594
            $player1->ID,
595
            $team1->Captain()->ID,
596
            'The captain exists for team 1'
597
        );
598
        $this->assertEquals(
599
            $player1->ID,
600
            $team1->getComponent('Captain')->ID,
601
            'The captain exists through the component getter'
602
        );
603
604
        $this->assertEquals(
605
            $team1->Captain()->FirstName,
606
            'Player 1',
607
            'Player 1 is the captain'
608
        );
609
        $this->assertEquals(
610
            $team1->getComponent('Captain')->FirstName,
611
            'Player 1',
612
            'Player 1 is the captain'
613
        );
614
615
        $team1->CaptainID = $player2->ID;
0 ignored issues
show
Documentation introduced by
The property CaptainID does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
616
        $team1->write();
617
618
        $this->assertEquals($player2->ID, $team1->Captain()->ID);
619
        $this->assertEquals($player2->ID, $team1->getComponent('Captain')->ID);
620
        $this->assertEquals('Player 2', $team1->Captain()->FirstName);
621
        $this->assertEquals('Player 2', $team1->getComponent('Captain')->FirstName);
622
623
624
        // Set the favourite team for fan1
625
        $fan1->setField('FavouriteID', $team1->ID);
626
        $fan1->setField('FavouriteClass', get_class($team1));
627
628
        $this->assertEquals($team1->ID, $fan1->Favourite()->ID, 'The team is assigned to fan 1');
629
        $this->assertInstanceOf(get_class($team1), $fan1->Favourite(), 'The team is assigned to fan 1');
630
        $this->assertEquals(
631
            $team1->ID,
632
            $fan1->getComponent('Favourite')->ID,
633
            'The team exists through the component getter'
634
        );
635
        $this->assertInstanceOf(
636
            get_class($team1),
637
            $fan1->getComponent('Favourite'),
638
            'The team exists through the component getter'
639
        );
640
641
        $this->assertEquals(
642
            $fan1->Favourite()->Title,
643
            'Team 1',
644
            'Team 1 is the favourite'
645
        );
646
        $this->assertEquals(
647
            $fan1->getComponent('Favourite')->Title,
648
            'Team 1',
649
            'Team 1 is the favourite'
650
        );
651
    }
652
653
    /**
654
     * @todo Extend type change tests (e.g. '0'==NULL)
655
     */
656
    public function testChangedFields()
657
    {
658
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
659
        $obj->FirstName = 'Captain-changed';
0 ignored issues
show
Documentation introduced by
The property FirstName does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
660
        $obj->IsRetired = true;
0 ignored issues
show
Documentation introduced by
The property IsRetired does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
661
662
        $this->assertEquals(
663
            $obj->getChangedFields(true, DataObject::CHANGE_STRICT),
664
            array(
665
                'FirstName' => array(
666
                    'before' => 'Captain',
667
                    'after' => 'Captain-changed',
668
                    'level' => DataObject::CHANGE_VALUE
669
                ),
670
                'IsRetired' => array(
671
                    'before' => 1,
672
                    'after' => true,
673
                    'level' => DataObject::CHANGE_STRICT
674
                )
675
            ),
676
            'Changed fields are correctly detected with strict type changes (level=1)'
677
        );
678
679
        $this->assertEquals(
680
            $obj->getChangedFields(true, DataObject::CHANGE_VALUE),
681
            array(
682
                'FirstName' => array(
683
                    'before'=>'Captain',
684
                    'after'=>'Captain-changed',
685
                    'level' => DataObject::CHANGE_VALUE
686
                )
687
            ),
688
            'Changed fields are correctly detected while ignoring type changes (level=2)'
689
        );
690
691
        $newObj = new DataObjectTest\Player();
692
        $newObj->FirstName = "New Player";
693
        $this->assertEquals(
694
            array(
695
                'FirstName' => array(
696
                    'before' => null,
697
                    'after' => 'New Player',
698
                    'level' => DataObject::CHANGE_VALUE
699
                )
700
            ),
701
            $newObj->getChangedFields(true, DataObject::CHANGE_VALUE),
702
            'Initialised fields are correctly detected as full changes'
703
        );
704
    }
705
706
    /**
707
     * @skipUpgrade
708
     */
709
    public function testIsChanged()
710
    {
711
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
712
        $obj->NonDBField = 'bob';
0 ignored issues
show
Documentation introduced by
The property NonDBField does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
713
        $obj->FirstName = 'Captain-changed';
0 ignored issues
show
Documentation introduced by
The property FirstName does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
714
        $obj->IsRetired = true; // type change only, database stores "1"
0 ignored issues
show
Documentation introduced by
The property IsRetired does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
715
716
        // Now that DB fields are changed, isChanged is true
717
        $this->assertTrue($obj->isChanged('NonDBField'));
718
        $this->assertFalse($obj->isChanged('NonField'));
719
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_STRICT));
720
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_VALUE));
721
        $this->assertTrue($obj->isChanged('IsRetired', DataObject::CHANGE_STRICT));
722
        $this->assertFalse($obj->isChanged('IsRetired', DataObject::CHANGE_VALUE));
723
        $this->assertFalse($obj->isChanged('Email', 1), 'Doesnt change mark unchanged property');
724
        $this->assertFalse($obj->isChanged('Email', 2), 'Doesnt change mark unchanged property');
725
726
        $newObj = new DataObjectTest\Player();
727
        $newObj->FirstName = "New Player";
728
        $this->assertTrue($newObj->isChanged('FirstName', DataObject::CHANGE_STRICT));
729
        $this->assertTrue($newObj->isChanged('FirstName', DataObject::CHANGE_VALUE));
730
        $this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_STRICT));
731
        $this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_VALUE));
732
733
        $newObj->write();
734
        $this->assertFalse($newObj->ischanged());
735
        $this->assertFalse($newObj->isChanged('FirstName', DataObject::CHANGE_STRICT));
736
        $this->assertFalse($newObj->isChanged('FirstName', DataObject::CHANGE_VALUE));
737
        $this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_STRICT));
738
        $this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_VALUE));
739
740
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
741
        $obj->FirstName = null;
0 ignored issues
show
Documentation introduced by
The property FirstName does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
742
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_STRICT));
743
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_VALUE));
744
745
        /* Test when there's not field provided */
746
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
747
        $this->assertFalse($obj->isChanged());
748
        $obj->NonDBField = 'new value';
0 ignored issues
show
Documentation introduced by
The property NonDBField does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
749
        $this->assertFalse($obj->isChanged());
750
        $obj->FirstName = "New Player";
0 ignored issues
show
Documentation introduced by
The property FirstName does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
751
        $this->assertTrue($obj->isChanged());
752
753
        $obj->write();
754
        $this->assertFalse($obj->isChanged());
755
    }
756
757
    public function testRandomSort()
758
    {
759
        /* If we perform the same regularly sorted query twice, it should return the same results */
760
        $itemsA = DataObject::get(DataObjectTest\TeamComment::class, "", "ID");
761
        foreach ($itemsA as $item) {
762
            $keysA[] = $item->ID;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$keysA was never initialized. Although not strictly required by PHP, it is generally a good practice to add $keysA = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
763
        }
764
765
        $itemsB = DataObject::get(DataObjectTest\TeamComment::class, "", "ID");
766
        foreach ($itemsB as $item) {
767
            $keysB[] = $item->ID;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$keysB was never initialized. Although not strictly required by PHP, it is generally a good practice to add $keysB = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
768
        }
769
770
        /* Test when there's not field provided */
771
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
772
        $obj->FirstName = "New Player";
0 ignored issues
show
Documentation introduced by
The property FirstName does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
773
        $this->assertTrue($obj->isChanged());
774
775
        $obj->write();
776
        $this->assertFalse($obj->isChanged());
777
778
        /* If we perform the same random query twice, it shouldn't return the same results */
779
        $itemsA = DataObject::get(DataObjectTest\TeamComment::class, "", DB::get_conn()->random());
780
        $itemsB = DataObject::get(DataObjectTest\TeamComment::class, "", DB::get_conn()->random());
781
        $itemsC = DataObject::get(DataObjectTest\TeamComment::class, "", DB::get_conn()->random());
782
        $itemsD = DataObject::get(DataObjectTest\TeamComment::class, "", DB::get_conn()->random());
783
        foreach ($itemsA as $item) {
784
            $keysA[] = $item->ID;
0 ignored issues
show
Bug introduced by
The variable $keysA does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
785
        }
786
        foreach ($itemsB as $item) {
787
            $keysB[] = $item->ID;
0 ignored issues
show
Bug introduced by
The variable $keysB does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
788
        }
789
        foreach ($itemsC as $item) {
790
            $keysC[] = $item->ID;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$keysC was never initialized. Although not strictly required by PHP, it is generally a good practice to add $keysC = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
791
        }
792
        foreach ($itemsD as $item) {
793
            $keysD[] = $item->ID;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$keysD was never initialized. Although not strictly required by PHP, it is generally a good practice to add $keysD = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
794
        }
795
796
        // These shouldn't all be the same (run it 4 times to minimise chance of an accidental collision)
797
        // There's about a 1 in a billion chance of an accidental collision
798
        $this->assertTrue($keysA != $keysB || $keysB != $keysC || $keysC != $keysD);
0 ignored issues
show
Bug introduced by
The variable $keysC does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $keysD does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
799
    }
800
801
    public function testWriteSavesToHasOneRelations()
802
    {
803
        /* DataObject::write() should save to a has_one relationship if you set a field called (relname)ID */
804
        $team = new DataObjectTest\Team();
805
        $captainID = $this->idFromFixture(DataObjectTest\Player::class, 'player1');
806
        $team->CaptainID = $captainID;
0 ignored issues
show
Documentation introduced by
The property CaptainID does not exist on object<SilverStripe\ORM\...ts\DataObjectTest\Team>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
807
        $team->write();
808
        $this->assertEquals(
809
            $captainID,
810
            DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $team->ID")->value()
811
        );
812
813
        /* After giving it a value, you should also be able to set it back to null */
814
        $team->CaptainID = '';
0 ignored issues
show
Documentation introduced by
The property CaptainID does not exist on object<SilverStripe\ORM\...ts\DataObjectTest\Team>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
815
        $team->write();
816
        $this->assertEquals(
817
            0,
818
            DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $team->ID")->value()
819
        );
820
821
        /* You should also be able to save a blank to it when it's first created */
822
        $team = new DataObjectTest\Team();
823
        $team->CaptainID = '';
0 ignored issues
show
Documentation introduced by
The property CaptainID does not exist on object<SilverStripe\ORM\...ts\DataObjectTest\Team>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
824
        $team->write();
825
        $this->assertEquals(
826
            0,
827
            DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $team->ID")->value()
828
        );
829
830
        /* Ditto for existing records without a value */
831
        $existingTeam = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
832
        $existingTeam->CaptainID = '';
0 ignored issues
show
Documentation introduced by
The property CaptainID does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
833
        $existingTeam->write();
834
        $this->assertEquals(
835
            0,
836
            DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $existingTeam->ID")->value()
837
        );
838
    }
839
840
    public function testCanAccessHasOneObjectsAsMethods()
841
    {
842
        /* If you have a has_one relation 'Captain' on $obj, and you set the $obj->CaptainID = (ID), then the
843
        * object itself should be accessible as $obj->Captain() */
844
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
845
        $captainID = $this->idFromFixture(DataObjectTest\Player::class, 'captain1');
846
847
        $team->CaptainID = $captainID;
0 ignored issues
show
Documentation introduced by
The property CaptainID does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
848
        $this->assertNotNull($team->Captain());
849
        $this->assertEquals($captainID, $team->Captain()->ID);
850
851
        // Test for polymorphic has_one relations
852
        $fan = $this->objFromFixture(DataObjectTest\Fan::class, 'fan1');
853
        $fan->FavouriteID = $team->ID;
0 ignored issues
show
Documentation introduced by
The property FavouriteID does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
854
        $fan->FavouriteClass = DataObjectTest\Team::class;
0 ignored issues
show
Documentation introduced by
The property FavouriteClass does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
855
        $this->assertNotNull($fan->Favourite());
856
        $this->assertEquals($team->ID, $fan->Favourite()->ID);
857
        $this->assertInstanceOf(DataObjectTest\Team::class, $fan->Favourite());
858
    }
859
860
    public function testFieldNamesThatMatchMethodNamesWork()
861
    {
862
        /* Check that a field name that corresponds to a method on DataObject will still work */
863
        $obj = new DataObjectTest\Fixture();
864
        $obj->Data = "value1";
0 ignored issues
show
Documentation introduced by
The property Data does not exist on object<SilverStripe\ORM\...DataObjectTest\Fixture>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
865
        $obj->DbObject = "value2";
0 ignored issues
show
Documentation introduced by
The property DbObject does not exist on object<SilverStripe\ORM\...DataObjectTest\Fixture>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
866
        $obj->Duplicate = "value3";
0 ignored issues
show
Documentation introduced by
The property Duplicate does not exist on object<SilverStripe\ORM\...DataObjectTest\Fixture>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
867
        $obj->write();
868
869
        $this->assertNotNull($obj->ID);
870
        $this->assertEquals(
871
            'value1',
872
            DB::query("SELECT \"Data\" FROM \"DataObjectTest_Fixture\" WHERE \"ID\" = $obj->ID")->value()
873
        );
874
        $this->assertEquals(
875
            'value2',
876
            DB::query("SELECT \"DbObject\" FROM \"DataObjectTest_Fixture\" WHERE \"ID\" = $obj->ID")->value()
877
        );
878
        $this->assertEquals(
879
            'value3',
880
            DB::query("SELECT \"Duplicate\" FROM \"DataObjectTest_Fixture\" WHERE \"ID\" = $obj->ID")->value()
881
        );
882
    }
883
884
    /**
885
     * @todo Re-enable all test cases for field existence after behaviour has been fixed
886
     */
887
    public function testFieldExistence()
888
    {
889
        $teamInstance = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
890
        $teamSingleton = singleton(DataObjectTest\Team::class);
891
892
        $subteamInstance = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
893
        $schema = DataObject::getSchema();
894
895
        /* hasField() singleton checks */
896
        $this->assertTrue(
897
            $teamSingleton->hasField('ID'),
898
            'hasField() finds built-in fields in singletons'
899
        );
900
        $this->assertTrue(
901
            $teamSingleton->hasField('Title'),
902
            'hasField() finds custom fields in singletons'
903
        );
904
905
        /* hasField() instance checks */
906
        $this->assertFalse(
907
            $teamInstance->hasField('NonExistingField'),
908
            'hasField() doesnt find non-existing fields in instances'
909
        );
910
        $this->assertTrue(
911
            $teamInstance->hasField('ID'),
912
            'hasField() finds built-in fields in instances'
913
        );
914
        $this->assertTrue(
915
            $teamInstance->hasField('Created'),
916
            'hasField() finds built-in fields in instances'
917
        );
918
        $this->assertTrue(
919
            $teamInstance->hasField('DatabaseField'),
920
            'hasField() finds custom fields in instances'
921
        );
922
        //$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...
923
        //'hasField() doesnt find subclass fields in parentclass instances');
924
        $this->assertTrue(
925
            $teamInstance->hasField('DynamicField'),
926
            'hasField() finds dynamic getters in instances'
927
        );
928
        $this->assertTrue(
929
            $teamInstance->hasField('HasOneRelationshipID'),
930
            'hasField() finds foreign keys in instances'
931
        );
932
        $this->assertTrue(
933
            $teamInstance->hasField('ExtendedDatabaseField'),
934
            'hasField() finds extended fields in instances'
935
        );
936
        $this->assertTrue(
937
            $teamInstance->hasField('ExtendedHasOneRelationshipID'),
938
            'hasField() finds extended foreign keys in instances'
939
        );
940
        //$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...
941
        //'hasField() includes extended dynamic getters in instances');
942
943
        /* hasField() subclass checks */
944
        $this->assertTrue(
945
            $subteamInstance->hasField('ID'),
946
            'hasField() finds built-in fields in subclass instances'
947
        );
948
        $this->assertTrue(
949
            $subteamInstance->hasField('Created'),
950
            'hasField() finds built-in fields in subclass instances'
951
        );
952
        $this->assertTrue(
953
            $subteamInstance->hasField('DatabaseField'),
954
            'hasField() finds custom fields in subclass instances'
955
        );
956
        $this->assertTrue(
957
            $subteamInstance->hasField('SubclassDatabaseField'),
958
            'hasField() finds custom fields in subclass instances'
959
        );
960
        $this->assertTrue(
961
            $subteamInstance->hasField('DynamicField'),
962
            'hasField() finds dynamic getters in subclass instances'
963
        );
964
        $this->assertTrue(
965
            $subteamInstance->hasField('HasOneRelationshipID'),
966
            'hasField() finds foreign keys in subclass instances'
967
        );
968
        $this->assertTrue(
969
            $subteamInstance->hasField('ExtendedDatabaseField'),
970
            'hasField() finds extended fields in subclass instances'
971
        );
972
        $this->assertTrue(
973
            $subteamInstance->hasField('ExtendedHasOneRelationshipID'),
974
            'hasField() finds extended foreign keys in subclass instances'
975
        );
976
977
        /* hasDatabaseField() singleton checks */
978
        //$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...
979
        //'hasDatabaseField() finds built-in fields in singletons');
980
        $this->assertNotEmpty(
981
            $schema->fieldSpec(DataObjectTest\Team::class, 'Title'),
982
            'hasDatabaseField() finds custom fields in singletons'
983
        );
984
985
        /* hasDatabaseField() instance checks */
986
        $this->assertNull(
987
            $schema->fieldSpec(DataObjectTest\Team::class, 'NonExistingField'),
988
            'hasDatabaseField() doesnt find non-existing fields in instances'
989
        );
990
        //$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...
991
        //'hasDatabaseField() finds built-in fields in instances');
992
        $this->assertNotEmpty(
993
            $schema->fieldSpec(DataObjectTest\Team::class, 'Created'),
994
            'hasDatabaseField() finds built-in fields in instances'
995
        );
996
        $this->assertNotEmpty(
997
            $schema->fieldSpec(DataObjectTest\Team::class, 'DatabaseField'),
998
            'hasDatabaseField() finds custom fields in instances'
999
        );
1000
        $this->assertNull(
1001
            $schema->fieldSpec(DataObjectTest\Team::class, 'SubclassDatabaseField'),
1002
            'hasDatabaseField() doesnt find subclass fields in parentclass instances'
1003
        );
1004
        //$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...
1005
        //'hasDatabaseField() doesnt dynamic getters in instances');
1006
        $this->assertNotEmpty(
1007
            $schema->fieldSpec(DataObjectTest\Team::class, 'HasOneRelationshipID'),
1008
            'hasDatabaseField() finds foreign keys in instances'
1009
        );
1010
        $this->assertNotEmpty(
1011
            $schema->fieldSpec(DataObjectTest\Team::class, 'ExtendedDatabaseField'),
1012
            'hasDatabaseField() finds extended fields in instances'
1013
        );
1014
        $this->assertNotEmpty(
1015
            $schema->fieldSpec(DataObjectTest\Team::class, 'ExtendedHasOneRelationshipID'),
1016
            'hasDatabaseField() finds extended foreign keys in instances'
1017
        );
1018
        $this->assertNull(
1019
            $schema->fieldSpec(DataObjectTest\Team::class, 'ExtendedDynamicField'),
1020
            'hasDatabaseField() doesnt include extended dynamic getters in instances'
1021
        );
1022
1023
        /* hasDatabaseField() subclass checks */
1024
        $this->assertNotEmpty(
1025
            $schema->fieldSpec(DataObjectTest\SubTeam::class, 'DatabaseField'),
1026
            'hasField() finds custom fields in subclass instances'
1027
        );
1028
        $this->assertNotEmpty(
1029
            $schema->fieldSpec(DataObjectTest\SubTeam::class, 'SubclassDatabaseField'),
1030
            'hasField() finds custom fields in subclass instances'
1031
        );
1032
    }
1033
1034
    /**
1035
     * @todo Re-enable all test cases for field inheritance aggregation after behaviour has been fixed
1036
     */
1037
    public function testFieldInheritance()
1038
    {
1039
        $schema = DataObject::getSchema();
1040
1041
        // Test logical fields (including composite)
1042
        $teamSpecifications = $schema->fieldSpecs(DataObjectTest\Team::class);
1043
        $this->assertEquals(
1044
            array(
1045
                'ID',
1046
                'ClassName',
1047
                'LastEdited',
1048
                'Created',
1049
                'Title',
1050
                'DatabaseField',
1051
                'ExtendedDatabaseField',
1052
                'CaptainID',
1053
                'FounderID',
1054
                'HasOneRelationshipID',
1055
                'ExtendedHasOneRelationshipID'
1056
            ),
1057
            array_keys($teamSpecifications),
1058
            'fieldSpecifications() contains all fields defined on instance: base, extended and foreign keys'
1059
        );
1060
1061
        $teamFields = $schema->databaseFields(DataObjectTest\Team::class, false);
1062
        $this->assertEquals(
1063
            array(
1064
                'ID',
1065
                'ClassName',
1066
                'LastEdited',
1067
                'Created',
1068
                'Title',
1069
                'DatabaseField',
1070
                'ExtendedDatabaseField',
1071
                'CaptainID',
1072
                'FounderID',
1073
                'HasOneRelationshipID',
1074
                'ExtendedHasOneRelationshipID'
1075
            ),
1076
            array_keys($teamFields),
1077
            'databaseFields() contains only fields defined on instance, including base, extended and foreign keys'
1078
        );
1079
1080
        $subteamSpecifications = $schema->fieldSpecs(DataObjectTest\SubTeam::class);
1081
        $this->assertEquals(
1082
            array(
1083
                'ID',
1084
                'ClassName',
1085
                'LastEdited',
1086
                'Created',
1087
                'Title',
1088
                'DatabaseField',
1089
                'ExtendedDatabaseField',
1090
                'CaptainID',
1091
                'FounderID',
1092
                'HasOneRelationshipID',
1093
                'ExtendedHasOneRelationshipID',
1094
                'SubclassDatabaseField',
1095
                'ParentTeamID',
1096
            ),
1097
            array_keys($subteamSpecifications),
1098
            'fieldSpecifications() on subclass contains all fields, including base, extended  and foreign keys'
1099
        );
1100
1101
        $subteamFields = $schema->databaseFields(DataObjectTest\SubTeam::class, false);
1102
        $this->assertEquals(
1103
            array(
1104
                'ID',
1105
                'SubclassDatabaseField',
1106
                'ParentTeamID',
1107
            ),
1108
            array_keys($subteamFields),
1109
            'databaseFields() on subclass contains only fields defined on instance'
1110
        );
1111
    }
1112
1113
    public function testSearchableFields()
1114
    {
1115
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
1116
        $fields = $player->searchableFields();
1117
        $this->assertArrayHasKey(
1118
            'IsRetired',
1119
            $fields,
1120
            'Fields defined by $searchable_fields static are correctly detected'
1121
        );
1122
        $this->assertArrayHasKey(
1123
            'ShirtNumber',
1124
            $fields,
1125
            'Fields defined by $searchable_fields static are correctly detected'
1126
        );
1127
1128
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1129
        $fields = $team->searchableFields();
1130
        $this->assertArrayHasKey(
1131
            'Title',
1132
            $fields,
1133
            'Fields can be inherited from the $summary_fields static, including methods called on fields'
1134
        );
1135
        $this->assertArrayHasKey(
1136
            'Captain.ShirtNumber',
1137
            $fields,
1138
            'Fields on related objects can be inherited from the $summary_fields static'
1139
        );
1140
        $this->assertArrayHasKey(
1141
            'Captain.FavouriteTeam.Title',
1142
            $fields,
1143
            'Fields on related objects can be inherited from the $summary_fields static'
1144
        );
1145
1146
        $testObj = new DataObjectTest\Fixture();
1147
        $fields = $testObj->searchableFields();
1148
        $this->assertEmpty($fields);
1149
    }
1150
1151
    public function testCastingHelper()
1152
    {
1153
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1154
1155
        $this->assertEquals('Varchar', $team->castingHelper('Title'), 'db field wasn\'t casted correctly');
1156
        $this->assertEquals('HTMLVarchar', $team->castingHelper('DatabaseField'), 'db field wasn\'t casted correctly');
1157
1158
        $sponsor = $team->Sponsors()->first();
1159
        $this->assertEquals('Int', $sponsor->castingHelper('SponsorFee'), 'many_many_extraFields not casted correctly');
1160
    }
1161
1162
    public function testSummaryFieldsCustomLabels()
1163
    {
1164
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1165
        $summaryFields = $team->summaryFields();
1166
1167
        $this->assertEquals(
1168
            'Custom Title',
1169
            $summaryFields['Title'],
1170
            'Custom title is preserved'
1171
        );
1172
1173
        $this->assertEquals(
1174
            'Captain\'s shirt number',
1175
            $summaryFields['Captain.ShirtNumber'],
1176
            'Custom title on relation is preserved'
1177
        );
1178
    }
1179
1180
    public function testDataObjectUpdate()
1181
    {
1182
        /* update() calls can use the dot syntax to reference has_one relations and other methods that return
1183
        * objects */
1184
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1185
        $team1->CaptainID = $this->idFromFixture(DataObjectTest\Player::class, 'captain1');
0 ignored issues
show
Documentation introduced by
The property CaptainID does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1186
1187
        $team1->update(
1188
            array(
1189
            'DatabaseField' => 'Something',
1190
            'Captain.FirstName' => 'Jim',
1191
            'Captain.Email' => '[email protected]',
1192
            'Captain.FavouriteTeam.Title' => 'New and improved team 1',
1193
            )
1194
        );
1195
1196
        /* Test the simple case of updating fields on the object itself */
1197
        $this->assertEquals('Something', $team1->DatabaseField);
1198
1199
        /* Setting Captain.Email and Captain.FirstName will have updated DataObjectTest_Captain.captain1 in
1200
        * the database.  Although update() doesn't usually write, it does write related records automatically. */
1201
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
1202
        $this->assertEquals('Jim', $captain1->FirstName);
1203
        $this->assertEquals('[email protected]', $captain1->Email);
1204
1205
        /* Jim's favourite team is team 1; we need to reload the object to the the change that setting Captain.
1206
        * FavouriteTeam.Title made */
1207
        $reloadedTeam1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1208
        $this->assertEquals('New and improved team 1', $reloadedTeam1->Title);
1209
    }
1210
1211
    public function testDataObjectUpdateNew()
1212
    {
1213
        /* update() calls can use the dot syntax to reference has_one relations and other methods that return
1214
        * objects */
1215
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1216
        $team1->CaptainID = 0;
0 ignored issues
show
Documentation introduced by
The property CaptainID does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1217
1218
        $team1->update(
1219
            array(
1220
            'Captain.FirstName' => 'Jim',
1221
            'Captain.FavouriteTeam.Title' => 'New and improved team 1',
1222
            )
1223
        );
1224
        /* Test that the captain ID has been updated */
1225
        $this->assertGreaterThan(0, $team1->CaptainID);
1226
1227
        /* Fetch the newly created captain */
1228
        $captain1 = DataObjectTest\Player::get()->byID($team1->CaptainID);
1229
        $this->assertEquals('Jim', $captain1->FirstName);
1230
1231
        /* Grab the favourite team and make sure it has the correct values */
1232
        $reloadedTeam1 = $captain1->FavouriteTeam();
1233
        $this->assertEquals($reloadedTeam1->ID, $captain1->FavouriteTeamID);
1234
        $this->assertEquals('New and improved team 1', $reloadedTeam1->Title);
1235
    }
1236
1237
    public function testWritingInvalidDataObjectThrowsException()
1238
    {
1239
        $validatedObject = new DataObjectTest\ValidatedObject();
1240
        $this->setExpectedException(ValidationException::class);
0 ignored issues
show
Deprecated Code introduced by
The method PHPUnit_Framework_TestCase::setExpectedException() has been deprecated with message: Method deprecated since Release 5.2.0; use expectException() instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1241
        $validatedObject->write();
1242
    }
1243
1244
    public function testWritingValidDataObjectDoesntThrowException()
1245
    {
1246
        $validatedObject = new DataObjectTest\ValidatedObject();
1247
        $validatedObject->Name = "Mr. Jones";
0 ignored issues
show
Documentation introduced by
The property Name does not exist on object<SilverStripe\ORM\...ctTest\ValidatedObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1248
1249
        $validatedObject->write();
1250
        $this->assertTrue($validatedObject->isInDB(), "Validated object was not saved to database");
1251
    }
1252
1253
    public function testSubclassCreation()
1254
    {
1255
        /* Creating a new object of a subclass should set the ClassName field correctly */
1256
        $obj = new DataObjectTest\SubTeam();
1257
        $obj->write();
1258
        $this->assertEquals(
1259
            DataObjectTest\SubTeam::class,
1260
            DB::query("SELECT \"ClassName\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $obj->ID")->value()
1261
        );
1262
    }
1263
1264
    public function testForceInsert()
1265
    {
1266
        /* If you set an ID on an object and pass forceInsert = true, then the object should be correctly created */
1267
        $conn = DB::get_conn();
1268
        if (method_exists($conn, 'allowPrimaryKeyEditing')) {
1269
            $conn->allowPrimaryKeyEditing(DataObjectTest\Team::class, true);
1270
        }
1271
        $obj = new DataObjectTest\SubTeam();
1272
        $obj->ID = 1001;
1273
        $obj->Title = 'asdfasdf';
1274
        $obj->SubclassDatabaseField = 'asdfasdf';
0 ignored issues
show
Bug introduced by
The property SubclassDatabaseField does not seem to exist. Did you mean DatabaseField?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1275
        $obj->write(false, true);
1276
        if (method_exists($conn, 'allowPrimaryKeyEditing')) {
1277
            $conn->allowPrimaryKeyEditing(DataObjectTest\Team::class, false);
1278
        }
1279
1280
        $this->assertEquals(
1281
            DataObjectTest\SubTeam::class,
1282
            DB::query("SELECT \"ClassName\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $obj->ID")->value()
1283
        );
1284
1285
        /* Check that it actually saves to the database with the correct ID */
1286
        $this->assertEquals(
1287
            "1001",
1288
            DB::query(
1289
                "SELECT \"ID\" FROM \"DataObjectTest_SubTeam\" WHERE \"SubclassDatabaseField\" = 'asdfasdf'"
1290
            )->value()
1291
        );
1292
        $this->assertEquals(
1293
            "1001",
1294
            DB::query("SELECT \"ID\" FROM \"DataObjectTest_Team\" WHERE \"Title\" = 'asdfasdf'")->value()
1295
        );
1296
    }
1297
1298
    public function testHasOwnTable()
1299
    {
1300
        $schema = DataObject::getSchema();
1301
        /* Test DataObject::has_own_table() returns true if the object has $has_one or $db values */
1302
        $this->assertTrue($schema->classHasTable(DataObjectTest\Player::class));
1303
        $this->assertTrue($schema->classHasTable(DataObjectTest\Team::class));
1304
        $this->assertTrue($schema->classHasTable(DataObjectTest\Fixture::class));
1305
1306
        /* Root DataObject that always have a table, even if they lack both $db and $has_one */
1307
        $this->assertTrue($schema->classHasTable(DataObjectTest\FieldlessTable::class));
1308
1309
        /* Subclasses without $db or $has_one don't have a table */
1310
        $this->assertFalse($schema->classHasTable(DataObjectTest\FieldlessSubTable::class));
1311
1312
        /* Return false if you don't pass it a subclass of DataObject */
1313
        $this->assertFalse($schema->classHasTable(DataObject::class));
1314
        $this->assertFalse($schema->classHasTable(ViewableData::class));
1315
1316
        // Invalid class
1317
        $this->setExpectedException(ReflectionException::class, 'Class ThisIsntADataObject does not exist');
0 ignored issues
show
Deprecated Code introduced by
The method PHPUnit_Framework_TestCase::setExpectedException() has been deprecated with message: Method deprecated since Release 5.2.0; use expectException() instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1318
        $this->assertFalse($schema->classHasTable("ThisIsntADataObject"));
1319
    }
1320
1321
    public function testMerge()
1322
    {
1323
        // test right merge of subclasses
1324
        $left = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
1325
        $right = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam2_with_player_relation');
1326
        $leftOrigID = $left->ID;
1327
        $left->merge($right, 'right', false, false);
0 ignored issues
show
Bug introduced by
It seems like $right defined by $this->objFromFixture(\S..._with_player_relation') on line 1325 can be null; however, SilverStripe\ORM\DataObject::merge() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1328
        $this->assertEquals(
1329
            $left->Title,
1330
            'Subteam 2',
1331
            'merge() with "right" priority overwrites fields with existing values on subclasses'
1332
        );
1333
        $this->assertEquals(
1334
            $left->ID,
1335
            $leftOrigID,
1336
            'merge() with "right" priority doesnt overwrite database ID'
1337
        );
1338
1339
        // test overwriteWithEmpty flag on existing left values
1340
        $left = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam2_with_player_relation');
1341
        $right = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam3_with_empty_fields');
1342
        $left->merge($right, 'right', false, true);
0 ignored issues
show
Bug introduced by
It seems like $right defined by $this->objFromFixture(\S...am3_with_empty_fields') on line 1341 can be null; however, SilverStripe\ORM\DataObject::merge() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1343
        $this->assertEquals(
1344
            $left->Title,
1345
            'Subteam 3',
1346
            'merge() with $overwriteWithEmpty overwrites non-empty fields on left object'
1347
        );
1348
1349
        // test overwriteWithEmpty flag on empty left values
1350
        $left = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
1351
        // $SubclassDatabaseField is empty on here
1352
        $right = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam2_with_player_relation');
1353
        $left->merge($right, 'right', false, true);
0 ignored issues
show
Bug introduced by
It seems like $right defined by $this->objFromFixture(\S..._with_player_relation') on line 1352 can be null; however, SilverStripe\ORM\DataObject::merge() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1354
        $this->assertEquals(
1355
            $left->SubclassDatabaseField,
1356
            null,
1357
            'merge() with $overwriteWithEmpty overwrites empty fields on left object'
1358
        );
1359
1360
        // @todo test "left" priority flag
1361
        // @todo test includeRelations flag
1362
        // @todo test includeRelations in combination with overwriteWithEmpty
1363
        // @todo test has_one relations
1364
        // @todo test has_many and many_many relations
1365
    }
1366
1367
    public function testPopulateDefaults()
1368
    {
1369
        $obj = new DataObjectTest\Fixture();
1370
        $this->assertEquals(
1371
            $obj->MyFieldWithDefault,
1372
            'Default Value',
1373
            'Defaults are populated for in-memory object from $defaults array'
1374
        );
1375
1376
        $this->assertEquals(
1377
            $obj->MyFieldWithAltDefault,
1378
            'Default Value',
1379
            'Defaults are populated from overloaded populateDefaults() method'
1380
        );
1381
    }
1382
1383
    public function testValidateModelDefinitionsFailsWithArray()
1384
    {
1385
        Config::modify()->merge(DataObjectTest\Team::class, 'has_one', array('NotValid' => array('NoArraysAllowed')));
1386
        $this->setExpectedException(InvalidArgumentException::class);
0 ignored issues
show
Deprecated Code introduced by
The method PHPUnit_Framework_TestCase::setExpectedException() has been deprecated with message: Method deprecated since Release 5.2.0; use expectException() instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1387
        DataObject::getSchema()->hasOneComponent(DataObjectTest\Team::class, 'NotValid');
1388
    }
1389
1390
    public function testValidateModelDefinitionsFailsWithIntKey()
1391
    {
1392
        Config::modify()->set(DataObjectTest\Team::class, 'has_many', array(0 => DataObjectTest\Player::class));
1393
        $this->setExpectedException(InvalidArgumentException::class);
0 ignored issues
show
Deprecated Code introduced by
The method PHPUnit_Framework_TestCase::setExpectedException() has been deprecated with message: Method deprecated since Release 5.2.0; use expectException() instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1394
        DataObject::getSchema()->hasManyComponent(DataObjectTest\Team::class, 0);
1395
    }
1396
1397
    public function testValidateModelDefinitionsFailsWithIntValue()
1398
    {
1399
        Config::modify()->merge(DataObjectTest\Team::class, 'many_many', array('Players' => 12));
1400
        $this->setExpectedException(InvalidArgumentException::class);
0 ignored issues
show
Deprecated Code introduced by
The method PHPUnit_Framework_TestCase::setExpectedException() has been deprecated with message: Method deprecated since Release 5.2.0; use expectException() instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1401
        DataObject::getSchema()->manyManyComponent(DataObjectTest\Team::class, 'Players');
1402
    }
1403
1404
    public function testNewClassInstance()
1405
    {
1406
        $dataObject = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1407
        $changedDO = $dataObject->newClassInstance(DataObjectTest\SubTeam::class);
1408
        $changedFields = $changedDO->getChangedFields();
1409
1410
        // Don't write the record, it will reset changed fields
1411
        $this->assertInstanceOf(DataObjectTest\SubTeam::class, $changedDO);
1412
        $this->assertEquals($changedDO->ClassName, DataObjectTest\SubTeam::class);
1413
        $this->assertEquals($changedDO->RecordClassName, DataObjectTest\SubTeam::class);
1414
        $this->assertContains('ClassName', array_keys($changedFields));
1415
        $this->assertEquals($changedFields['ClassName']['before'], DataObjectTest\Team::class);
1416
        $this->assertEquals($changedFields['ClassName']['after'], DataObjectTest\SubTeam::class);
1417
        $this->assertEquals($changedFields['RecordClassName']['before'], DataObjectTest\Team::class);
1418
        $this->assertEquals($changedFields['RecordClassName']['after'], DataObjectTest\SubTeam::class);
1419
1420
        $changedDO->write();
1421
1422
        $this->assertInstanceOf(DataObjectTest\SubTeam::class, $changedDO);
1423
        $this->assertEquals($changedDO->ClassName, DataObjectTest\SubTeam::class);
1424
1425
        // Test invalid classes fail
1426
        $this->setExpectedException('InvalidArgumentException', "Controller is not a valid subclass of DataObject");
0 ignored issues
show
Deprecated Code introduced by
The method PHPUnit_Framework_TestCase::setExpectedException() has been deprecated with message: Method deprecated since Release 5.2.0; use expectException() instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1427
        /**
1428
 * @skipUpgrade
1429
*/
1430
        $dataObject->newClassInstance('Controller');
1431
    }
1432
1433
    public function testMultipleManyManyWithSameClass()
1434
    {
1435
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1436
        $company2 = $this->objFromFixture(DataObjectTest\EquipmentCompany::class, 'equipmentcompany2');
1437
        $sponsors = $team->Sponsors();
1438
        $equipmentSuppliers = $team->EquipmentSuppliers();
1439
1440
        // Check that DataObject::many_many() works as expected
1441
        $manyManyComponent = DataObject::getSchema()->manyManyComponent(DataObjectTest\Team::class, 'Sponsors');
1442
        $this->assertEquals(ManyManyList::class, $manyManyComponent['relationClass']);
1443
        $this->assertEquals(
1444
            DataObjectTest\Team::class,
1445
            $manyManyComponent['parentClass'],
1446
            'DataObject::many_many() didn\'t find the correct base class'
1447
        );
1448
        $this->assertEquals(
1449
            DataObjectTest\EquipmentCompany::class,
1450
            $manyManyComponent['childClass'],
1451
            'DataObject::many_many() didn\'t find the correct target class for the relation'
1452
        );
1453
        $this->assertEquals(
1454
            'DataObjectTest_EquipmentCompany_SponsoredTeams',
1455
            $manyManyComponent['join'],
1456
            'DataObject::many_many() didn\'t find the correct relation table'
1457
        );
1458
        $this->assertEquals('DataObjectTest_TeamID', $manyManyComponent['parentField']);
1459
        $this->assertEquals('DataObjectTest_EquipmentCompanyID', $manyManyComponent['childField']);
1460
1461
        // Check that ManyManyList still works
1462
        $this->assertEquals(2, $sponsors->count(), 'Rows are missing from relation');
1463
        $this->assertEquals(1, $equipmentSuppliers->count(), 'Rows are missing from relation');
1464
1465
        // Check everything works when no relation is present
1466
        $teamWithoutSponsor = $this->objFromFixture(DataObjectTest\Team::class, 'team3');
1467
        $this->assertInstanceOf(ManyManyList::class, $teamWithoutSponsor->Sponsors());
1468
        $this->assertEquals(0, $teamWithoutSponsor->Sponsors()->count());
1469
1470
        // Test that belongs_many_many can be infered from with getNonReciprocalComponent
1471
        $this->assertDOSEquals(
1472
            [
1473
                ['Name' => 'Company corp'],
1474
                ['Name' => 'Team co.'],
1475
            ],
1476
            $team->inferReciprocalComponent(DataObjectTest\EquipmentCompany::class, 'SponsoredTeams')
1477
        );
1478
1479
        // Test that many_many can be infered from getNonReciprocalComponent
1480
        $this->assertDOSEquals(
1481
            [
1482
                ['Title' => 'Team 1'],
1483
                ['Title' => 'Team 2'],
1484
                ['Title' => 'Subteam 1'],
1485
            ],
1486
            $company2->inferReciprocalComponent(DataObjectTest\Team::class, 'Sponsors')
1487
        );
1488
1489
        // Check many_many_extraFields still works
1490
        $equipmentCompany = $this->objFromFixture(DataObjectTest\EquipmentCompany::class, 'equipmentcompany1');
1491
        $equipmentCompany->SponsoredTeams()->add($teamWithoutSponsor, array('SponsorFee' => 1000));
1492
        $sponsoredTeams = $equipmentCompany->SponsoredTeams();
1493
        $this->assertEquals(
1494
            1000,
1495
            $sponsoredTeams->byID($teamWithoutSponsor->ID)->SponsorFee,
1496
            'Data from many_many_extraFields was not stored/extracted correctly'
1497
        );
1498
1499
        // Check subclasses correctly inherit multiple many_manys
1500
        $subTeam = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
1501
        $this->assertEquals(
1502
            2,
1503
            $subTeam->Sponsors()->count(),
1504
            'Child class did not inherit multiple many_manys'
1505
        );
1506
        $this->assertEquals(
1507
            1,
1508
            $subTeam->EquipmentSuppliers()->count(),
1509
            'Child class did not inherit multiple many_manys'
1510
        );
1511
        // Team 2 has one EquipmentCompany sponsor and one SubEquipmentCompany
1512
        $team2 = $this->objFromFixture(DataObjectTest\Team::class, 'team2');
1513
        $this->assertEquals(
1514
            2,
1515
            $team2->Sponsors()->count(),
1516
            'Child class did not inherit multiple belongs_many_manys'
1517
        );
1518
1519
        // Check many_many_extraFields also works from the belongs_many_many side
1520
        $sponsors = $team2->Sponsors();
1521
        $sponsors->add($equipmentCompany, array('SponsorFee' => 750));
1522
        $this->assertEquals(
1523
            750,
1524
            $sponsors->byID($equipmentCompany->ID)->SponsorFee,
1525
            'Data from many_many_extraFields was not stored/extracted correctly'
1526
        );
1527
1528
        $subEquipmentCompany = $this->objFromFixture(DataObjectTest\SubEquipmentCompany::class, 'subequipmentcompany1');
1529
        $subTeam->Sponsors()->add($subEquipmentCompany, array('SponsorFee' => 1200));
1530
        $this->assertEquals(
1531
            1200,
1532
            $subTeam->Sponsors()->byID($subEquipmentCompany->ID)->SponsorFee,
1533
            'Data from inherited many_many_extraFields was not stored/extracted correctly'
1534
        );
1535
    }
1536
1537
    public function testManyManyExtraFields()
1538
    {
1539
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1540
        $schema = DataObject::getSchema();
1541
1542
        // Get all extra fields
1543
        $teamExtraFields = $team->manyManyExtraFields();
1544
        $this->assertEquals(
1545
            array(
1546
            'Players' => array('Position' => 'Varchar(100)')
1547
            ),
1548
            $teamExtraFields
1549
        );
1550
1551
        // Ensure fields from parent classes are included
1552
        $subTeam = singleton(DataObjectTest\SubTeam::class);
1553
        $teamExtraFields = $subTeam->manyManyExtraFields();
1554
        $this->assertEquals(
1555
            array(
1556
            'Players' => array('Position' => 'Varchar(100)'),
1557
            'FormerPlayers' => array('Position' => 'Varchar(100)')
1558
            ),
1559
            $teamExtraFields
1560
        );
1561
1562
        // Extra fields are immediately available on the Team class (defined in $many_many_extraFields)
1563
        $teamExtraFields = $schema->manyManyExtraFieldsForComponent(DataObjectTest\Team::class, 'Players');
1564
        $this->assertEquals(
1565
            $teamExtraFields,
1566
            array(
1567
            'Position' => 'Varchar(100)'
1568
            )
1569
        );
1570
1571
        // We'll have to go through the relation to get the extra fields on Player
1572
        $playerExtraFields = $schema->manyManyExtraFieldsForComponent(DataObjectTest\Player::class, 'Teams');
1573
        $this->assertEquals(
1574
            $playerExtraFields,
1575
            array(
1576
            'Position' => 'Varchar(100)'
1577
            )
1578
        );
1579
1580
        // Iterate through a many-many relationship and confirm that extra fields are included
1581
        $newTeam = new DataObjectTest\Team();
1582
        $newTeam->Title = "New team";
1583
        $newTeam->write();
1584
        $newTeamID = $newTeam->ID;
1585
1586
        $newPlayer = new DataObjectTest\Player();
1587
        $newPlayer->FirstName = "Sam";
1588
        $newPlayer->Surname = "Minnee";
1589
        $newPlayer->write();
1590
1591
        // The idea of Sam as a prop is essentially humourous.
1592
        $newTeam->Players()->add($newPlayer, array("Position" => "Prop"));
1593
1594
        // Requery and uncache everything
1595
        $newTeam->flushCache();
1596
        $newTeam = DataObject::get_by_id(DataObjectTest\Team::class, $newTeamID);
1597
1598
        // Check that the Position many_many_extraField is extracted.
1599
        $player = $newTeam->Players()->first();
1600
        $this->assertEquals('Sam', $player->FirstName);
1601
        $this->assertEquals("Prop", $player->Position);
1602
1603
        // Check that ordering a many-many relation by an aggregate column doesn't fail
1604
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
1605
        $player->Teams()->sort("count(DISTINCT \"DataObjectTest_Team_Players\".\"DataObjectTest_PlayerID\") DESC");
1606
    }
1607
1608
    /**
1609
     * Check that the queries generated for many-many relation queries can have unlimitedRowCount
1610
     * called on them.
1611
     */
1612
    public function testManyManyUnlimitedRowCount()
1613
    {
1614
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
1615
        // TODO: What's going on here?
1616
        $this->assertEquals(2, $player->Teams()->dataQuery()->query()->unlimitedRowCount());
1617
    }
1618
1619
    /**
1620
     * Tests that singular_name() generates sensible defaults.
1621
     */
1622
    public function testSingularName()
1623
    {
1624
        $assertions = array(
1625
            DataObjectTest\Player::class => 'Player',
1626
            DataObjectTest\Team::class => 'Team',
1627
            DataObjectTest\Fixture::class => 'Fixture',
1628
        );
1629
1630
        foreach ($assertions as $class => $expectedSingularName) {
1631
            $this->assertEquals(
1632
                $expectedSingularName,
1633
                singleton($class)->singular_name(),
1634
                "Assert that the singular_name for '$class' is correct."
1635
            );
1636
        }
1637
    }
1638
1639
    /**
1640
     * Tests that plural_name() generates sensible defaults.
1641
     */
1642
    public function testPluralName()
1643
    {
1644
        $assertions = array(
1645
            DataObjectTest\Player::class => 'Players',
1646
            DataObjectTest\Team::class => 'Teams',
1647
            DataObjectTest\Fixture::class => 'Fixtures',
1648
            DataObjectTest\Play::class => 'Plays',
1649
            DataObjectTest\Bogey::class => 'Bogeys',
1650
            DataObjectTest\Ploy::class => 'Ploys',
1651
        );
1652
        i18n::set_locale('en_NZ');
1653
        foreach ($assertions as $class => $expectedPluralName) {
1654
            $this->assertEquals(
1655
                $expectedPluralName,
1656
                DataObject::singleton($class)->plural_name(),
1657
                "Assert that the plural_name for '$class' is correct."
1658
            );
1659
            $this->assertEquals(
1660
                $expectedPluralName,
1661
                DataObject::singleton($class)->i18n_plural_name(),
1662
                "Assert that the i18n_plural_name for '$class' is correct."
1663
            );
1664
        }
1665
    }
1666
1667
    public function testHasDatabaseField()
1668
    {
1669
        $team = singleton(DataObjectTest\Team::class);
1670
        $subteam = singleton(DataObjectTest\SubTeam::class);
1671
1672
        $this->assertTrue(
1673
            $team->hasDatabaseField('Title'),
1674
            "hasOwnDatabaseField() works with \$db fields"
1675
        );
1676
        $this->assertTrue(
1677
            $team->hasDatabaseField('CaptainID'),
1678
            "hasOwnDatabaseField() works with \$has_one fields"
1679
        );
1680
        $this->assertFalse(
1681
            $team->hasDatabaseField('NonExistentField'),
1682
            "hasOwnDatabaseField() doesn't detect non-existend fields"
1683
        );
1684
        $this->assertTrue(
1685
            $team->hasDatabaseField('ExtendedDatabaseField'),
1686
            "hasOwnDatabaseField() works with extended fields"
1687
        );
1688
        $this->assertFalse(
1689
            $team->hasDatabaseField('SubclassDatabaseField'),
1690
            "hasOwnDatabaseField() doesn't pick up fields in subclasses on parent class"
1691
        );
1692
1693
        $this->assertTrue(
1694
            $subteam->hasDatabaseField('SubclassDatabaseField'),
1695
            "hasOwnDatabaseField() picks up fields in subclasses"
1696
        );
1697
    }
1698
1699
    public function testFieldTypes()
1700
    {
1701
        $obj = new DataObjectTest\Fixture();
1702
        $obj->DateField = '1988-01-02';
0 ignored issues
show
Documentation introduced by
The property DateField does not exist on object<SilverStripe\ORM\...DataObjectTest\Fixture>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1703
        $obj->DatetimeField = '1988-03-04 06:30';
0 ignored issues
show
Documentation introduced by
The property DatetimeField does not exist on object<SilverStripe\ORM\...DataObjectTest\Fixture>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1704
        $obj->write();
1705
        $obj->flushCache();
1706
1707
        $obj = DataObject::get_by_id(DataObjectTest\Fixture::class, $obj->ID);
1708
        $this->assertEquals('1988-01-02', $obj->DateField);
1709
        $this->assertEquals('1988-03-04 06:30:00', $obj->DatetimeField);
1710
    }
1711
1712
    public function testTwoSubclassesWithTheSameFieldNameWork()
1713
    {
1714
        // Create two objects of different subclasses, setting the values of fields that are
1715
        // defined separately in each subclass
1716
        $obj1 = new DataObjectTest\SubTeam();
1717
        $obj1->SubclassDatabaseField = "obj1";
0 ignored issues
show
Bug introduced by
The property SubclassDatabaseField does not seem to exist. Did you mean DatabaseField?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1718
        $obj2 = new DataObjectTest\OtherSubclassWithSameField();
1719
        $obj2->SubclassDatabaseField = "obj2";
0 ignored issues
show
Bug introduced by
The property SubclassDatabaseField does not seem to exist. Did you mean DatabaseField?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1720
1721
        // Write them to the database
1722
        $obj1->write();
1723
        $obj2->write();
1724
1725
        // Check that the values of those fields are properly read from the database
1726
        $values = DataObject::get(
1727
            DataObjectTest\Team::class,
1728
            "\"DataObjectTest_Team\".\"ID\" IN
1729
			($obj1->ID, $obj2->ID)"
1730
        )->column("SubclassDatabaseField");
1731
        $this->assertEquals(array_intersect($values, array('obj1', 'obj2')), $values);
1732
    }
1733
1734
    public function testClassNameSetForNewObjects()
1735
    {
1736
        $d = new DataObjectTest\Player();
1737
        $this->assertEquals(DataObjectTest\Player::class, $d->ClassName);
1738
    }
1739
1740
    public function testHasValue()
1741
    {
1742
        $team = new DataObjectTest\Team();
1743
        $this->assertFalse($team->hasValue('Title', null, false));
1744
        $this->assertFalse($team->hasValue('DatabaseField', null, false));
1745
1746
        $team->Title = 'hasValue';
1747
        $this->assertTrue($team->hasValue('Title', null, false));
1748
        $this->assertFalse($team->hasValue('DatabaseField', null, false));
1749
1750
        $team->Title = '<p></p>';
1751
        $this->assertTrue(
1752
            $team->hasValue('Title', null, false),
1753
            'Test that an empty paragraph is a value for non-HTML fields.'
1754
        );
1755
1756
        $team->DatabaseField = 'hasValue';
1757
        $this->assertTrue($team->hasValue('Title', null, false));
1758
        $this->assertTrue($team->hasValue('DatabaseField', null, false));
1759
    }
1760
1761
    public function testHasMany()
1762
    {
1763
        $company = new DataObjectTest\Company();
1764
1765
        $this->assertEquals(
1766
            array (
1767
                'CurrentStaff'     => DataObjectTest\Staff::class,
1768
                'PreviousStaff'    => DataObjectTest\Staff::class
1769
            ),
1770
            $company->hasMany(),
1771
            'has_many strips field name data by default.'
1772
        );
1773
1774
        $this->assertEquals(
1775
            DataObjectTest\Staff::class,
1776
            DataObject::getSchema()->hasManyComponent(DataObjectTest\Company::class, 'CurrentStaff'),
1777
            'has_many strips field name data by default on single relationships.'
1778
        );
1779
1780
        $this->assertEquals(
1781
            array (
1782
                'CurrentStaff'     => DataObjectTest\Staff::class.'.CurrentCompany',
1783
                'PreviousStaff'    => DataObjectTest\Staff::class.'.PreviousCompany'
1784
            ),
1785
            $company->hasMany(false),
1786
            'has_many returns field name data when $classOnly is false.'
1787
        );
1788
1789
        $this->assertEquals(
1790
            DataObjectTest\Staff::class.'.CurrentCompany',
1791
            DataObject::getSchema()->hasManyComponent(DataObjectTest\Company::class, 'CurrentStaff', false),
1792
            'has_many returns field name data on single records when $classOnly is false.'
1793
        );
1794
    }
1795
1796
    public function testGetRemoteJoinField()
1797
    {
1798
        $schema = DataObject::getSchema();
1799
1800
        // Company schema
1801
        $staffJoinField = $schema->getRemoteJoinField(
1802
            DataObjectTest\Company::class,
1803
            'CurrentStaff',
1804
            'has_many',
1805
            $polymorphic
1806
        );
1807
        $this->assertEquals('CurrentCompanyID', $staffJoinField);
1808
        $this->assertFalse($polymorphic, 'DataObjectTest_Company->CurrentStaff is not polymorphic');
1809
        $previousStaffJoinField = $schema->getRemoteJoinField(
1810
            DataObjectTest\Company::class,
1811
            'PreviousStaff',
1812
            'has_many',
1813
            $polymorphic
1814
        );
1815
        $this->assertEquals('PreviousCompanyID', $previousStaffJoinField);
1816
        $this->assertFalse($polymorphic, 'DataObjectTest_Company->PreviousStaff is not polymorphic');
1817
1818
        // CEO Schema
1819
        $this->assertEquals(
1820
            'CEOID',
1821
            $schema->getRemoteJoinField(
1822
                DataObjectTest\CEO::class,
1823
                'Company',
1824
                'belongs_to',
1825
                $polymorphic
1826
            )
1827
        );
1828
        $this->assertFalse($polymorphic, 'DataObjectTest_CEO->Company is not polymorphic');
1829
        $this->assertEquals(
1830
            'PreviousCEOID',
1831
            $schema->getRemoteJoinField(
1832
                DataObjectTest\CEO::class,
1833
                'PreviousCompany',
1834
                'belongs_to',
1835
                $polymorphic
1836
            )
1837
        );
1838
        $this->assertFalse($polymorphic, 'DataObjectTest_CEO->PreviousCompany is not polymorphic');
1839
1840
        // Team schema
1841
        $this->assertEquals(
1842
            'Favourite',
1843
            $schema->getRemoteJoinField(
1844
                DataObjectTest\Team::class,
1845
                'Fans',
1846
                'has_many',
1847
                $polymorphic
1848
            )
1849
        );
1850
        $this->assertTrue($polymorphic, 'DataObjectTest_Team->Fans is polymorphic');
1851
        $this->assertEquals(
1852
            'TeamID',
1853
            $schema->getRemoteJoinField(
1854
                DataObjectTest\Team::class,
1855
                'Comments',
1856
                'has_many',
1857
                $polymorphic
1858
            )
1859
        );
1860
        $this->assertFalse($polymorphic, 'DataObjectTest_Team->Comments is not polymorphic');
1861
    }
1862
1863
    public function testBelongsTo()
1864
    {
1865
        $company = new DataObjectTest\Company();
1866
        $ceo     = new DataObjectTest\CEO();
1867
1868
        $company->Name = 'New Company';
0 ignored issues
show
Documentation introduced by
The property Name does not exist on object<SilverStripe\ORM\...DataObjectTest\Company>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1869
        $company->write();
1870
        $ceo->write();
1871
1872
        // Test belongs_to assignment
1873
        $company->CEOID = $ceo->ID;
0 ignored issues
show
Documentation introduced by
The property CEOID does not exist on object<SilverStripe\ORM\...DataObjectTest\Company>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1874
        $company->write();
1875
1876
        $this->assertEquals($company->ID, $ceo->Company()->ID, 'belongs_to returns the right results.');
1877
1878
        // Test belongs_to can be infered via getNonReciprocalComponent
1879
        // Note: Will be returned as has_many since the belongs_to is ignored.
1880
        $this->assertDOSEquals(
1881
            [['Name' => 'New Company']],
1882
            $ceo->inferReciprocalComponent(DataObjectTest\Company::class, 'CEO')
1883
        );
1884
1885
        // Test has_one to a belongs_to can be infered via getNonReciprocalComponent
1886
        $this->assertEquals(
1887
            $ceo->ID,
1888
            $company->inferReciprocalComponent(DataObjectTest\CEO::class, 'Company')->ID
1889
        );
1890
1891
        // Test automatic creation of class where no assigment exists
1892
        $ceo = new DataObjectTest\CEO();
1893
        $ceo->write();
1894
1895
        $this->assertTrue(
1896
            $ceo->Company() instanceof DataObjectTest\Company,
1897
            'DataObjects across belongs_to relations are automatically created.'
1898
        );
1899
        $this->assertEquals($ceo->ID, $ceo->Company()->CEOID, 'Remote IDs are automatically set.');
1900
1901
        // Write object with components
1902
        $ceo->Name = 'Edward Scissorhands';
0 ignored issues
show
Documentation introduced by
The property Name does not exist on object<SilverStripe\ORM\Tests\DataObjectTest\CEO>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1903
        $ceo->write(false, false, false, true);
1904
        $this->assertTrue($ceo->Company()->isInDB(), 'write() writes belongs_to components to the database.');
1905
1906
        $newCEO = DataObject::get_by_id(DataObjectTest\CEO::class, $ceo->ID);
1907
        $this->assertEquals(
1908
            $ceo->Company()->ID,
1909
            $newCEO->Company()->ID,
1910
            'belongs_to can be retrieved from the database.'
1911
        );
1912
    }
1913
1914
    public function testBelongsToPolymorphic()
1915
    {
1916
        $company = new DataObjectTest\Company();
1917
        $ceo     = new DataObjectTest\CEO();
1918
1919
        $company->write();
1920
        $ceo->write();
1921
1922
        // Test belongs_to assignment
1923
        $company->OwnerID = $ceo->ID;
0 ignored issues
show
Documentation introduced by
The property OwnerID does not exist on object<SilverStripe\ORM\...DataObjectTest\Company>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1924
        $company->OwnerClass = DataObjectTest\CEO::class;
0 ignored issues
show
Documentation introduced by
The property OwnerClass does not exist on object<SilverStripe\ORM\...DataObjectTest\Company>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1925
        $company->write();
1926
1927
        $this->assertEquals($company->ID, $ceo->CompanyOwned()->ID, 'belongs_to returns the right results.');
1928
        $this->assertInstanceOf(
1929
            DataObjectTest\Company::class,
1930
            $ceo->CompanyOwned(),
1931
            'belongs_to returns the right results.'
1932
        );
1933
1934
        // Test automatic creation of class where no assigment exists
1935
        $ceo = new DataObjectTest\CEO();
1936
        $ceo->write();
1937
1938
        $this->assertTrue(
1939
            $ceo->CompanyOwned() instanceof DataObjectTest\Company,
1940
            'DataObjects across polymorphic belongs_to relations are automatically created.'
1941
        );
1942
        $this->assertEquals($ceo->ID, $ceo->CompanyOwned()->OwnerID, 'Remote IDs are automatically set.');
1943
        $this->assertInstanceOf($ceo->CompanyOwned()->OwnerClass, $ceo, 'Remote class is automatically  set');
1944
1945
        // Write object with components
1946
        $ceo->write(false, false, false, true);
1947
        $this->assertTrue($ceo->CompanyOwned()->isInDB(), 'write() writes belongs_to components to the database.');
1948
1949
        $newCEO = DataObject::get_by_id(DataObjectTest\CEO::class, $ceo->ID);
1950
        $this->assertEquals(
1951
            $ceo->CompanyOwned()->ID,
1952
            $newCEO->CompanyOwned()->ID,
1953
            'polymorphic belongs_to can be retrieved from the database.'
1954
        );
1955
    }
1956
1957
    /**
1958
     * @expectedException \LogicException
1959
     */
1960
    public function testInvalidate()
1961
    {
1962
        $do = new DataObjectTest\Fixture();
1963
        $do->write();
1964
1965
        $do->delete();
1966
1967
        $do->delete(); // Prohibit invalid object manipulation
1968
        $do->write();
1969
        $do->duplicate();
1970
    }
1971
1972
    public function testToMap()
1973
    {
1974
        $obj = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
1975
1976
        $map = $obj->toMap();
1977
1978
        $this->assertArrayHasKey('ID', $map, 'Contains base fields');
1979
        $this->assertArrayHasKey('Title', $map, 'Contains fields from parent class');
1980
        $this->assertArrayHasKey('SubclassDatabaseField', $map, 'Contains fields from concrete class');
1981
1982
        $this->assertEquals(
1983
            $obj->ID,
1984
            $map['ID'],
1985
            'Contains values from base fields'
1986
        );
1987
        $this->assertEquals(
1988
            $obj->Title,
1989
            $map['Title'],
1990
            'Contains values from parent class fields'
1991
        );
1992
        $this->assertEquals(
1993
            $obj->SubclassDatabaseField,
1994
            $map['SubclassDatabaseField'],
1995
            'Contains values from concrete class fields'
1996
        );
1997
1998
        $newObj = new DataObjectTest\SubTeam();
1999
        $this->assertArrayHasKey('Title', $map, 'Contains null fields');
2000
    }
2001
2002
    public function testIsEmpty()
2003
    {
2004
        $objEmpty = new DataObjectTest\Team();
2005
        $this->assertTrue($objEmpty->isEmpty(), 'New instance without populated defaults is empty');
2006
2007
        $objEmpty->Title = '0'; //
2008
        $this->assertFalse($objEmpty->isEmpty(), 'Zero value in attribute considered non-empty');
2009
    }
2010
2011
    public function testRelField()
2012
    {
2013
        $captain = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
2014
        // Test traversal of a single has_one
2015
        $this->assertEquals("Team 1", $captain->relField('FavouriteTeam.Title'));
2016
        // Test direct field access
2017
        $this->assertEquals("Captain", $captain->relField('FirstName'));
2018
2019
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
2020
        // Test that we can traverse more than once, and that arbitrary methods are okay
2021
        $this->assertEquals("Team 1", $player->relField('Teams.First.Title'));
2022
2023
        $newPlayer = new DataObjectTest\Player();
2024
        $this->assertNull($newPlayer->relField('Teams.First.Title'));
2025
2026
        // Test that relField works on db field manipulations
2027
        $comment = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment3');
2028
        $this->assertEquals("PHIL IS A UNIQUE GUY, AND COMMENTS ON TEAM2", $comment->relField('Comment.UpperCase'));
2029
    }
2030
2031
    public function testRelObject()
2032
    {
2033
        $captain = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
2034
2035
        // Test traversal of a single has_one
2036
        $this->assertInstanceOf(DBVarchar::class, $captain->relObject('FavouriteTeam.Title'));
2037
        $this->assertEquals("Team 1", $captain->relObject('FavouriteTeam.Title')->getValue());
2038
2039
        // Test direct field access
2040
        $this->assertInstanceOf(DBBoolean::class, $captain->relObject('IsRetired'));
2041
        $this->assertEquals(1, $captain->relObject('IsRetired')->getValue());
2042
2043
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
2044
        // Test that we can traverse more than once, and that arbitrary methods are okay
2045
        $this->assertInstanceOf(DBVarchar::class, $player->relObject('Teams.First.Title'));
2046
        $this->assertEquals("Team 1", $player->relObject('Teams.First.Title')->getValue());
2047
    }
2048
2049
    public function testLateStaticBindingStyle()
2050
    {
2051
        // Confirm that DataObjectTest_Player::get() operates as excepted
2052
        $this->assertEquals(4, DataObjectTest\Player::get()->count());
2053
        $this->assertInstanceOf(DataObjectTest\Player::class, DataObjectTest\Player::get()->first());
2054
2055
        // You can't pass arguments to LSB syntax - use the DataList methods instead.
2056
        $this->setExpectedException('InvalidArgumentException');
0 ignored issues
show
Deprecated Code introduced by
The method PHPUnit_Framework_TestCase::setExpectedException() has been deprecated with message: Method deprecated since Release 5.2.0; use expectException() instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
2057
        DataObjectTest\Player::get(null, "\"ID\" = 1");
2058
    }
2059
2060
    public function testBrokenLateStaticBindingStyle()
2061
    {
2062
        // If you call DataObject::get() you have to pass a first argument
2063
        $this->setExpectedException('InvalidArgumentException');
0 ignored issues
show
Deprecated Code introduced by
The method PHPUnit_Framework_TestCase::setExpectedException() has been deprecated with message: Method deprecated since Release 5.2.0; use expectException() instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
2064
        DataObject::get();
2065
    }
2066
2067
    public function testBigIntField()
2068
    {
2069
        $staff = new DataObjectTest\Staff();
2070
        $staff->Salary = PHP_INT_MAX;
0 ignored issues
show
Documentation introduced by
The property Salary does not exist on object<SilverStripe\ORM\...s\DataObjectTest\Staff>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
2071
        $staff->write();
2072
        $this->assertEquals(PHP_INT_MAX, DataObjectTest\Staff::get()->byID($staff->ID)->Salary);
2073
    }
2074
2075
    public function testGetOneMissingValueReturnsNull()
2076
    {
2077
2078
        // Test that missing values return null
2079
        $this->assertEquals(null, DataObject::get_one(
2080
            DataObjectTest\TeamComment::class,
2081
            ['"DataObjectTest_TeamComment"."Name"' => 'does not exists']
2082
        ));
2083
    }
2084
}
2085