Completed
Pull Request — master (#6724)
by Damian
09:07
created

DataObjectTest::testPluralName()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 24
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 18
nc 2
nop 0
dl 0
loc 24
rs 8.9713
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
    );
55
56
    protected function getExtraDataObjects()
57
    {
58
        return array_merge(
59
            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...
60
            ManyManyListTest::$extra_data_objects
61
        );
62
    }
63
64
    public function testDb()
65
    {
66
        $schema = DataObject::getSchema();
67
        $dbFields = $schema->fieldSpecs(DataObjectTest\TeamComment::class);
68
69
        // Assert fields are included
70
        $this->assertArrayHasKey('Name', $dbFields);
71
72
        // Assert the base fields are included
73
        $this->assertArrayHasKey('Created', $dbFields);
74
        $this->assertArrayHasKey('LastEdited', $dbFields);
75
        $this->assertArrayHasKey('ClassName', $dbFields);
76
        $this->assertArrayHasKey('ID', $dbFields);
77
78
        // Assert that the correct field type is returned when passing a field
79
        $this->assertEquals('Varchar', $schema->fieldSpec(DataObjectTest\TeamComment::class, 'Name'));
80
        $this->assertEquals('Text', $schema->fieldSpec(DataObjectTest\TeamComment::class, 'Comment'));
81
82
        // Test with table required
83
        $this->assertEquals(
84
            DataObjectTest\TeamComment::class.'.Varchar',
85
            $schema->fieldSpec(DataObjectTest\TeamComment::class, 'Name', DataObjectSchema::INCLUDE_CLASS)
86
        );
87
        $this->assertEquals(
88
            DataObjectTest\TeamComment::class.'.Text',
89
            $schema->fieldSpec(DataObjectTest\TeamComment::class, 'Comment', DataObjectSchema::INCLUDE_CLASS)
90
        );
91
        $dbFields = $schema->fieldSpecs(DataObjectTest\ExtendedTeamComment::class);
92
93
        // fixed fields are still included in extended classes
94
        $this->assertArrayHasKey('Created', $dbFields);
95
        $this->assertArrayHasKey('LastEdited', $dbFields);
96
        $this->assertArrayHasKey('ClassName', $dbFields);
97
        $this->assertArrayHasKey('ID', $dbFields);
98
99
        // Assert overloaded fields have correct data type
100
        $this->assertEquals('HTMLText', $schema->fieldSpec(DataObjectTest\ExtendedTeamComment::class, 'Comment'));
101
        $this->assertEquals(
102
            'HTMLText',
103
            $dbFields['Comment'],
104
            'Calls to DataObject::db without a field specified return correct data types'
105
        );
106
107
        // assertEquals doesn't verify the order of array elements, so access keys manually to check order:
108
        // 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...
109
        $this->assertEquals(
110
            array(
111
                'Name',
112
                'Comment'
113
            ),
114
            array_slice(array_keys($dbFields), 4, 2),
115
            'DataObject::db returns fields in correct order'
116
        );
117
    }
118
119
    public function testConstructAcceptsValues()
120
    {
121
        // Values can be an array...
122
        $player = new DataObjectTest\Player(
123
            array(
124
            'FirstName' => 'James',
125
            'Surname' => 'Smith'
126
            )
127
        );
128
129
        $this->assertEquals('James', $player->FirstName);
130
        $this->assertEquals('Smith', $player->Surname);
131
132
        // ... or a stdClass inst
133
        $data = new stdClass();
134
        $data->FirstName = 'John';
135
        $data->Surname = 'Doe';
136
        $player = new DataObjectTest\Player($data);
137
138
        $this->assertEquals('John', $player->FirstName);
139
        $this->assertEquals('Doe', $player->Surname);
140
141
        // IDs should be stored as integers, not strings
142
        $player = new DataObjectTest\Player(array('ID' => '5'));
143
        $this->assertSame(5, $player->ID);
144
    }
145
146
    public function testValidObjectsForBaseFields()
147
    {
148
        $obj = new DataObjectTest\ValidatedObject();
149
150
        foreach (array('Created', 'LastEdited', 'ClassName', 'ID') as $field) {
151
            $helper = $obj->dbObject($field);
152
            $this->assertTrue(
153
                ($helper instanceof DBField),
154
                "for {$field} expected helper to be DBField, but was " .
155
                (is_object($helper) ? get_class($helper) : "null")
156
            );
157
        }
158
    }
159
160
    public function testDataIntegrityWhenTwoSubclassesHaveSameField()
161
    {
162
        // Save data into DataObjectTest_SubTeam.SubclassDatabaseField
163
        $obj = new DataObjectTest\SubTeam();
164
        $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...
165
        $obj->write();
166
167
        // Change the class
168
        $obj->ClassName = DataObjectTest\OtherSubclassWithSameField::class;
169
        $obj->write();
170
        $obj->flushCache();
171
172
        // Re-fetch from the database and confirm that the data is sourced from
173
        // OtherSubclassWithSameField.SubclassDatabaseField
174
        $obj = DataObject::get_by_id(DataObjectTest\Team::class, $obj->ID);
175
        $this->assertNull($obj->SubclassDatabaseField);
176
177
        // Confirm that save the object in the other direction.
178
        $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...
179
        $obj->write();
180
181
        $obj->ClassName = DataObjectTest\SubTeam::class;
182
        $obj->write();
183
        $obj->flushCache();
184
185
        // If we restore the class, the old value has been lying dormant and will be available again.
186
        // NOTE: This behaviour is volatile; we may change this in the future to clear fields that
187
        // are no longer relevant when changing ClassName
188
        $obj = DataObject::get_by_id(DataObjectTest\Team::class, $obj->ID);
189
        $this->assertEquals('obj-SubTeam', $obj->SubclassDatabaseField);
190
    }
191
192
    /**
193
     * Test deletion of DataObjects
194
     *   - Deleting using delete() on the DataObject
195
     *   - Deleting using DataObject::delete_by_id()
196
     */
197
    public function testDelete()
198
    {
199
        // Test deleting using delete() on the DataObject
200
        // Get the first page
201
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
202
        $objID = $obj->ID;
203
        // Check the page exists before deleting
204
        $this->assertTrue(is_object($obj) && $obj->exists());
205
        // Delete the page
206
        $obj->delete();
207
        // Check that page does not exist after deleting
208
        $obj = DataObject::get_by_id(DataObjectTest\Player::class, $objID);
209
        $this->assertTrue(!$obj || !$obj->exists());
210
211
212
        // Test deleting using DataObject::delete_by_id()
213
        // Get the second page
214
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
215
        $objID = $obj->ID;
216
        // Check the page exists before deleting
217
        $this->assertTrue(is_object($obj) && $obj->exists());
218
        // Delete the page
219
        DataObject::delete_by_id(DataObjectTest\Player::class, $obj->ID);
220
        // Check that page does not exist after deleting
221
        $obj = DataObject::get_by_id(DataObjectTest\Player::class, $objID);
222
        $this->assertTrue(!$obj || !$obj->exists());
223
    }
224
225
    /**
226
     * Test methods that get DataObjects
227
     *   - DataObject::get()
228
     *       - All records of a DataObject
229
     *       - Filtering
230
     *       - Sorting
231
     *       - Joins
232
     *       - Limit
233
     *       - Container class
234
     *   - DataObject::get_by_id()
235
     *   - DataObject::get_one()
236
     *        - With and without caching
237
     *        - With and without ordering
238
     */
239
    public function testGet()
240
    {
241
        // Test getting all records of a DataObject
242
        $comments = DataObject::get(DataObjectTest\TeamComment::class);
243
        $this->assertEquals(3, $comments->count());
244
245
        // Test WHERE clause
246
        $comments = DataObject::get(DataObjectTest\TeamComment::class, "\"Name\"='Bob'");
247
        $this->assertEquals(1, $comments->count());
248
        foreach ($comments as $comment) {
249
            $this->assertEquals('Bob', $comment->Name);
250
        }
251
252
        // Test sorting
253
        $comments = DataObject::get(DataObjectTest\TeamComment::class, '', "\"Name\" ASC");
254
        $this->assertEquals(3, $comments->count());
255
        $this->assertEquals('Bob', $comments->first()->Name);
256
        $comments = DataObject::get(DataObjectTest\TeamComment::class, '', "\"Name\" DESC");
257
        $this->assertEquals(3, $comments->count());
258
        $this->assertEquals('Phil', $comments->first()->Name);
259
260
        // Test limit
261
        $comments = DataObject::get(DataObjectTest\TeamComment::class, '', "\"Name\" ASC", '', '1,2');
262
        $this->assertEquals(2, $comments->count());
263
        $this->assertEquals('Joe', $comments->first()->Name);
264
        $this->assertEquals('Phil', $comments->last()->Name);
265
266
        // Test get_by_id()
267
        $captain1ID = $this->idFromFixture(DataObjectTest\Player::class, 'captain1');
268
        $captain1 = DataObject::get_by_id(DataObjectTest\Player::class, $captain1ID);
269
        $this->assertEquals('Captain', $captain1->FirstName);
270
271
        // Test get_one() without caching
272
        $comment1 = DataObject::get_one(
273
            DataObjectTest\TeamComment::class,
274
            array(
275
            '"DataObjectTest_TeamComment"."Name"' => 'Joe'
276
            ),
277
            false
278
        );
279
        $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...
280
281
        $comment2 = DataObject::get_one(
282
            DataObjectTest\TeamComment::class,
283
            array(
284
            '"DataObjectTest_TeamComment"."Name"' => 'Joe'
285
            ),
286
            false
287
        );
288
        $this->assertNotEquals($comment1->Comment, $comment2->Comment);
289
290
        // Test get_one() with caching
291
        $comment1 = DataObject::get_one(
292
            DataObjectTest\TeamComment::class,
293
            array(
294
            '"DataObjectTest_TeamComment"."Name"' => 'Bob'
295
            ),
296
            true
297
        );
298
        $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...
299
300
        $comment2 = DataObject::get_one(
301
            DataObjectTest\TeamComment::class,
302
            array(
303
            '"DataObjectTest_TeamComment"."Name"' => 'Bob'
304
            ),
305
            true
306
        );
307
        $this->assertEquals((string)$comment1->Comment, (string)$comment2->Comment);
308
309
        // Test get_one() with order by without caching
310
        $comment = DataObject::get_one(DataObjectTest\TeamComment::class, '', false, "\"Name\" ASC");
311
        $this->assertEquals('Bob', $comment->Name);
312
313
        $comment = DataObject::get_one(DataObjectTest\TeamComment::class, '', false, "\"Name\" DESC");
314
        $this->assertEquals('Phil', $comment->Name);
315
316
        // Test get_one() with order by with caching
317
        $comment = DataObject::get_one(DataObjectTest\TeamComment::class, '', true, '"Name" ASC');
318
        $this->assertEquals('Bob', $comment->Name);
319
        $comment = DataObject::get_one(DataObjectTest\TeamComment::class, '', true, '"Name" DESC');
320
        $this->assertEquals('Phil', $comment->Name);
321
    }
