Completed
Push — master ( f39c4d...b2e354 )
by Sam
03:35 queued 03:17
created

DataObjectTest::makeAccessible()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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