Completed
Push — master ( 61abbf...bcc21c )
by Daniel
09:54
created

DataObjectTest::testBelongsToPolymorphic()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 37
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 24
nc 1
nop 0
dl 0
loc 37
rs 8.8571
c 0
b 0
f 0
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 21 and the first side effect is on line 2064.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
3
use SilverStripe\Core\Config\Config;
4
use SilverStripe\Dev\SapphireTest;
5
use SilverStripe\Dev\TestOnly;
6
use SilverStripe\ORM\DataObjectSchema;
7
use SilverStripe\ORM\FieldType\DBField;
8
use SilverStripe\ORM\DataObject;
9
use SilverStripe\ORM\DB;
10
use SilverStripe\ORM\Connect\MySQLDatabase;
11
use SilverStripe\ORM\DataExtension;
12
use SilverStripe\ORM\ManyManyList;
13
use SilverStripe\ORM\ValidationResult;
14
use SilverStripe\Security\Member;
15
use SilverStripe\View\ViewableData;
16
17
/**
18
 * @package framework
19
 * @subpackage tests
20
 */
21
class DataObjectTest extends SapphireTest {
22
23
	protected static $fixture_file = 'DataObjectTest.yml';
24
25
	/**
26
	 * Standard set of dataobject test classes
27
	 *
28
	 * @var array
29
	 */
30
	public static $extra_data_objects = array(
31
		'DataObjectTest_Team',
32
		'DataObjectTest_Fixture',
33
		'DataObjectTest_SubTeam',
34
		'OtherSubclassWithSameField',
35
		'DataObjectTest_FieldlessTable',
36
		'DataObjectTest_FieldlessSubTable',
37
		'DataObjectTest_ValidatedObject',
38
		'DataObjectTest_Player',
39
		'DataObjectTest_TeamComment',
40
		'DataObjectTest_EquipmentCompany',
41
		'DataObjectTest_SubEquipmentCompany',
42
		'DataObjectTest\NamespacedClass',
43
		'DataObjectTest_Sortable',
44
		'DataObjectTest\RelationClass',
45
		'DataObjectTest_ExtendedTeamComment',
46
		'DataObjectTest_Company',
47
		'DataObjectTest_Staff',
48
		'DataObjectTest_CEO',
49
		'DataObjectTest_Fan',
50
		'DataObjectTest_Play',
51
		'DataObjectTest_Ploy',
52
		'DataObjectTest_Bogey',
53
		// From ManyManyListTest
54
		'ManyManyListTest_ExtraFields',
55
		'ManyManyListTest_Product',
56
		'ManyManyListTest_Category',
57
	);
58
59
	public function setUpOnce() {
60
		$this->extraDataObjects = static::$extra_data_objects;
61
		parent::setUpOnce();
62
	}
63
64
	public function testDb() {
65
		$schema = DataObject::getSchema();
66
		$dbFields = $schema->fieldSpecs(DataObjectTest_TeamComment::class);
67
68
		// Assert fields are included
69
		$this->assertArrayHasKey('Name', $dbFields);
70
71
		// Assert the base fields are included
72
		$this->assertArrayHasKey('Created', $dbFields);
73
		$this->assertArrayHasKey('LastEdited', $dbFields);
74
		$this->assertArrayHasKey('ClassName', $dbFields);
75
		$this->assertArrayHasKey('ID', $dbFields);
76
77
		// Assert that the correct field type is returned when passing a field
78
		$this->assertEquals('Varchar', $schema->fieldSpec(DataObjectTest_TeamComment::class, 'Name'));
79
		$this->assertEquals('Text', $schema->fieldSpec(DataObjectTest_TeamComment::class, 'Comment'));
80
81
		// Test with table required
82
		$this->assertEquals(
83
			'DataObjectTest_TeamComment.Varchar',
84
			$schema->fieldSpec(DataObjectTest_TeamComment::class, 'Name', DataObjectSchema::INCLUDE_CLASS)
85
		);
86
		$this->assertEquals(
87
			'DataObjectTest_TeamComment.Text',
88
			$schema->fieldSpec(DataObjectTest_TeamComment::class, 'Comment', DataObjectSchema::INCLUDE_CLASS)
89
		);
90
		$dbFields = $schema->fieldSpecs(DataObjectTest_ExtendedTeamComment::class);
91
92
		// fixed fields are still included in extended classes
93
		$this->assertArrayHasKey('Created', $dbFields);
94
		$this->assertArrayHasKey('LastEdited', $dbFields);
95
		$this->assertArrayHasKey('ClassName', $dbFields);
96
		$this->assertArrayHasKey('ID', $dbFields);
97
98
		// Assert overloaded fields have correct data type
99
		$this->assertEquals('HTMLText', $schema->fieldSpec(DataObjectTest_ExtendedTeamComment::class, 'Comment'));
100
		$this->assertEquals('HTMLText', $dbFields['Comment'],
101
			'Calls to DataObject::db without a field specified return correct data types');
102
103
		// assertEquals doesn't verify the order of array elements, so access keys manually to check order:
104
		// 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...
105
		$this->assertEquals(
106
			array(
107
				'Name',
108
				'Comment'
109
			),
110
			array_slice(array_keys($dbFields), 4, 2),
111
			'DataObject::db returns fields in correct order'
112
		);
113
	}
114
115
	public function testConstructAcceptsValues() {
116
		// Values can be an array...
117
		$player = new DataObjectTest_Player(array(
118
			'FirstName' => 'James',
119
			'Surname' => 'Smith'
120
		));
121
122
		$this->assertEquals('James', $player->FirstName);
123
		$this->assertEquals('Smith', $player->Surname);
124
125
		// ... or a stdClass inst
126
		$data = new stdClass();
127
		$data->FirstName = 'John';
128
		$data->Surname = 'Doe';
129
		$player = new DataObjectTest_Player($data);
130
131
		$this->assertEquals('John', $player->FirstName);
132
		$this->assertEquals('Doe', $player->Surname);
133
134
		// IDs should be stored as integers, not strings
135
		$player = new DataObjectTest_Player(array('ID' => '5'));
136
		$this->assertSame(5, $player->ID);
137
	}
138
139
	public function testValidObjectsForBaseFields() {
140
		$obj = new DataObjectTest_ValidatedObject();
141
142
		foreach (array('Created', 'LastEdited', 'ClassName', 'ID') as $field) {
143
			$helper = $obj->dbObject($field);
144
			$this->assertTrue(
145
				($helper instanceof DBField),
146
				"for {$field} expected helper to be DBField, but was " .
147
				(is_object($helper) ? get_class($helper) : "null")
148
			);
149
		}
150
	}
151
152
	public function testDataIntegrityWhenTwoSubclassesHaveSameField() {
153
		// Save data into DataObjectTest_SubTeam.SubclassDatabaseField
154
		$obj = new DataObjectTest_SubTeam();
155
		$obj->SubclassDatabaseField = "obj-SubTeam";
0 ignored issues
show
Documentation introduced by
The property SubclassDatabaseField does not exist on object<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...
156
		$obj->write();
157
158
		// Change the class
159
		$obj->ClassName = 'OtherSubclassWithSameField';
160
		$obj->write();
161
		$obj->flushCache();
162
163
		// Re-fetch from the database and confirm that the data is sourced from
164
		// OtherSubclassWithSameField.SubclassDatabaseField
165
		$obj = DataObject::get_by_id('DataObjectTest_Team', $obj->ID);
166
		$this->assertNull($obj->SubclassDatabaseField);
167
168
		// Confirm that save the object in the other direction.
169
		$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...
170
		$obj->write();
171
172
		$obj->ClassName = 'DataObjectTest_SubTeam';
173
		$obj->write();
174
		$obj->flushCache();
175
176
		// If we restore the class, the old value has been lying dormant and will be available again.
177
		// NOTE: This behaviour is volatile; we may change this in the future to clear fields that
178
		// are no longer relevant when changing ClassName
179
		$obj = DataObject::get_by_id('DataObjectTest_Team', $obj->ID);
180
		$this->assertEquals('obj-SubTeam', $obj->SubclassDatabaseField);
181
	}
182
183
	/**
184
	 * Test deletion of DataObjects
185
	 *   - Deleting using delete() on the DataObject
186
	 *   - Deleting using DataObject::delete_by_id()
187
	 */
188
	public function testDelete() {
189
		// Test deleting using delete() on the DataObject
190
		// Get the first page
191
		$obj = $this->objFromFixture('DataObjectTest_Player', 'captain1');
192
		$objID = $obj->ID;
193
		// Check the page exists before deleting
194
		$this->assertTrue(is_object($obj) && $obj->exists());
195
		// Delete the page
196
		$obj->delete();
197
		// Check that page does not exist after deleting
198
		$obj = DataObject::get_by_id('DataObjectTest_Player', $objID);
199
		$this->assertTrue(!$obj || !$obj->exists());
200
201
202
		// Test deleting using DataObject::delete_by_id()
203
		// Get the second page
204
		$obj = $this->objFromFixture('DataObjectTest_Player', 'captain2');
205
		$objID = $obj->ID;
206
		// Check the page exists before deleting
207
		$this->assertTrue(is_object($obj) && $obj->exists());
208
		// Delete the page
209
		DataObject::delete_by_id('DataObjectTest_Player', $obj->ID);
210
		// Check that page does not exist after deleting
211
		$obj = DataObject::get_by_id('DataObjectTest_Player', $objID);
212
		$this->assertTrue(!$obj || !$obj->exists());
213
	}
214
215
	/**
216
	 * Test methods that get DataObjects
217
	 *   - DataObject::get()
218
	 *       - All records of a DataObject
219
	 *       - Filtering
220
	 *       - Sorting
221
	 *       - Joins
222
	 *       - Limit
223
	 *       - Container class
224
	 *   - DataObject::get_by_id()
225
	 *   - DataObject::get_one()
226
	 *        - With and without caching
227
	 *        - With and without ordering
228
	 */
229
	public function testGet() {
230
		// Test getting all records of a DataObject
231
		$comments = DataObject::get('DataObjectTest_TeamComment');
232
		$this->assertEquals(3, $comments->Count());
233
234
		// Test WHERE clause
235
		$comments = DataObject::get('DataObjectTest_TeamComment', "\"Name\"='Bob'");
236
		$this->assertEquals(1, $comments->Count());
237
		foreach($comments as $comment) {
238
			$this->assertEquals('Bob', $comment->Name);
239
		}
240
241
		// Test sorting
242
		$comments = DataObject::get('DataObjectTest_TeamComment', '', "\"Name\" ASC");
243
		$this->assertEquals(3, $comments->Count());
244
		$this->assertEquals('Bob', $comments->First()->Name);
245
		$comments = DataObject::get('DataObjectTest_TeamComment', '', "\"Name\" DESC");
246
		$this->assertEquals(3, $comments->Count());
247
		$this->assertEquals('Phil', $comments->First()->Name);
248
249
		// Test limit
250
		$comments = DataObject::get('DataObjectTest_TeamComment', '', "\"Name\" ASC", '', '1,2');
251
		$this->assertEquals(2, $comments->Count());
252
		$this->assertEquals('Joe', $comments->First()->Name);
253
		$this->assertEquals('Phil', $comments->Last()->Name);
254
255
		// Test get_by_id()
256
		$captain1ID = $this->idFromFixture('DataObjectTest_Player', 'captain1');
257
		$captain1 = DataObject::get_by_id('DataObjectTest_Player', $captain1ID);
258
		$this->assertEquals('Captain', $captain1->FirstName);
259
260
		// Test get_one() without caching
261
		$comment1 = DataObject::get_one('DataObjectTest_TeamComment', array(
262
			'"DataObjectTest_TeamComment"."Name"' => 'Joe'
263
		), false);
264
		$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...
265
266
		$comment2 = DataObject::get_one('DataObjectTest_TeamComment', array(
267
			'"DataObjectTest_TeamComment"."Name"' => 'Joe'
268
		), false);
269
		$this->assertNotEquals($comment1->Comment, $comment2->Comment);
270
271
		// Test get_one() with caching
272
		$comment1 = DataObject::get_one('DataObjectTest_TeamComment', array(
273
			'"DataObjectTest_TeamComment"."Name"' => 'Bob'
274
		), true);
275
		$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...
276
277
		$comment2 = DataObject::get_one('DataObjectTest_TeamComment', array(
278
			'"DataObjectTest_TeamComment"."Name"' => 'Bob'
279
		), true);
280
		$this->assertEquals((string)$comment1->Comment, (string)$comment2->Comment);
281
282
		// Test get_one() with order by without caching
283
		$comment = DataObject::get_one('DataObjectTest_TeamComment', '', false, "\"Name\" ASC");
284
		$this->assertEquals('Bob', $comment->Name);
285
286
		$comment = DataObject::get_one('DataObjectTest_TeamComment', '', false, "\"Name\" DESC");
287
		$this->assertEquals('Phil', $comment->Name);
288
289
		// Test get_one() with order by with caching
290
		$comment = DataObject::get_one('DataObjectTest_TeamComment', '', true, '"Name" ASC');
291
		$this->assertEquals('Bob', $comment->Name);
292
		$comment = DataObject::get_one('DataObjectTest_TeamComment', '', true, '"Name" DESC');
293
		$this->assertEquals('Phil', $comment->Name);
294
	}
295
296
	public function testGetCaseInsensitive() {
297
		// Test get_one() with bad case on the classname
298
		// Note: This will succeed only if the underlying DB server supports case-insensitive
299
		// table names (e.g. such as MySQL, but not SQLite3)
300
		if(!(DB::get_conn() instanceof MySQLDatabase)) {
301
			$this->markTestSkipped('MySQL only');
302
		}
303
304
		$subteam1 = DataObject::get_one('dataobjecttest_subteam', array(
305
			'"DataObjectTest_Team"."Title"' => 'Subteam 1'
306
		), true);
307
		$this->assertNotEmpty($subteam1);
308
		$this->assertEquals($subteam1->Title, "Subteam 1");
309
	}
310
311
	public function testGetSubclassFields() {
312
		/* Test that fields / has_one relations from the parent table and the subclass tables are extracted */
313
		$captain1 = $this->objFromFixture("DataObjectTest_Player", "captain1");
314
		// Base field
315
		$this->assertEquals('Captain', $captain1->FirstName);
316
		// Subclass field
317
		$this->assertEquals('007', $captain1->ShirtNumber);
318
		// Subclass has_one relation
319
		$this->assertEquals($this->idFromFixture('DataObjectTest_Team', 'team1'), $captain1->FavouriteTeamID);
320
	}
321
322
	public function testGetRelationClass() {
323
		$obj = new DataObjectTest_Player();
324
		$this->assertEquals(singleton('DataObjectTest_Player')->getRelationClass('FavouriteTeam'),
325
			'DataObjectTest_Team', 'has_one is properly inspected');
326
		$this->assertEquals(singleton('DataObjectTest_Company')->getRelationClass('CurrentStaff'),
327
			'DataObjectTest_Staff', 'has_many is properly inspected');
328
		$this->assertEquals(singleton('DataObjectTest_Team')->getRelationClass('Players'), 'DataObjectTest_Player',
329
			'many_many is properly inspected');
330
		$this->assertEquals(singleton('DataObjectTest_Player')->getRelationClass('Teams'), 'DataObjectTest_Team',
331
			'belongs_many_many is properly inspected');
332
		$this->assertEquals(singleton('DataObjectTest_CEO')->getRelationClass('Company'), 'DataObjectTest_Company',
333
			'belongs_to is properly inspected');
334
		$this->assertEquals(singleton('DataObjectTest_Fan')->getRelationClass('Favourite'), 'SilverStripe\\ORM\\DataObject',
335
			'polymorphic has_one is properly inspected');
336
	}
337
338
	/**
339
	 * Test that has_one relations can be retrieved
340
	 */
341
	public function testGetHasOneRelations() {
342
		$captain1 = $this->objFromFixture("DataObjectTest_Player", "captain1");
343
		$team1ID = $this->idFromFixture('DataObjectTest_Team', 'team1');
344
345
		// There will be a field called (relname)ID that contains the ID of the
346
		// object linked to via the has_one relation
347
		$this->assertEquals($team1ID, $captain1->FavouriteTeamID);
348
349
		// There will be a method called $obj->relname() that returns the object itself
350
		$this->assertEquals($team1ID, $captain1->FavouriteTeam()->ID);
351
352
		// Test that getNonReciprocalComponent can find has_one from the has_many end
353
		$this->assertEquals(
354
			$team1ID,
355
			$captain1->inferReciprocalComponent('DataObjectTest_Team', 'PlayerFans')->ID
356
		);
357
358
		// Check entity with polymorphic has-one
359
		$fan1 = $this->objFromFixture("DataObjectTest_Fan", "fan1");
360
		$this->assertTrue((bool)$fan1->hasValue('Favourite'));
361
362
		// There will be fields named (relname)ID and (relname)Class for polymorphic
363
		// entities
364
		$this->assertEquals($team1ID, $fan1->FavouriteID);
365
		$this->assertEquals('DataObjectTest_Team', $fan1->FavouriteClass);
366
367
		// There will be a method called $obj->relname() that returns the object itself
368
		$favourite = $fan1->Favourite();
369
		$this->assertEquals($team1ID, $favourite->ID);
370
		$this->assertInstanceOf('DataObjectTest_Team', $favourite);
371
372
		// check behaviour of dbObject with polymorphic relations
373
		$favouriteDBObject = $fan1->dbObject('Favourite');
374
		$favouriteValue = $favouriteDBObject->getValue();
375
		$this->assertInstanceOf('SilverStripe\\ORM\\FieldType\\DBPolymorphicForeignKey', $favouriteDBObject);
376
		$this->assertEquals($favourite->ID, $favouriteValue->ID);
377
		$this->assertEquals($favourite->ClassName, $favouriteValue->ClassName);
378
	}
379
380
	/**
381
	 * Simple test to ensure that namespaced classes and polymorphic relations work together
382
	 */
383
	public function testPolymorphicNamespacedRelations() {
384
		$parent = new \DataObjectTest\NamespacedClass();
385
		$parent->Name = 'New Parent';
0 ignored issues
show
Documentation introduced by
The property Name does not exist on object<DataObjectTest\NamespacedClass>. 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...
386
		$parent->write();
387
388
		$child = new \DataObjectTest\RelationClass();
389
		$child->Title = 'New Child';
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<DataObjectTest\RelationClass>. 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...
390
		$child->write();
391
		$parent->Relations()->add($child);
0 ignored issues
show
Bug introduced by
The method Relations() does not exist on DataObjectTest\NamespacedClass. Did you maybe mean duplicateManyManyRelations()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
392
393
		$this->assertEquals(1, $parent->Relations()->count());
0 ignored issues
show
Bug introduced by
The method Relations() does not exist on DataObjectTest\NamespacedClass. Did you maybe mean duplicateManyManyRelations()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
394
		$this->assertEquals(array('New Child'), $parent->Relations()->column('Title'));
0 ignored issues
show
Bug introduced by
The method Relations() does not exist on DataObjectTest\NamespacedClass. Did you maybe mean duplicateManyManyRelations()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
395
		$this->assertEquals('New Parent', $child->Parent()->Name);
0 ignored issues
show
Bug introduced by
The method Parent() does not exist on DataObjectTest\RelationClass. Did you maybe mean parentClass()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

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