322
323
    public function testGetCaseInsensitive()
324
    {
325
        // Test get_one() with bad case on the classname
326
        // Note: This will succeed only if the underlying DB server supports case-insensitive
327
        // table names (e.g. such as MySQL, but not SQLite3)
328
        if (!(DB::get_conn() instanceof MySQLDatabase)) {
329
            $this->markTestSkipped('MySQL only');
330
        }
331
332
        $subteam1 = DataObject::get_one(
333
            strtolower(DataObjectTest\SubTeam::class),
334
            array(
335
            '"DataObjectTest_Team"."Title"' => 'Subteam 1'
336
            ),
337
            true
338
        );
339
        $this->assertNotEmpty($subteam1);
340
        $this->assertEquals($subteam1->Title, "Subteam 1");
341
    }
342
343
    public function testGetSubclassFields()
344
    {
345
        /* Test that fields / has_one relations from the parent table and the subclass tables are extracted */
346
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, "captain1");
347
        // Base field
348
        $this->assertEquals('Captain', $captain1->FirstName);
349
        // Subclass field
350
        $this->assertEquals('007', $captain1->ShirtNumber);
351
        // Subclass has_one relation
352
        $this->assertEquals($this->idFromFixture(DataObjectTest\Team::class, 'team1'), $captain1->FavouriteTeamID);
353
    }
354
355
    public function testGetRelationClass()
356
    {
357
        $obj = new DataObjectTest\Player();
358
        $this->assertEquals(
359
            singleton(DataObjectTest\Player::class)->getRelationClass('FavouriteTeam'),
360
            DataObjectTest\Team::class,
361
            'has_one is properly inspected'
362
        );
363
        $this->assertEquals(
364
            singleton(DataObjectTest\Company::class)->getRelationClass('CurrentStaff'),
365
            DataObjectTest\Staff::class,
366
            'has_many is properly inspected'
367
        );
368
        $this->assertEquals(
369
            singleton(DataObjectTest\Team::class)->getRelationClass('Players'),
370
            DataObjectTest\Player::class,
371
            'many_many is properly inspected'
372
        );
373
        $this->assertEquals(
374
            singleton(DataObjectTest\Player::class)->getRelationClass('Teams'),
375
            DataObjectTest\Team::class,
376
            'belongs_many_many is properly inspected'
377
        );
378
        $this->assertEquals(
379
            singleton(DataObjectTest\CEO::class)->getRelationClass('Company'),
380
            DataObjectTest\Company::class,
381
            'belongs_to is properly inspected'
382
        );
383
        $this->assertEquals(
384
            singleton(DataObjectTest\Fan::class)->getRelationClass('Favourite'),
385
            DataObject::class,
386
            'polymorphic has_one is properly inspected'
387
        );
388
    }
389
390
    /**
391
     * Test that has_one relations can be retrieved
392
     */
393
    public function testGetHasOneRelations()
394
    {
395
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, "captain1");
396
        $team1ID = $this->idFromFixture(DataObjectTest\Team::class, 'team1');
397
398
        // There will be a field called (relname)ID that contains the ID of the
399
        // object linked to via the has_one relation
400
        $this->assertEquals($team1ID, $captain1->FavouriteTeamID);
401
402
        // There will be a method called $obj->relname() that returns the object itself
403
        $this->assertEquals($team1ID, $captain1->FavouriteTeam()->ID);
404
405
        // Test that getNonReciprocalComponent can find has_one from the has_many end
406
        $this->assertEquals(
407
            $team1ID,
408
            $captain1->inferReciprocalComponent(DataObjectTest\Team::class, 'PlayerFans')->ID
409
        );
410
411
        // Check entity with polymorphic has-one
412
        $fan1 = $this->objFromFixture(DataObjectTest\Fan::class, "fan1");
413
        $this->assertTrue((bool)$fan1->hasValue('Favourite'));
414
415
        // There will be fields named (relname)ID and (relname)Class for polymorphic
416
        // entities
417
        $this->assertEquals($team1ID, $fan1->FavouriteID);
418
        $this->assertEquals(DataObjectTest\Team::class, $fan1->FavouriteClass);
419
420
        // There will be a method called $obj->relname() that returns the object itself
421
        $favourite = $fan1->Favourite();
422
        $this->assertEquals($team1ID, $favourite->ID);
423
        $this->assertInstanceOf(DataObjectTest\Team::class, $favourite);
424
425
        // check behaviour of dbObject with polymorphic relations
426
        $favouriteDBObject = $fan1->dbObject('Favourite');
427
        $favouriteValue = $favouriteDBObject->getValue();
428
        $this->assertInstanceOf(DBPolymorphicForeignKey::class, $favouriteDBObject);
429
        $this->assertEquals($favourite->ID, $favouriteValue->ID);
430
        $this->assertEquals($favourite->ClassName, $favouriteValue->ClassName);
431
    }
432
433
    public function testLimitAndCount()
434
    {
435
        $players = DataObject::get(DataObjectTest\Player::class);
436
437
        // There's 4 records in total
438
        $this->assertEquals(4, $players->count());
439
440
        // Testing "##, ##" syntax
441
        $this->assertEquals(4, $players->limit(20)->count());
442
        $this->assertEquals(4, $players->limit(20, 0)->count());
443
        $this->assertEquals(0, $players->limit(20, 20)->count());
444
        $this->assertEquals(2, $players->limit(2, 0)->count());
445
        $this->assertEquals(1, $players->limit(5, 3)->count());
446
    }
447
448
    /**
449
     * Test writing of database columns which don't correlate to a DBField,
450
     * e.g. all relation fields on has_one/has_many like "ParentID".
451
     */
452
    public function testWritePropertyWithoutDBField()
453
    {
454
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
455
        $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...
456
        $obj->write();
457
458
        // reload the page from the database
459
        $savedObj = DataObject::get_by_id(DataObjectTest\Player::class, $obj->ID);
460
        $this->assertTrue($savedObj->FavouriteTeamID == 99);
461
462
        // Test with porymorphic relation
463
        $obj2 = $this->objFromFixture(DataObjectTest\Fan::class, "fan1");
464
        $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...
465
        $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...
466
        $obj2->write();
467
468
        $savedObj2 = DataObject::get_by_id(DataObjectTest\Fan::class, $obj2->ID);
469
        $this->assertTrue($savedObj2->FavouriteID == 99);
470
        $this->assertTrue($savedObj2->FavouriteClass == DataObjectTest\Player::class);
471
    }
472
473
    /**
474
     * Test has many relationships
475
     *   - Test getComponents() gets the ComponentSet of the other side of the relation
476
     *   - Test the IDs on the DataObjects are set correctly
477
     */
478
    public function testHasManyRelationships()
479
    {
480
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
481
482
        // Test getComponents() gets the ComponentSet of the other side of the relation
483
        $this->assertTrue($team1->Comments()->count() == 2);
484
485
        $team1Comments = [
486
            ['Comment' => 'This is a team comment by Joe'],
487
            ['Comment' => 'This is a team comment by Bob'],
488
        ];
489
490
        // Test the IDs on the DataObjects are set correctly
491
        $this->assertDOSEquals($team1Comments, $team1->Comments());
492
493
        // Test that has_many can be infered from the has_one via getNonReciprocalComponent
494
        $this->assertDOSEquals(
495
            $team1Comments,
496
            $team1->inferReciprocalComponent(DataObjectTest\TeamComment::class, 'Team')
497
        );
498
499
        // Test that we can add and remove items that already exist in the database
500
        $newComment = new DataObjectTest\TeamComment();
501
        $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...
502
        $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...
503
        $newComment->write();
504
        $team1->Comments()->add($newComment);
505
        $this->assertEquals($team1->ID, $newComment->TeamID);
506
507
        $comment1 = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment1');
508
        $comment2 = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment2');
509
        $team1->Comments()->remove($comment2);
510
511
        $team1CommentIDs = $team1->Comments()->sort('ID')->column('ID');
512
        $this->assertEquals(array($comment1->ID, $newComment->ID), $team1CommentIDs);
513
514
        // Test that removing an item from a list doesn't remove it from the same
515
        // relation belonging to a different object
516
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
517
        $team2 = $this->objFromFixture(DataObjectTest\Team::class, 'team2');
518
        $team2->Comments()->remove($comment1);
519
        $team1CommentIDs = $team1->Comments()->sort('ID')->column('ID');
520
        $this->assertEquals(array($comment1->ID, $newComment->ID), $team1CommentIDs);
521
    }
522
523
524
    /**
525
     * Test has many relationships against polymorphic has_one fields
526
     *   - Test getComponents() gets the ComponentSet of the other side of the relation
527
     *   - Test the IDs on the DataObjects are set correctly
528
     */
529
    public function testHasManyPolymorphicRelationships()
530
    {
531
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
532
533
        // Test getComponents() gets the ComponentSet of the other side of the relation
534
        $this->assertTrue($team1->Fans()->count() == 2);
535
536
        // Test the IDs/Classes on the DataObjects are set correctly
537
        foreach ($team1->Fans() as $fan) {
538
            $this->assertEquals($team1->ID, $fan->FavouriteID, 'Fan has the correct FavouriteID');
539
            $this->assertEquals(DataObjectTest\Team::class, $fan->FavouriteClass, 'Fan has the correct FavouriteClass');
540
        }
541
542
        // Test that we can add and remove items that already exist in the database
543
        $newFan = new DataObjectTest\Fan();
544
        $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...
545
        $newFan->write();
546
        $team1->Fans()->add($newFan);
547
        $this->assertEquals($team1->ID, $newFan->FavouriteID, 'Newly created fan has the correct FavouriteID');
548
        $this->assertEquals(
549
            DataObjectTest\Team::class,
550
            $newFan->FavouriteClass,
551
            'Newly created fan has the correct FavouriteClass'
552
        );
553
554
        $fan1 = $this->objFromFixture(DataObjectTest\Fan::class, 'fan1');
555
        $fan3 = $this->objFromFixture(DataObjectTest\Fan::class, 'fan3');
556
        $team1->Fans()->remove($fan3);
557
558
        $team1FanIDs = $team1->Fans()->sort('ID')->column('ID');
559
        $this->assertEquals(array($fan1->ID, $newFan->ID), $team1FanIDs);
560
561
        // Test that removing an item from a list doesn't remove it from the same
562
        // relation belonging to a different object
563
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
564
        $player1 = $this->objFromFixture(DataObjectTest\Player::class, 'player1');
565
        $player1->Fans()->remove($fan1);
566
        $team1FanIDs = $team1->Fans()->sort('ID')->column('ID');
567
        $this->assertEquals(array($fan1->ID, $newFan->ID), $team1FanIDs);
568
    }
569
570
571
    public function testHasOneRelationship()
572
    {
573
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
574
        $player1 = $this->objFromFixture(DataObjectTest\Player::class, 'player1');
575
        $player2 = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
576
        $fan1 = $this->objFromFixture(DataObjectTest\Fan::class, 'fan1');
577
578
        // Test relation probing
579
        $this->assertFalse((bool)$team1->hasValue('Captain', null, false));
580
        $this->assertFalse((bool)$team1->hasValue('CaptainID', null, false));
581
582
        // Add a captain to team 1
583
        $team1->setField('CaptainID', $player1->ID);
584
        $team1->write();
585
586
        $this->assertTrue((bool)$team1->hasValue('Captain', null, false));
587
        $this->assertTrue((bool)$team1->hasValue('CaptainID', null, false));
588
589
        $this->assertEquals(
590
            $player1->ID,
591
            $team1->Captain()->ID,
592
            'The captain exists for team 1'
593
        );
594
        $this->assertEquals(
595
            $player1->ID,
596
            $team1->getComponent('Captain')->ID,
597
            'The captain exists through the component getter'
598
        );
599
600
        $this->assertEquals(
601
            $team1->Captain()->FirstName,
602
            'Player 1',
603
            'Player 1 is the captain'
604
        );
605
        $this->assertEquals(
606
            $team1->getComponent('Captain')->FirstName,
607
            'Player 1',
608
            'Player 1 is the captain'
609
        );
610
611
        $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...
612
        $team1->write();
613
614
        $this->assertEquals($player2->ID, $team1->Captain()->ID);
615
        $this->assertEquals($player2->ID, $team1->getComponent('Captain')->ID);
616
        $this->assertEquals('Player 2', $team1->Captain()->FirstName);
617
        $this->assertEquals('Player 2', $team1->getComponent('Captain')->FirstName);
618
619
620
        // Set the favourite team for fan1
621
        $fan1->setField('FavouriteID', $team1->ID);
622
        $fan1->setField('FavouriteClass', $team1->class);
623
624
        $this->assertEquals($team1->ID, $fan1->Favourite()->ID, 'The team is assigned to fan 1');
625
        $this->assertInstanceOf($team1->class, $fan1->Favourite(), 'The team is assigned to fan 1');
626
        $this->assertEquals(
627
            $team1->ID,
628
            $fan1->getComponent('Favourite')->ID,
629
            'The team exists through the component getter'
630
        );
631
        $this->assertInstanceOf(
632
            $team1->class,
633
            $fan1->getComponent('Favourite'),
634
            'The team exists through the component getter'
635
        );
636
637
        $this->assertEquals(
638
            $fan1->Favourite()->Title,
639
            'Team 1',
640
            'Team 1 is the favourite'
641
        );
642
        $this->assertEquals(
643
            $fan1->getComponent('Favourite')->Title,
644
            'Team 1',
645
            'Team 1 is the favourite'
646
        );
647
    }
648
649
    /**
650
     * @todo Extend type change tests (e.g. '0'==NULL)
651
     */
652
    public function testChangedFields()
653
    {
654
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
655
        $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...
656
        $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...
657
658
        $this->assertEquals(
659
            $obj->getChangedFields(true, DataObject::CHANGE_STRICT),
660
            array(
661
                'FirstName' => array(
662
                    'before' => 'Captain',
663
                    'after' => 'Captain-changed',
664
                    'level' => DataObject::CHANGE_VALUE
665
                ),
666
                'IsRetired' => array(
667
                    'before' => 1,
668
                    'after' => true,
669
                    'level' => DataObject::CHANGE_STRICT
670
                )
671
            ),
672
            'Changed fields are correctly detected with strict type changes (level=1)'
673
        );
674
675
        $this->assertEquals(
676
            $obj->getChangedFields(true, DataObject::CHANGE_VALUE),
677
            array(
678
                'FirstName' => array(
679
                    'before'=>'Captain',
680
                    'after'=>'Captain-changed',
681
                    'level' => DataObject::CHANGE_VALUE
682
                )
683
            ),
684
            'Changed fields are correctly detected while ignoring type changes (level=2)'
685
        );
686
687
        $newObj = new DataObjectTest\Player();
688
        $newObj->FirstName = "New Player";
689
        $this->assertEquals(
690
            array(
691
                'FirstName' => array(
692
                    'before' => null,
693
                    'after' => 'New Player',
694
                    'level' => DataObject::CHANGE_VALUE
695
                )
696
            ),
697
            $newObj->getChangedFields(true, DataObject::CHANGE_VALUE),
698
            'Initialised fields are correctly detected as full changes'
699
        );
700
    }
701
702
    /**
703
     * @skipUpgrade
704
     */
705
    public function testIsChanged()
706
    {
707
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
708
        $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...
709
        $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...
710
        $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...
711
712
        // Now that DB fields are changed, isChanged is true
713
        $this->assertTrue($obj->isChanged('NonDBField'));
714
        $this->assertFalse($obj->isChanged('NonField'));
715
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_STRICT));
716
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_VALUE));
717
        $this->assertTrue($obj->isChanged('IsRetired', DataObject::CHANGE_STRICT));
718
        $this->assertFalse($obj->isChanged('IsRetired', DataObject::CHANGE_VALUE));
719
        $this->assertFalse($obj->isChanged('Email', 1), 'Doesnt change mark unchanged property');
720
        $this->assertFalse($obj->isChanged('Email', 2), 'Doesnt change mark unchanged property');
721
722
        $newObj = new DataObjectTest\Player();
723
        $newObj->FirstName = "New Player";
724
        $this->assertTrue($newObj->isChanged('FirstName', DataObject::CHANGE_STRICT));
725
        $this->assertTrue($newObj->isChanged('FirstName', DataObject::CHANGE_VALUE));
726
        $this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_STRICT));
727
        $this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_VALUE));
728
729
        $newObj->write();
730
        $this->assertFalse($newObj->ischanged());
731
        $this->assertFalse($newObj->isChanged('FirstName', DataObject::CHANGE_STRICT));
732
        $this->assertFalse($newObj->isChanged('FirstName', DataObject::CHANGE_VALUE));
733
        $this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_STRICT));
734
        $this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_VALUE));
735
736
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
737
        $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...
738
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_STRICT));
739
        $this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_VALUE));
740
741
        /* Test when there's not field provided */
742
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain2');
743
        $this->assertFalse($obj->isChanged());
744
        $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...
745
        $this->assertFalse($obj->isChanged());
746
        $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...
747
        $this->assertTrue($obj->isChanged());
748
749
        $obj->write();
750
        $this->assertFalse($obj->isChanged());
751
    }
752
753
    public function testRandomSort()
754
    {
755
        /* If we perform the same regularly sorted query twice, it should return the same results */
756
        $itemsA = DataObject::get(DataObjectTest\TeamComment::class, "", "ID");
757
        foreach ($itemsA as $item) {
758
            $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...
759
        }
760
761
        $itemsB = DataObject::get(DataObjectTest\TeamComment::class, "", "ID");
762
        foreach ($itemsB as $item) {
763
            $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...
764
        }
765
766
        /* Test when there's not field provided */
767
        $obj = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
768
        $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...
769
        $this->assertTrue($obj->isChanged());
770
771
        $obj->write();
772
        $this->assertFalse($obj->isChanged());
773
774
        /* If we perform the same random query twice, it shouldn't return the same results */
775
        $itemsA = DataObject::get(DataObjectTest\TeamComment::class, "", DB::get_conn()->random());
776
        $itemsB = DataObject::get(DataObjectTest\TeamComment::class, "", DB::get_conn()->random());
777
        $itemsC = DataObject::get(DataObjectTest\TeamComment::class, "", DB::get_conn()->random());
778
        $itemsD = DataObject::get(DataObjectTest\TeamComment::class, "", DB::get_conn()->random());
779
        foreach ($itemsA as $item) {
780
            $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...
781
        }
782
        foreach ($itemsB as $item) {
783
            $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...
784
        }
785
        foreach ($itemsC as $item) {
786
            $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...
787
        }
788
        foreach ($itemsD as $item) {
789
            $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...
790
        }
791
792
        // These shouldn't all be the same (run it 4 times to minimise chance of an accidental collision)
793
        // There's about a 1 in a billion chance of an accidental collision
794
        $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...
795
    }
796
797
    public function testWriteSavesToHasOneRelations()
798
    {
799
        /* DataObject::write() should save to a has_one relationship if you set a field called (relname)ID */
800
        $team = new DataObjectTest\Team();
801
        $captainID = $this->idFromFixture(DataObjectTest\Player::class, 'player1');
802
        $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...
803
        $team->write();
804
        $this->assertEquals(
805
            $captainID,
806
            DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $team->ID")->value()
807
        );
808
809
        /* After giving it a value, you should also be able to set it back to null */
810
        $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...
811
        $team->write();
812
        $this->assertEquals(
813
            0,
814
            DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $team->ID")->value()
815
        );
816
817
        /* You should also be able to save a blank to it when it's first created */
818
        $team = new DataObjectTest\Team();
819
        $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...
820
        $team->write();
821
        $this->assertEquals(
822
            0,
823
            DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $team->ID")->value()
824
        );
825
826
        /* Ditto for existing records without a value */
827
        $existingTeam = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
828
        $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...
829
        $existingTeam->write();
830
        $this->assertEquals(
831
            0,
832
            DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $existingTeam->ID")->value()
833
        );
834
    }
835
836
    public function testCanAccessHasOneObjectsAsMethods()
837
    {
838
        /* If you have a has_one relation 'Captain' on $obj, and you set the $obj->CaptainID = (ID), then the
839
        * object itself should be accessible as $obj->Captain() */
840
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
841
        $captainID = $this->idFromFixture(DataObjectTest\Player::class, 'captain1');
842
843
        $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...
844
        $this->assertNotNull($team->Captain());
845
        $this->assertEquals($captainID, $team->Captain()->ID);
846
847
        // Test for polymorphic has_one relations
848
        $fan = $this->objFromFixture(DataObjectTest\Fan::class, 'fan1');
849
        $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...
850
        $fan->FavouriteClass = $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...
851
        $this->assertNotNull($fan->Favourite());
852
        $this->assertEquals($team->ID, $fan->Favourite()->ID);
853
        $this->assertInstanceOf($team->class, $fan->Favourite());
854
    }
855
856
    public function testFieldNamesThatMatchMethodNamesWork()
857
    {
858
        /* Check that a field name that corresponds to a method on DataObject will still work */
859
        $obj = new DataObjectTest\Fixture();
860
        $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...
861
        $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...
862
        $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...
863
        $obj->write();
864
865
        $this->assertNotNull($obj->ID);
866
        $this->assertEquals(
867
            'value1',
868
            DB::query("SELECT \"Data\" FROM \"DataObjectTest_Fixture\" WHERE \"ID\" = $obj->ID")->value()
869
        );
870
        $this->assertEquals(
871
            'value2',
872
            DB::query("SELECT \"DbObject\" FROM \"DataObjectTest_Fixture\" WHERE \"ID\" = $obj->ID")->value()
873
        );
874
        $this->assertEquals(
875
            'value3',
876
            DB::query("SELECT \"Duplicate\" FROM \"DataObjectTest_Fixture\" WHERE \"ID\" = $obj->ID")->value()
877
        );
878
    }
879
880
    /**
881
     * @todo Re-enable all test cases for field existence after behaviour has been fixed
882
     */
883
    public function testFieldExistence()
884
    {
885
        $teamInstance = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
886
        $teamSingleton = singleton(DataObjectTest\Team::class);
887
888
        $subteamInstance = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
889
        $schema = DataObject::getSchema();
890
891
        /* hasField() singleton checks */
892
        $this->assertTrue(
893
            $teamSingleton->hasField('ID'),
894
            'hasField() finds built-in fields in singletons'
895
        );
896
        $this->assertTrue(
897
            $teamSingleton->hasField('Title'),
898
            'hasField() finds custom fields in singletons'
899
        );
900
901
        /* hasField() instance checks */
902
        $this->assertFalse(
903
            $teamInstance->hasField('NonExistingField'),
904
            'hasField() doesnt find non-existing fields in instances'
905
        );
906
        $this->assertTrue(
907
            $teamInstance->hasField('ID'),
908
            'hasField() finds built-in fields in instances'
909
        );
910
        $this->assertTrue(
911
            $teamInstance->hasField('Created'),
912
            'hasField() finds built-in fields in instances'
913
        );
914
        $this->assertTrue(
915
            $teamInstance->hasField('DatabaseField'),
916
            'hasField() finds custom fields in instances'
917
        );
918
        //$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...
919
        //'hasField() doesnt find subclass fields in parentclass instances');
920
        $this->assertTrue(
921
            $teamInstance->hasField('DynamicField'),
922
            'hasField() finds dynamic getters in instances'
923
        );
924
        $this->assertTrue(
925
            $teamInstance->hasField('HasOneRelationshipID'),
926
            'hasField() finds foreign keys in instances'
927
        );
928
        $this->assertTrue(
929
            $teamInstance->hasField('ExtendedDatabaseField'),
930
            'hasField() finds extended fields in instances'
931
        );
932
        $this->assertTrue(
933
            $teamInstance->hasField('ExtendedHasOneRelationshipID'),
934
            'hasField() finds extended foreign keys in instances'
935
        );
936
        //$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...
937
        //'hasField() includes extended dynamic getters in instances');
938
939
        /* hasField() subclass checks */
940
        $this->assertTrue(
941
            $subteamInstance->hasField('ID'),
942
            'hasField() finds built-in fields in subclass instances'
943
        );
944
        $this->assertTrue(
945
            $subteamInstance->hasField('Created'),
946
            'hasField() finds built-in fields in subclass instances'
947
        );
948
        $this->assertTrue(
949
            $subteamInstance->hasField('DatabaseField'),
950
            'hasField() finds custom fields in subclass instances'
951
        );
952
        $this->assertTrue(
953
            $subteamInstance->hasField('SubclassDatabaseField'),
954
            'hasField() finds custom fields in subclass instances'
955
        );
956
        $this->assertTrue(
957
            $subteamInstance->hasField('DynamicField'),
958
            'hasField() finds dynamic getters in subclass instances'
959
        );
960
        $this->assertTrue(
961
            $subteamInstance->hasField('HasOneRelationshipID'),
962
            'hasField() finds foreign keys in subclass instances'
963
        );
964
        $this->assertTrue(
965
            $subteamInstance->hasField('ExtendedDatabaseField'),
966
            'hasField() finds extended fields in subclass instances'
967
        );
968
        $this->assertTrue(
969
            $subteamInstance->hasField('ExtendedHasOneRelationshipID'),
970
            'hasField() finds extended foreign keys in subclass instances'
971
        );
972
973
        /* hasDatabaseField() singleton checks */
974
        //$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...
975
        //'hasDatabaseField() finds built-in fields in singletons');
976
        $this->assertNotEmpty(
977
            $schema->fieldSpec(DataObjectTest\Team::class, 'Title'),
978
            'hasDatabaseField() finds custom fields in singletons'
979
        );
980
981
        /* hasDatabaseField() instance checks */
982
        $this->assertNull(
983
            $schema->fieldSpec(DataObjectTest\Team::class, 'NonExistingField'),
984
            'hasDatabaseField() doesnt find non-existing fields in instances'
985
        );
986
        //$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...
987
        //'hasDatabaseField() finds built-in fields in instances');
988
        $this->assertNotEmpty(
989
            $schema->fieldSpec(DataObjectTest\Team::class, 'Created'),
990
            'hasDatabaseField() finds built-in fields in instances'
991
        );
992
        $this->assertNotEmpty(
993
            $schema->fieldSpec(DataObjectTest\Team::class, 'DatabaseField'),
994
            'hasDatabaseField() finds custom fields in instances'
995
        );
996
        $this->assertNull(
997
            $schema->fieldSpec(DataObjectTest\Team::class, 'SubclassDatabaseField'),
998
            'hasDatabaseField() doesnt find subclass fields in parentclass instances'
999
        );
1000
        //$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...
1001
        //'hasDatabaseField() doesnt dynamic getters in instances');
1002
        $this->assertNotEmpty(
1003
            $schema->fieldSpec(DataObjectTest\Team::class, 'HasOneRelationshipID'),
1004
            'hasDatabaseField() finds foreign keys in instances'
1005
        );
1006
        $this->assertNotEmpty(
1007
            $schema->fieldSpec(DataObjectTest\Team::class, 'ExtendedDatabaseField'),
1008
            'hasDatabaseField() finds extended fields in instances'
1009
        );
1010
        $this->assertNotEmpty(
1011
            $schema->fieldSpec(DataObjectTest\Team::class, 'ExtendedHasOneRelationshipID'),
1012
            'hasDatabaseField() finds extended foreign keys in instances'
1013
        );
1014
        $this->assertNull(
1015
            $schema->fieldSpec(DataObjectTest\Team::class, 'ExtendedDynamicField'),
1016
            'hasDatabaseField() doesnt include extended dynamic getters in instances'
1017
        );
1018
1019
        /* hasDatabaseField() subclass checks */
1020
        $this->assertNotEmpty(
1021
            $schema->fieldSpec(DataObjectTest\SubTeam::class, 'DatabaseField'),
1022
            'hasField() finds custom fields in subclass instances'
1023
        );
1024
        $this->assertNotEmpty(
1025
            $schema->fieldSpec(DataObjectTest\SubTeam::class, 'SubclassDatabaseField'),
1026
            'hasField() finds custom fields in subclass instances'
1027
        );
1028
    }
1029
1030
    /**
1031
     * @todo Re-enable all test cases for field inheritance aggregation after behaviour has been fixed
1032
     */
1033
    public function testFieldInheritance()
1034
    {
1035
        $schema = DataObject::getSchema();
1036
1037
        // Test logical fields (including composite)
1038
        $teamSpecifications = $schema->fieldSpecs(DataObjectTest\Team::class);
1039
        $this->assertEquals(
1040
            array(
1041
                'ID',
1042
                'ClassName',
1043
                'LastEdited',
1044
                'Created',
1045
                'Title',
1046
                'DatabaseField',
1047
                'ExtendedDatabaseField',
1048
                'CaptainID',
1049
                'FounderID',
1050
                'HasOneRelationshipID',
1051
                'ExtendedHasOneRelationshipID'
1052
            ),
1053
            array_keys($teamSpecifications),
1054
            'fieldSpecifications() contains all fields defined on instance: base, extended and foreign keys'
1055
        );
1056
1057
        $teamFields = $schema->databaseFields(DataObjectTest\Team::class, false);
1058
        $this->assertEquals(
1059
            array(
1060
                'ID',
1061
                'ClassName',
1062
                'LastEdited',
1063
                'Created',
1064
                'Title',
1065
                'DatabaseField',
1066
                'ExtendedDatabaseField',
1067
                'CaptainID',
1068
                'FounderID',
1069
                'HasOneRelationshipID',
1070
                'ExtendedHasOneRelationshipID'
1071
            ),
1072
            array_keys($teamFields),
1073
            'databaseFields() contains only fields defined on instance, including base, extended and foreign keys'
1074
        );
1075
1076
        $subteamSpecifications = $schema->fieldSpecs(DataObjectTest\SubTeam::class);
1077
        $this->assertEquals(
1078
            array(
1079
                'ID',
1080
                'ClassName',
1081
                'LastEdited',
1082
                'Created',
1083
                'Title',
1084
                'DatabaseField',
1085
                'ExtendedDatabaseField',
1086
                'CaptainID',
1087
                'FounderID',
1088
                'HasOneRelationshipID',
1089
                'ExtendedHasOneRelationshipID',
1090
                'SubclassDatabaseField',
1091
                'ParentTeamID',
1092
            ),
1093
            array_keys($subteamSpecifications),
1094
            'fieldSpecifications() on subclass contains all fields, including base, extended  and foreign keys'
1095
        );
1096
1097
        $subteamFields = $schema->databaseFields(DataObjectTest\SubTeam::class, false);
1098
        $this->assertEquals(
1099
            array(
1100
                'ID',
1101
                'SubclassDatabaseField',
1102
                'ParentTeamID',
1103
            ),
1104
            array_keys($subteamFields),
1105
            'databaseFields() on subclass contains only fields defined on instance'
1106
        );
1107
    }
1108
1109
    public function testSearchableFields()
1110
    {
1111
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
1112
        $fields = $player->searchableFields();
1113
        $this->assertArrayHasKey(
1114
            'IsRetired',
1115
            $fields,
1116
            'Fields defined by $searchable_fields static are correctly detected'
1117
        );
1118
        $this->assertArrayHasKey(
1119
            'ShirtNumber',
1120
            $fields,
1121
            'Fields defined by $searchable_fields static are correctly detected'
1122
        );
1123
1124
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1125
        $fields = $team->searchableFields();
1126
        $this->assertArrayHasKey(
1127
            'Title',
1128
            $fields,
1129
            'Fields can be inherited from the $summary_fields static, including methods called on fields'
1130
        );
1131
        $this->assertArrayHasKey(
1132
            'Captain.ShirtNumber',
1133
            $fields,
1134
            'Fields on related objects can be inherited from the $summary_fields static'
1135
        );
1136
        $this->assertArrayHasKey(
1137
            'Captain.FavouriteTeam.Title',
1138
            $fields,
1139
            'Fields on related objects can be inherited from the $summary_fields static'
1140
        );
1141
1142
        $testObj = new DataObjectTest\Fixture();
1143
        $fields = $testObj->searchableFields();
1144
        $this->assertEmpty($fields);
1145
    }
1146
1147
    public function testCastingHelper()
1148
    {
1149
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1150
1151
        $this->assertEquals('Varchar', $team->castingHelper('Title'), 'db field wasn\'t casted correctly');
1152
        $this->assertEquals('HTMLVarchar', $team->castingHelper('DatabaseField'), 'db field wasn\'t casted correctly');
1153
1154
        $sponsor = $team->Sponsors()->first();
1155
        $this->assertEquals('Int', $sponsor->castingHelper('SponsorFee'), 'many_many_extraFields not casted correctly');
1156
    }
1157
1158
    public function testSummaryFieldsCustomLabels()
1159
    {
1160
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1161
        $summaryFields = $team->summaryFields();
1162
1163
        $this->assertEquals(
1164
            'Custom Title',
1165
            $summaryFields['Title'],
1166
            'Custom title is preserved'
1167
        );
1168
1169
        $this->assertEquals(
1170
            'Captain\'s shirt number',
1171
            $summaryFields['Captain.ShirtNumber'],
1172
            'Custom title on relation is preserved'
1173
        );
1174
    }
1175
1176
    public function testDataObjectUpdate()
1177
    {
1178
        /* update() calls can use the dot syntax to reference has_one relations and other methods that return
1179
        * objects */
1180
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1181
        $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...
1182
1183
        $team1->update(
1184
            array(
1185
            'DatabaseField' => 'Something',
1186
            'Captain.FirstName' => 'Jim',
1187
            'Captain.Email' => '[email protected]',
1188
            'Captain.FavouriteTeam.Title' => 'New and improved team 1',
1189
            )
1190
        );
1191
1192
        /* Test the simple case of updating fields on the object itself */
1193
        $this->assertEquals('Something', $team1->DatabaseField);
1194
1195
        /* Setting Captain.Email and Captain.FirstName will have updated DataObjectTest_Captain.captain1 in
1196
        * the database.  Although update() doesn't usually write, it does write related records automatically. */
1197
        $captain1 = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
1198
        $this->assertEquals('Jim', $captain1->FirstName);
1199
        $this->assertEquals('[email protected]', $captain1->Email);
1200
1201
        /* Jim's favourite team is team 1; we need to reload the object to the the change that setting Captain.
1202
        * FavouriteTeam.Title made */
1203
        $reloadedTeam1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1204
        $this->assertEquals('New and improved team 1', $reloadedTeam1->Title);
1205
    }
1206
1207
    public function testDataObjectUpdateNew()
1208
    {
1209
        /* update() calls can use the dot syntax to reference has_one relations and other methods that return
1210
        * objects */
1211
        $team1 = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1212
        $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...
1213
1214
        $team1->update(
1215
            array(
1216
            'Captain.FirstName' => 'Jim',
1217
            'Captain.FavouriteTeam.Title' => 'New and improved team 1',
1218
            )
1219
        );
1220
        /* Test that the captain ID has been updated */
1221
        $this->assertGreaterThan(0, $team1->CaptainID);
1222
1223
        /* Fetch the newly created captain */
1224
        $captain1 = DataObjectTest\Player::get()->byID($team1->CaptainID);
1225
        $this->assertEquals('Jim', $captain1->FirstName);
1226
1227
        /* Grab the favourite team and make sure it has the correct values */
1228
        $reloadedTeam1 = $captain1->FavouriteTeam();
1229
        $this->assertEquals($reloadedTeam1->ID, $captain1->FavouriteTeamID);
1230
        $this->assertEquals('New and improved team 1', $reloadedTeam1->Title);
1231
    }
1232
1233
    public function testWritingInvalidDataObjectThrowsException()
1234
    {
1235
        $validatedObject = new DataObjectTest\ValidatedObject();
1236
        $this->setExpectedException(ValidationException::class);
1237
        $validatedObject->write();
1238
    }
1239
1240
    public function testWritingValidDataObjectDoesntThrowException()
1241
    {
1242
        $validatedObject = new DataObjectTest\ValidatedObject();
1243
        $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...
1244
1245
        $validatedObject->write();
1246
        $this->assertTrue($validatedObject->isInDB(), "Validated object was not saved to database");
1247
    }
1248
1249
    public function testSubclassCreation()
1250
    {
1251
        /* Creating a new object of a subclass should set the ClassName field correctly */
1252
        $obj = new DataObjectTest\SubTeam();
1253
        $obj->write();
1254
        $this->assertEquals(
1255
            DataObjectTest\SubTeam::class,
1256
            DB::query("SELECT \"ClassName\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $obj->ID")->value()
1257
        );
1258
    }
1259
1260
    public function testForceInsert()
1261
    {
1262
        /* If you set an ID on an object and pass forceInsert = true, then the object should be correctly created */
1263
        $conn = DB::get_conn();
1264
        if (method_exists($conn, 'allowPrimaryKeyEditing')) {
1265
            $conn->allowPrimaryKeyEditing(DataObjectTest\Team::class, true);
1266
        }
1267
        $obj = new DataObjectTest\SubTeam();
1268
        $obj->ID = 1001;
1269
        $obj->Title = 'asdfasdf';
1270
        $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...
1271
        $obj->write(false, true);
1272
        if (method_exists($conn, 'allowPrimaryKeyEditing')) {
1273
            $conn->allowPrimaryKeyEditing(DataObjectTest\Team::class, false);
1274
        }
1275
1276
        $this->assertEquals(
1277
            DataObjectTest\SubTeam::class,
1278
            DB::query("SELECT \"ClassName\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $obj->ID")->value()
1279
        );
1280
1281
        /* Check that it actually saves to the database with the correct ID */
1282
        $this->assertEquals(
1283
            "1001",
1284
            DB::query(
1285
                "SELECT \"ID\" FROM \"DataObjectTest_SubTeam\" WHERE \"SubclassDatabaseField\" = 'asdfasdf'"
1286
            )->value()
1287
        );
1288
        $this->assertEquals(
1289
            "1001",
1290
            DB::query("SELECT \"ID\" FROM \"DataObjectTest_Team\" WHERE \"Title\" = 'asdfasdf'")->value()
1291
        );
1292
    }
1293
1294
    public function testHasOwnTable()
1295
    {
1296
        $schema = DataObject::getSchema();
1297
        /* Test DataObject::has_own_table() returns true if the object has $has_one or $db values */
1298
        $this->assertTrue($schema->classHasTable(DataObjectTest\Player::class));
1299
        $this->assertTrue($schema->classHasTable(DataObjectTest\Team::class));
1300
        $this->assertTrue($schema->classHasTable(DataObjectTest\Fixture::class));
1301
1302
        /* Root DataObject that always have a table, even if they lack both $db and $has_one */
1303
        $this->assertTrue($schema->classHasTable(DataObjectTest\FieldlessTable::class));
1304
1305
        /* Subclasses without $db or $has_one don't have a table */
1306
        $this->assertFalse($schema->classHasTable(DataObjectTest\FieldlessSubTable::class));
1307
1308
        /* Return false if you don't pass it a subclass of DataObject */
1309
        $this->assertFalse($schema->classHasTable(DataObject::class));
1310
        $this->assertFalse($schema->classHasTable(ViewableData::class));
1311
1312
        // Invalid class
1313
        $this->setExpectedException(ReflectionException::class, 'Class ThisIsntADataObject does not exist');
1314
        $this->assertFalse($schema->classHasTable("ThisIsntADataObject"));
1315
    }
1316
1317
    public function testMerge()
1318
    {
1319
        // test right merge of subclasses
1320
        $left = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
1321
        $right = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam2_with_player_relation');
1322
        $leftOrigID = $left->ID;
1323
        $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 1321 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...
1324
        $this->assertEquals(
1325
            $left->Title,
1326
            'Subteam 2',
1327
            'merge() with "right" priority overwrites fields with existing values on subclasses'
1328
        );
1329
        $this->assertEquals(
1330
            $left->ID,
1331
            $leftOrigID,
1332
            'merge() with "right" priority doesnt overwrite database ID'
1333
        );
1334
1335
        // test overwriteWithEmpty flag on existing left values
1336
        $left = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam2_with_player_relation');
1337
        $right = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam3_with_empty_fields');
1338
        $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 1337 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...
1339
        $this->assertEquals(
1340
            $left->Title,
1341
            'Subteam 3',
1342
            'merge() with $overwriteWithEmpty overwrites non-empty fields on left object'
1343
        );
1344
1345
        // test overwriteWithEmpty flag on empty left values
1346
        $left = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
1347
        // $SubclassDatabaseField is empty on here
1348
        $right = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam2_with_player_relation');
1349
        $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 1348 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...
1350
        $this->assertEquals(
1351
            $left->SubclassDatabaseField,
1352
            null,
1353
            'merge() with $overwriteWithEmpty overwrites empty fields on left object'
1354
        );
1355
1356
        // @todo test "left" priority flag
1357
        // @todo test includeRelations flag
1358
        // @todo test includeRelations in combination with overwriteWithEmpty
1359
        // @todo test has_one relations
1360
        // @todo test has_many and many_many relations
1361
    }
1362
1363
    public function testPopulateDefaults()
1364
    {
1365
        $obj = new DataObjectTest\Fixture();
1366
        $this->assertEquals(
1367
            $obj->MyFieldWithDefault,
1368
            'Default Value',
1369
            'Defaults are populated for in-memory object from $defaults array'
1370
        );
1371
1372
        $this->assertEquals(
1373
            $obj->MyFieldWithAltDefault,
1374
            'Default Value',
1375
            'Defaults are populated from overloaded populateDefaults() method'
1376
        );
1377
    }
1378
1379
    public function testValidateModelDefinitionsFailsWithArray()
1380
    {
1381
        Config::modify()->merge(DataObjectTest\Team::class, 'has_one', array('NotValid' => array('NoArraysAllowed')));
1382
        $this->setExpectedException(InvalidArgumentException::class);
1383
        DataObject::getSchema()->hasOneComponent(DataObjectTest\Team::class, 'NotValid');
1384
    }
1385
1386
    public function testValidateModelDefinitionsFailsWithIntKey()
1387
    {
1388
        Config::modify()->set(DataObjectTest\Team::class, 'has_many', array(0 => DataObjectTest\Player::class));
1389
        $this->setExpectedException(InvalidArgumentException::class);
1390
        DataObject::getSchema()->hasManyComponent(DataObjectTest\Team::class, 0);
1391
    }
1392
1393
    public function testValidateModelDefinitionsFailsWithIntValue()
1394
    {
1395
        Config::modify()->merge(DataObjectTest\Team::class, 'many_many', array('Players' => 12));
1396
        $this->setExpectedException(InvalidArgumentException::class);
1397
        DataObject::getSchema()->manyManyComponent(DataObjectTest\Team::class, 'Players');
1398
    }
1399
1400
    public function testNewClassInstance()
1401
    {
1402
        $dataObject = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1403
        $changedDO = $dataObject->newClassInstance(DataObjectTest\SubTeam::class);
1404
        $changedFields = $changedDO->getChangedFields();
1405
1406
        // Don't write the record, it will reset changed fields
1407
        $this->assertInstanceOf(DataObjectTest\SubTeam::class, $changedDO);
1408
        $this->assertEquals($changedDO->ClassName, DataObjectTest\SubTeam::class);
1409
        $this->assertEquals($changedDO->RecordClassName, DataObjectTest\SubTeam::class);
1410
        $this->assertContains('ClassName', array_keys($changedFields));
1411
        $this->assertEquals($changedFields['ClassName']['before'], DataObjectTest\Team::class);
1412
        $this->assertEquals($changedFields['ClassName']['after'], DataObjectTest\SubTeam::class);
1413
        $this->assertEquals($changedFields['RecordClassName']['before'], DataObjectTest\Team::class);
1414
        $this->assertEquals($changedFields['RecordClassName']['after'], DataObjectTest\SubTeam::class);
1415
1416
        $changedDO->write();
1417
1418
        $this->assertInstanceOf(DataObjectTest\SubTeam::class, $changedDO);
1419
        $this->assertEquals($changedDO->ClassName, DataObjectTest\SubTeam::class);
1420
1421
        // Test invalid classes fail
1422
        $this->setExpectedException('InvalidArgumentException', "Controller is not a valid subclass of DataObject");
1423
        /**
1424
 * @skipUpgrade
1425
*/
1426
        $dataObject->newClassInstance('Controller');
1427
    }
1428
1429
    public function testMultipleManyManyWithSameClass()
1430
    {
1431
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1432
        $company2 = $this->objFromFixture(DataObjectTest\EquipmentCompany::class, 'equipmentcompany2');
1433
        $sponsors = $team->Sponsors();
1434
        $equipmentSuppliers = $team->EquipmentSuppliers();
1435
1436
        // Check that DataObject::many_many() works as expected
1437
        list($relationClass, $class, $targetClass, $parentField, $childField, $joinTable)
1438
            = DataObject::getSchema()->manyManyComponent(DataObjectTest\Team::class, 'Sponsors');
1439
        $this->assertEquals(ManyManyList::class, $relationClass);
1440
        $this->assertEquals(
1441
            DataObjectTest\Team::class,
1442
            $class,
1443
            'DataObject::many_many() didn\'t find the correct base class'
1444
        );
1445
        $this->assertEquals(
1446
            DataObjectTest\EquipmentCompany::class,
1447
            $targetClass,
1448
            'DataObject::many_many() didn\'t find the correct target class for the relation'
1449
        );
1450
        $this->assertEquals(
1451
            'DataObjectTest_EquipmentCompany_SponsoredTeams',
1452
            $joinTable,
1453
            'DataObject::many_many() didn\'t find the correct relation table'
1454
        );
1455
        $this->assertEquals('DataObjectTest_TeamID', $parentField);
1456
        $this->assertEquals('DataObjectTest_EquipmentCompanyID', $childField);
1457
1458
        // Check that ManyManyList still works
1459
        $this->assertEquals(2, $sponsors->count(), 'Rows are missing from relation');
1460
        $this->assertEquals(1, $equipmentSuppliers->count(), 'Rows are missing from relation');
1461
1462
        // Check everything works when no relation is present
1463
        $teamWithoutSponsor = $this->objFromFixture(DataObjectTest\Team::class, 'team3');
1464
        $this->assertInstanceOf(ManyManyList::class, $teamWithoutSponsor->Sponsors());
1465
        $this->assertEquals(0, $teamWithoutSponsor->Sponsors()->count());
1466
1467
        // Test that belongs_many_many can be infered from with getNonReciprocalComponent
1468
        $this->assertDOSEquals(
1469
            [
1470
                ['Name' => 'Company corp'],
1471
                ['Name' => 'Team co.'],
1472
            ],
1473
            $team->inferReciprocalComponent(DataObjectTest\EquipmentCompany::class, 'SponsoredTeams')
1474
        );
1475
1476
        // Test that many_many can be infered from getNonReciprocalComponent
1477
        $this->assertDOSEquals(
1478
            [
1479
                ['Title' => 'Team 1'],
1480
                ['Title' => 'Team 2'],
1481
                ['Title' => 'Subteam 1'],
1482
            ],
1483
            $company2->inferReciprocalComponent(DataObjectTest\Team::class, 'Sponsors')
1484
        );
1485
1486
        // Check many_many_extraFields still works
1487
        $equipmentCompany = $this->objFromFixture(DataObjectTest\EquipmentCompany::class, 'equipmentcompany1');
1488
        $equipmentCompany->SponsoredTeams()->add($teamWithoutSponsor, array('SponsorFee' => 1000));
1489
        $sponsoredTeams = $equipmentCompany->SponsoredTeams();
1490
        $this->assertEquals(
1491
            1000,
1492
            $sponsoredTeams->byID($teamWithoutSponsor->ID)->SponsorFee,
1493
            'Data from many_many_extraFields was not stored/extracted correctly'
1494
        );
1495
1496
        // Check subclasses correctly inherit multiple many_manys
1497
        $subTeam = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
1498
        $this->assertEquals(
1499
            2,
1500
            $subTeam->Sponsors()->count(),
1501
            'Child class did not inherit multiple many_manys'
1502
        );
1503
        $this->assertEquals(
1504
            1,
1505
            $subTeam->EquipmentSuppliers()->count(),
1506
            'Child class did not inherit multiple many_manys'
1507
        );
1508
        // Team 2 has one EquipmentCompany sponsor and one SubEquipmentCompany
1509
        $team2 = $this->objFromFixture(DataObjectTest\Team::class, 'team2');
1510
        $this->assertEquals(
1511
            2,
1512
            $team2->Sponsors()->count(),
1513
            'Child class did not inherit multiple belongs_many_manys'
1514
        );
1515
1516
        // Check many_many_extraFields also works from the belongs_many_many side
1517
        $sponsors = $team2->Sponsors();
1518
        $sponsors->add($equipmentCompany, array('SponsorFee' => 750));
1519
        $this->assertEquals(
1520
            750,
1521
            $sponsors->byID($equipmentCompany->ID)->SponsorFee,
1522
            'Data from many_many_extraFields was not stored/extracted correctly'
1523
        );
1524
1525
        $subEquipmentCompany = $this->objFromFixture(DataObjectTest\SubEquipmentCompany::class, 'subequipmentcompany1');
1526
        $subTeam->Sponsors()->add($subEquipmentCompany, array('SponsorFee' => 1200));
1527
        $this->assertEquals(
1528
            1200,
1529
            $subTeam->Sponsors()->byID($subEquipmentCompany->ID)->SponsorFee,
1530
            'Data from inherited many_many_extraFields was not stored/extracted correctly'
1531
        );
1532
    }
1533
1534
    public function testManyManyExtraFields()
1535
    {
1536
        $team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
1537
        $schema = DataObject::getSchema();
1538
1539
        // Get all extra fields
1540
        $teamExtraFields = $team->manyManyExtraFields();
1541
        $this->assertEquals(
1542
            array(
1543
            'Players' => array('Position' => 'Varchar(100)')
1544
            ),
1545
            $teamExtraFields
1546
        );
1547
1548
        // Ensure fields from parent classes are included
1549
        $subTeam = singleton(DataObjectTest\SubTeam::class);
1550
        $teamExtraFields = $subTeam->manyManyExtraFields();
1551
        $this->assertEquals(
1552
            array(
1553
            'Players' => array('Position' => 'Varchar(100)'),
1554
            'FormerPlayers' => array('Position' => 'Varchar(100)')
1555
            ),
1556
            $teamExtraFields
1557
        );
1558
1559
        // Extra fields are immediately available on the Team class (defined in $many_many_extraFields)
1560
        $teamExtraFields = $schema->manyManyExtraFieldsForComponent(DataObjectTest\Team::class, 'Players');
1561
        $this->assertEquals(
1562
            $teamExtraFields,
1563
            array(
1564
            'Position' => 'Varchar(100)'
1565
            )
1566
        );
1567
1568
        // We'll have to go through the relation to get the extra fields on Player
1569
        $playerExtraFields = $schema->manyManyExtraFieldsForComponent(DataObjectTest\Player::class, 'Teams');
1570
        $this->assertEquals(
1571
            $playerExtraFields,
1572
            array(
1573
            'Position' => 'Varchar(100)'
1574
            )
1575
        );
1576
1577
        // Iterate through a many-many relationship and confirm that extra fields are included
1578
        $newTeam = new DataObjectTest\Team();
1579
        $newTeam->Title = "New team";
1580
        $newTeam->write();
1581
        $newTeamID = $newTeam->ID;
1582
1583
        $newPlayer = new DataObjectTest\Player();
1584
        $newPlayer->FirstName = "Sam";
1585
        $newPlayer->Surname = "Minnee";
1586
        $newPlayer->write();
1587
1588
        // The idea of Sam as a prop is essentially humourous.
1589
        $newTeam->Players()->add($newPlayer, array("Position" => "Prop"));
1590
1591
        // Requery and uncache everything
1592
        $newTeam->flushCache();
1593
        $newTeam = DataObject::get_by_id(DataObjectTest\Team::class, $newTeamID);
1594
1595
        // Check that the Position many_many_extraField is extracted.
1596
        $player = $newTeam->Players()->first();
1597
        $this->assertEquals('Sam', $player->FirstName);
1598
        $this->assertEquals("Prop", $player->Position);
1599
1600
        // Check that ordering a many-many relation by an aggregate column doesn't fail
1601
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
1602
        $player->Teams()->sort("count(DISTINCT \"DataObjectTest_Team_Players\".\"DataObjectTest_PlayerID\") DESC");
1603
    }
1604
1605
    /**
1606
     * Check that the queries generated for many-many relation queries can have unlimitedRowCount
1607
     * called on them.
1608
     */
1609
    public function testManyManyUnlimitedRowCount()
1610
    {
1611
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
1612
        // TODO: What's going on here?
1613
        $this->assertEquals(2, $player->Teams()->dataQuery()->query()->unlimitedRowCount());
1614
    }
1615
1616
    /**
1617
     * Tests that singular_name() generates sensible defaults.
1618
     */
1619
    public function testSingularName()
1620
    {
1621
        $assertions = array(
1622
            DataObjectTest\Player::class => 'Player',
1623
            DataObjectTest\Team::class => 'Team',
1624
            DataObjectTest\Fixture::class => 'Fixture',
1625
        );
1626
1627
        foreach ($assertions as $class => $expectedSingularName) {
1628
            $this->assertEquals(
1629
                $expectedSingularName,
1630
                singleton($class)->singular_name(),
1631
                "Assert that the singular_name for '$class' is correct."
1632
            );
1633
        }
1634
    }
1635
1636
    /**
1637
     * Tests that plural_name() generates sensible defaults.
1638
     */
1639
    public function testPluralName()
1640
    {
1641
        $assertions = array(
1642
            DataObjectTest\Player::class => 'Players',
1643
            DataObjectTest\Team::class => 'Teams',
1644
            DataObjectTest\Fixture::class => 'Fixtures',
1645
            DataObjectTest\Play::class => 'Plays',
1646
            DataObjectTest\Bogey::class => 'Bogeys',
1647
            DataObjectTest\Ploy::class => 'Ploys',
1648
        );
1649
        i18n::set_locale('en_NZ');
1650
        foreach ($assertions as $class => $expectedPluralName) {
1651
            $this->assertEquals(
1652
                $expectedPluralName,
1653
                DataObject::singleton($class)->plural_name(),
1654
                "Assert that the plural_name for '$class' is correct."
1655
            );
1656
            $this->assertEquals(
1657
                $expectedPluralName,
1658
                DataObject::singleton($class)->i18n_plural_name(),
1659
                "Assert that the i18n_plural_name for '$class' is correct."
1660
            );
1661
        }
1662
    }
1663
1664
    public function testHasDatabaseField()
1665
    {
1666
        $team = singleton(DataObjectTest\Team::class);
1667
        $subteam = singleton(DataObjectTest\SubTeam::class);
1668
1669
        $this->assertTrue(
1670
            $team->hasDatabaseField('Title'),
1671
            "hasOwnDatabaseField() works with \$db fields"
1672
        );
1673
        $this->assertTrue(
1674
            $team->hasDatabaseField('CaptainID'),
1675
            "hasOwnDatabaseField() works with \$has_one fields"
1676
        );
1677
        $this->assertFalse(
1678
            $team->hasDatabaseField('NonExistentField'),
1679
            "hasOwnDatabaseField() doesn't detect non-existend fields"
1680
        );
1681
        $this->assertTrue(
1682
            $team->hasDatabaseField('ExtendedDatabaseField'),
1683
            "hasOwnDatabaseField() works with extended fields"
1684
        );
1685
        $this->assertFalse(
1686
            $team->hasDatabaseField('SubclassDatabaseField'),
1687
            "hasOwnDatabaseField() doesn't pick up fields in subclasses on parent class"
1688
        );
1689
1690
        $this->assertTrue(
1691
            $subteam->hasDatabaseField('SubclassDatabaseField'),
1692
            "hasOwnDatabaseField() picks up fields in subclasses"
1693
        );
1694
    }
1695
1696
    public function testFieldTypes()
1697
    {
1698
        $obj = new DataObjectTest\Fixture();
1699
        $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...
1700
        $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...
1701
        $obj->write();
1702
        $obj->flushCache();
1703
1704
        $obj = DataObject::get_by_id(DataObjectTest\Fixture::class, $obj->ID);
1705
        $this->assertEquals('1988-01-02', $obj->DateField);
1706
        $this->assertEquals('1988-03-04 06:30:00', $obj->DatetimeField);
1707
    }
1708
1709
    public function testTwoSubclassesWithTheSameFieldNameWork()
1710
    {
1711
        // Create two objects of different subclasses, setting the values of fields that are
1712
        // defined separately in each subclass
1713
        $obj1 = new DataObjectTest\SubTeam();
1714
        $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...
1715
        $obj2 = new DataObjectTest\OtherSubclassWithSameField();
1716
        $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...
1717
1718
        // Write them to the database
1719
        $obj1->write();
1720
        $obj2->write();
1721
1722
        // Check that the values of those fields are properly read from the database
1723
        $values = DataObject::get(
1724
            DataObjectTest\Team::class,
1725
            "\"DataObjectTest_Team\".\"ID\" IN
1726
			($obj1->ID, $obj2->ID)"
1727
        )->column("SubclassDatabaseField");
1728
        $this->assertEquals(array_intersect($values, array('obj1', 'obj2')), $values);
1729
    }
1730
1731
    public function testClassNameSetForNewObjects()
1732
    {
1733
        $d = new DataObjectTest\Player();
1734
        $this->assertEquals(DataObjectTest\Player::class, $d->ClassName);
1735
    }
1736
1737
    public function testHasValue()
1738
    {
1739
        $team = new DataObjectTest\Team();
1740
        $this->assertFalse($team->hasValue('Title', null, false));
1741
        $this->assertFalse($team->hasValue('DatabaseField', null, false));
1742
1743
        $team->Title = 'hasValue';
1744
        $this->assertTrue($team->hasValue('Title', null, false));
1745
        $this->assertFalse($team->hasValue('DatabaseField', null, false));
1746
1747
        $team->Title = '<p></p>';
1748
        $this->assertTrue(
1749
            $team->hasValue('Title', null, false),
1750
            'Test that an empty paragraph is a value for non-HTML fields.'
1751
        );
1752
1753
        $team->DatabaseField = 'hasValue';
1754
        $this->assertTrue($team->hasValue('Title', null, false));
1755
        $this->assertTrue($team->hasValue('DatabaseField', null, false));
1756
    }
1757
1758
    public function testHasMany()
1759
    {
1760
        $company = new DataObjectTest\Company();
1761
1762
        $this->assertEquals(
1763
            array (
1764
                'CurrentStaff'     => DataObjectTest\Staff::class,
1765
                'PreviousStaff'    => DataObjectTest\Staff::class
1766
            ),
1767
            $company->hasMany(),
1768
            'has_many strips field name data by default.'
1769
        );
1770
1771
        $this->assertEquals(
1772
            DataObjectTest\Staff::class,
1773
            DataObject::getSchema()->hasManyComponent(DataObjectTest\Company::class, 'CurrentStaff'),
1774
            'has_many strips field name data by default on single relationships.'
1775
        );
1776
1777
        $this->assertEquals(
1778
            array (
1779
                'CurrentStaff'     => DataObjectTest\Staff::class.'.CurrentCompany',
1780
                'PreviousStaff'    => DataObjectTest\Staff::class.'.PreviousCompany'
1781
            ),
1782
            $company->hasMany(false),
1783
            'has_many returns field name data when $classOnly is false.'
1784
        );
1785
1786
        $this->assertEquals(
1787
            DataObjectTest\Staff::class.'.CurrentCompany',
1788
            DataObject::getSchema()->hasManyComponent(DataObjectTest\Company::class, 'CurrentStaff', false),
1789
            'has_many returns field name data on single records when $classOnly is false.'
1790
        );
1791
    }
1792
1793
    public function testGetRemoteJoinField()
1794
    {
1795
        $schema = DataObject::getSchema();
1796
1797
        // Company schema
1798
        $staffJoinField = $schema->getRemoteJoinField(
1799
            DataObjectTest\Company::class,
1800
            'CurrentStaff',
1801
            'has_many',
1802
            $polymorphic
1803
        );
1804
        $this->assertEquals('CurrentCompanyID', $staffJoinField);
1805
        $this->assertFalse($polymorphic, 'DataObjectTest_Company->CurrentStaff is not polymorphic');
1806
        $previousStaffJoinField = $schema->getRemoteJoinField(
1807
            DataObjectTest\Company::class,
1808
            'PreviousStaff',
1809
            'has_many',
1810
            $polymorphic
1811
        );
1812
        $this->assertEquals('PreviousCompanyID', $previousStaffJoinField);
1813
        $this->assertFalse($polymorphic, 'DataObjectTest_Company->PreviousStaff is not polymorphic');
1814
1815
        // CEO Schema
1816
        $this->assertEquals(
1817
            'CEOID',
1818
            $schema->getRemoteJoinField(
1819
                DataObjectTest\CEO::class,
1820
                'Company',
1821
                'belongs_to',
1822
                $polymorphic
1823
            )
1824
        );
1825
        $this->assertFalse($polymorphic, 'DataObjectTest_CEO->Company is not polymorphic');
1826
        $this->assertEquals(
1827
            'PreviousCEOID',
1828
            $schema->getRemoteJoinField(
1829
                DataObjectTest\CEO::class,
1830
                'PreviousCompany',
1831
                'belongs_to',
1832
                $polymorphic
1833
            )
1834
        );
1835
        $this->assertFalse($polymorphic, 'DataObjectTest_CEO->PreviousCompany is not polymorphic');
1836
1837
        // Team schema
1838
        $this->assertEquals(
1839
            'Favourite',
1840
            $schema->getRemoteJoinField(
1841
                DataObjectTest\Team::class,
1842
                'Fans',
1843
                'has_many',
1844
                $polymorphic
1845
            )
1846
        );
1847
        $this->assertTrue($polymorphic, 'DataObjectTest_Team->Fans is polymorphic');
1848
        $this->assertEquals(
1849
            'TeamID',
1850
            $schema->getRemoteJoinField(
1851
                DataObjectTest\Team::class,
1852
                'Comments',
1853
                'has_many',
1854
                $polymorphic
1855
            )
1856
        );
1857
        $this->assertFalse($polymorphic, 'DataObjectTest_Team->Comments is not polymorphic');
1858
    }
1859
1860
    public function testBelongsTo()
1861
    {
1862
        $company = new DataObjectTest\Company();
1863
        $ceo     = new DataObjectTest\CEO();
1864
1865
        $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...
1866
        $company->write();
1867
        $ceo->write();
1868
1869
        // Test belongs_to assignment
1870
        $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...
1871
        $company->write();
1872
1873
        $this->assertEquals($company->ID, $ceo->Company()->ID, 'belongs_to returns the right results.');
1874
1875
        // Test belongs_to can be infered via getNonReciprocalComponent
1876
        // Note: Will be returned as has_many since the belongs_to is ignored.
1877
        $this->assertDOSEquals(
1878
            [['Name' => 'New Company']],
1879
            $ceo->inferReciprocalComponent(DataObjectTest\Company::class, 'CEO')
1880
        );
1881
1882
        // Test has_one to a belongs_to can be infered via getNonReciprocalComponent
1883
        $this->assertEquals(
1884
            $ceo->ID,
1885
            $company->inferReciprocalComponent(DataObjectTest\CEO::class, 'Company')->ID
1886
        );
1887
1888
        // Test automatic creation of class where no assigment exists
1889
        $ceo = new DataObjectTest\CEO();
1890
        $ceo->write();
1891
1892
        $this->assertTrue(
1893
            $ceo->Company() instanceof DataObjectTest\Company,
1894
            'DataObjects across belongs_to relations are automatically created.'
1895
        );
1896
        $this->assertEquals($ceo->ID, $ceo->Company()->CEOID, 'Remote IDs are automatically set.');
1897
1898
        // Write object with components
1899
        $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...
1900
        $ceo->write(false, false, false, true);
1901
        $this->assertTrue($ceo->Company()->isInDB(), 'write() writes belongs_to components to the database.');
1902
1903
        $newCEO = DataObject::get_by_id(DataObjectTest\CEO::class, $ceo->ID);
1904
        $this->assertEquals(
1905
            $ceo->Company()->ID,
1906
            $newCEO->Company()->ID,
1907
            'belongs_to can be retrieved from the database.'
1908
        );
1909
    }
1910
1911
    public function testBelongsToPolymorphic()
1912
    {
1913
        $company = new DataObjectTest\Company();
1914
        $ceo     = new DataObjectTest\CEO();
1915
1916
        $company->write();
1917
        $ceo->write();
1918
1919
        // Test belongs_to assignment
1920
        $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...
1921
        $company->OwnerClass = $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...
1922
        $company->write();
1923
1924
        $this->assertEquals($company->ID, $ceo->CompanyOwned()->ID, 'belongs_to returns the right results.');
1925
        $this->assertEquals($company->class, $ceo->CompanyOwned()->class, 'belongs_to returns the right results.');
1926
1927
        // Test automatic creation of class where no assigment exists
1928
        $ceo = new DataObjectTest\CEO();
1929
        $ceo->write();
1930
1931
        $this->assertTrue(
1932
            $ceo->CompanyOwned() instanceof DataObjectTest\Company,
1933
            'DataObjects across polymorphic belongs_to relations are automatically created.'
1934
        );
1935
        $this->assertEquals($ceo->ID, $ceo->CompanyOwned()->OwnerID, 'Remote IDs are automatically set.');
1936
        $this->assertInstanceOf($ceo->CompanyOwned()->OwnerClass, $ceo, 'Remote class is automatically  set');
1937
1938
        // Write object with components
1939
        $ceo->write(false, false, false, true);
1940
        $this->assertTrue($ceo->CompanyOwned()->isInDB(), 'write() writes belongs_to components to the database.');
1941
1942
        $newCEO = DataObject::get_by_id(DataObjectTest\CEO::class, $ceo->ID);
1943
        $this->assertEquals(
1944
            $ceo->CompanyOwned()->ID,
1945
            $newCEO->CompanyOwned()->ID,
1946
            'polymorphic belongs_to can be retrieved from the database.'
1947
        );
1948
    }
1949
1950
    /**
1951
     * @expectedException \LogicException
1952
     */
1953
    public function testInvalidate()
1954
    {
1955
        $do = new DataObjectTest\Fixture();
1956
        $do->write();
1957
1958
        $do->delete();
1959
1960
        $do->delete(); // Prohibit invalid object manipulation
1961
        $do->write();
1962
        $do->duplicate();
1963
    }
1964
1965
    public function testToMap()
1966
    {
1967
        $obj = $this->objFromFixture(DataObjectTest\SubTeam::class, 'subteam1');
1968
1969
        $map = $obj->toMap();
1970
1971
        $this->assertArrayHasKey('ID', $map, 'Contains base fields');
1972
        $this->assertArrayHasKey('Title', $map, 'Contains fields from parent class');
1973
        $this->assertArrayHasKey('SubclassDatabaseField', $map, 'Contains fields from concrete class');
1974
1975
        $this->assertEquals(
1976
            $obj->ID,
1977
            $map['ID'],
1978
            'Contains values from base fields'
1979
        );
1980
        $this->assertEquals(
1981
            $obj->Title,
1982
            $map['Title'],
1983
            'Contains values from parent class fields'
1984
        );
1985
        $this->assertEquals(
1986
            $obj->SubclassDatabaseField,
1987
            $map['SubclassDatabaseField'],
1988
            'Contains values from concrete class fields'
1989
        );
1990
1991
        $newObj = new DataObjectTest\SubTeam();
1992
        $this->assertArrayHasKey('Title', $map, 'Contains null fields');
1993
    }
1994
1995
    public function testIsEmpty()
1996
    {
1997
        $objEmpty = new DataObjectTest\Team();
1998
        $this->assertTrue($objEmpty->isEmpty(), 'New instance without populated defaults is empty');
1999
2000
        $objEmpty->Title = '0'; //
2001
        $this->assertFalse($objEmpty->isEmpty(), 'Zero value in attribute considered non-empty');
2002
    }
2003
2004
    public function testRelField()
2005
    {
2006
        $captain = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
2007
        // Test traversal of a single has_one
2008
        $this->assertEquals("Team 1", $captain->relField('FavouriteTeam.Title'));
2009
        // Test direct field access
2010
        $this->assertEquals("Captain", $captain->relField('FirstName'));
2011
2012
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
2013
        // Test that we can traverse more than once, and that arbitrary methods are okay
2014
        $this->assertEquals("Team 1", $player->relField('Teams.First.Title'));
2015
2016
        $newPlayer = new DataObjectTest\Player();
2017
        $this->assertNull($newPlayer->relField('Teams.First.Title'));
2018
2019
        // Test that relField works on db field manipulations
2020
        $comment = $this->objFromFixture(DataObjectTest\TeamComment::class, 'comment3');
2021
        $this->assertEquals("PHIL IS A UNIQUE GUY, AND COMMENTS ON TEAM2", $comment->relField('Comment.UpperCase'));
2022
    }
2023
2024
    public function testRelObject()
2025
    {
2026
        $captain = $this->objFromFixture(DataObjectTest\Player::class, 'captain1');
2027
2028
        // Test traversal of a single has_one
2029
        $this->assertInstanceOf(DBVarchar::class, $captain->relObject('FavouriteTeam.Title'));
2030
        $this->assertEquals("Team 1", $captain->relObject('FavouriteTeam.Title')->getValue());
2031
2032
        // Test direct field access
2033
        $this->assertInstanceOf(DBBoolean::class, $captain->relObject('IsRetired'));
2034
        $this->assertEquals(1, $captain->relObject('IsRetired')->getValue());
2035
2036
        $player = $this->objFromFixture(DataObjectTest\Player::class, 'player2');
2037
        // Test that we can traverse more than once, and that arbitrary methods are okay
2038
        $this->assertInstanceOf(DBVarchar::class, $player->relObject('Teams.First.Title'));
2039
        $this->assertEquals("Team 1", $player->relObject('Teams.First.Title')->getValue());
2040
    }
2041
2042
    public function testLateStaticBindingStyle()
2043
    {
2044
        // Confirm that DataObjectTest_Player::get() operates as excepted
2045
        $this->assertEquals(4, DataObjectTest\Player::get()->count());
2046
        $this->assertInstanceOf(DataObjectTest\Player::class, DataObjectTest\Player::get()->first());
2047
2048
        // You can't pass arguments to LSB syntax - use the DataList methods instead.
2049
        $this->setExpectedException('InvalidArgumentException');
2050
        DataObjectTest\Player::get(null, "\"ID\" = 1");
2051
    }
2052
2053
    public function testBrokenLateStaticBindingStyle()
2054
    {
2055
        // If you call DataObject::get() you have to pass a first argument
2056
        $this->setExpectedException('InvalidArgumentException');
2057
        DataObject::get();
2058
    }
2059
2060
    public function testBigIntField()
2061
    {
2062
        $staff = new DataObjectTest\Staff();
2063
        $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...
2064
        $staff->write();
2065
        $this->assertEquals(PHP_INT_MAX, DataObjectTest\Staff::get()->byID($staff->ID)->Salary);
2066
    }
2067
}
2068