Completed
Pull Request — master (#5653)
by Damian
12:15
created

testWritingInvalidDataObjectThrowsException()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 6
rs 9.4285
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 18 and the first side effect is on line 2061.

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
4
use SilverStripe\ORM\FieldType\DBField;
5
use SilverStripe\ORM\DataObject;
6
use SilverStripe\ORM\DB;
7
use SilverStripe\ORM\Connect\MySQLDatabase;
8
use SilverStripe\ORM\DataExtension;
9
use SilverStripe\ORM\ValidationResult;
10
use SilverStripe\Security\Member;
11
12
13
14
/**
15
 * @package framework
16
 * @subpackage tests
17
 */
18
class DataObjectTest extends SapphireTest {
19
20
	protected static $fixture_file = 'DataObjectTest.yml';
21
22
	/**
23
	 * Standard set of dataobject test classes
24
	 *
25
	 * @var array
26
	 */
27
	public static $extra_data_objects = array(
28
		'DataObjectTest_Team',
29
		'DataObjectTest_Fixture',
30
		'DataObjectTest_SubTeam',
31
		'OtherSubclassWithSameField',
32
		'DataObjectTest_FieldlessTable',
33
		'DataObjectTest_FieldlessSubTable',
34
		'DataObjectTest_ValidatedObject',
35
		'DataObjectTest_Player',
36
		'DataObjectTest_TeamComment',
37
		'DataObjectTest_EquipmentCompany',
38
		'DataObjectTest_SubEquipmentCompany',
39
		'DataObjectTest\NamespacedClass',
40
		'DataObjectTest\RelationClass',
41
		'DataObjectTest_ExtendedTeamComment',
42
		'DataObjectTest_Company',
43
		'DataObjectTest_Staff',
44
		'DataObjectTest_CEO',
45
		'DataObjectTest_Fan',
46
		'DataObjectTest_Play',
47
		'DataObjectTest_Ploy',
48
		'DataObjectTest_Bogey',
49
		// From ManyManyListTest
50
		'ManyManyListTest_ExtraFields',
51
		'ManyManyListTest_Product',
52
		'ManyManyListTest_Category',
53
	);
54
55
	public function setUpOnce() {
56
		$this->extraDataObjects = static::$extra_data_objects;
57
		parent::setUpOnce();
58
	}
59
60
	public function testDb() {
61
		$obj = new DataObjectTest_TeamComment();
62
		$dbFields = $obj->db();
63
64
		// Assert fields are included
65
		$this->assertArrayHasKey('Name', $dbFields);
66
67
		// Assert the base fields are included
68
		$this->assertArrayHasKey('Created', $dbFields);
69
		$this->assertArrayHasKey('LastEdited', $dbFields);
70
		$this->assertArrayHasKey('ClassName', $dbFields);
71
		$this->assertArrayHasKey('ID', $dbFields);
72
73
		// Assert that the correct field type is returned when passing a field
74
		$this->assertEquals('Varchar', $obj->db('Name'));
75
		$this->assertEquals('Text', $obj->db('Comment'));
76
77
		// Test with table required
78
		$this->assertEquals('DataObjectTest_TeamComment.Varchar', $obj->db('Name', true));
79
		$this->assertEquals('DataObjectTest_TeamComment.Text', $obj->db('Comment', true));
80
81
		$obj = new DataObjectTest_ExtendedTeamComment();
82
		$dbFields = $obj->db();
83
84
		// fixed fields are still included in extended classes
85
		$this->assertArrayHasKey('Created', $dbFields);
86
		$this->assertArrayHasKey('LastEdited', $dbFields);
87
		$this->assertArrayHasKey('ClassName', $dbFields);
88
		$this->assertArrayHasKey('ID', $dbFields);
89
90
		// Assert overloaded fields have correct data type
91
		$this->assertEquals('HTMLText', $obj->db('Comment'));
92
		$this->assertEquals('HTMLText', $dbFields['Comment'],
93
			'Calls to DataObject::db without a field specified return correct data types');
94
95
		// assertEquals doesn't verify the order of array elements, so access keys manually to check order:
96
		// 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...
97
		$this->assertEquals(
98
			array(
99
				'Name',
100
				'Comment'
101
			),
102
			array_slice(array_keys($dbFields), 4, 2),
103
			'DataObject::db returns fields in correct order'
104
		);
105
	}
106
107
	public function testConstructAcceptsValues() {
108
		// Values can be an array...
109
		$player = new DataObjectTest_Player(array(
110
			'FirstName' => 'James',
111
			'Surname' => 'Smith'
112
		));
113
114
		$this->assertEquals('James', $player->FirstName);
115
		$this->assertEquals('Smith', $player->Surname);
116
117
		// ... or a stdClass inst
118
		$data = new stdClass();
119
		$data->FirstName = 'John';
120
		$data->Surname = 'Doe';
121
		$player = new DataObjectTest_Player($data);
122
123
		$this->assertEquals('John', $player->FirstName);
124
		$this->assertEquals('Doe', $player->Surname);
125
126
		// IDs should be stored as integers, not strings
127
		$player = new DataObjectTest_Player(array('ID' => '5'));
128
		$this->assertSame(5, $player->ID);
129
	}
130
131
	public function testValidObjectsForBaseFields() {
132
		$obj = new DataObjectTest_ValidatedObject();
133
134
		foreach (array('Created', 'LastEdited', 'ClassName', 'ID') as $field) {
135
			$helper = $obj->dbObject($field);
136
			$this->assertTrue(
137
				($helper instanceof DBField),
138
				"for {$field} expected helper to be DBField, but was " .
139
				(is_object($helper) ? get_class($helper) : "null")
140
			);
141
		}
142
	}
143
144
	public function testDataIntegrityWhenTwoSubclassesHaveSameField() {
145
		// Save data into DataObjectTest_SubTeam.SubclassDatabaseField
146
		$obj = new DataObjectTest_SubTeam();
147
		$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...
148
		$obj->write();
149
150
		// Change the class
151
		$obj->ClassName = 'OtherSubclassWithSameField';
152
		$obj->write();
153
		$obj->flushCache();
154
155
		// Re-fetch from the database and confirm that the data is sourced from
156
		// OtherSubclassWithSameField.SubclassDatabaseField
157
		$obj = DataObject::get_by_id('DataObjectTest_Team', $obj->ID);
158
		$this->assertNull($obj->SubclassDatabaseField);
159
160
		// Confirm that save the object in the other direction.
161
		$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...
162
		$obj->write();
163
164
		$obj->ClassName = 'DataObjectTest_SubTeam';
165
		$obj->write();
166
		$obj->flushCache();
167
168
		// If we restore the class, the old value has been lying dormant and will be available again.
169
		// NOTE: This behaviour is volatile; we may change this in the future to clear fields that
170
		// are no longer relevant when changing ClassName
171
		$obj = DataObject::get_by_id('DataObjectTest_Team', $obj->ID);
172
		$this->assertEquals('obj-SubTeam', $obj->SubclassDatabaseField);
173
	}
174
175
	/**
176
	 * Test deletion of DataObjects
177
	 *   - Deleting using delete() on the DataObject
178
	 *   - Deleting using DataObject::delete_by_id()
179
	 */
180
	public function testDelete() {
181
		// Test deleting using delete() on the DataObject
182
		// Get the first page
183
		$obj = $this->objFromFixture('DataObjectTest_Player', 'captain1');
184
		$objID = $obj->ID;
185
		// Check the page exists before deleting
186
		$this->assertTrue(is_object($obj) && $obj->exists());
187
		// Delete the page
188
		$obj->delete();
189
		// Check that page does not exist after deleting
190
		$obj = DataObject::get_by_id('DataObjectTest_Player', $objID);
191
		$this->assertTrue(!$obj || !$obj->exists());
192
193
194
		// Test deleting using DataObject::delete_by_id()
195
		// Get the second page
196
		$obj = $this->objFromFixture('DataObjectTest_Player', 'captain2');
197
		$objID = $obj->ID;
198
		// Check the page exists before deleting
199
		$this->assertTrue(is_object($obj) && $obj->exists());
200
		// Delete the page
201
		DataObject::delete_by_id('DataObjectTest_Player', $obj->ID);
202
		// Check that page does not exist after deleting
203
		$obj = DataObject::get_by_id('DataObjectTest_Player', $objID);
204
		$this->assertTrue(!$obj || !$obj->exists());
205
	}
206
207
	/**
208
	 * Test methods that get DataObjects
209
	 *   - DataObject::get()
210
	 *       - All records of a DataObject
211
	 *       - Filtering
212
	 *       - Sorting
213
	 *       - Joins
214
	 *       - Limit
215
	 *       - Container class
216
	 *   - DataObject::get_by_id()
217
	 *   - DataObject::get_one()
218
	 *        - With and without caching
219
	 *        - With and without ordering
220
	 */
221
	public function testGet() {
222
		// Test getting all records of a DataObject
223
		$comments = DataObject::get('DataObjectTest_TeamComment');
224
		$this->assertEquals(3, $comments->Count());
225
226
		// Test WHERE clause
227
		$comments = DataObject::get('DataObjectTest_TeamComment', "\"Name\"='Bob'");
228
		$this->assertEquals(1, $comments->Count());
229
		foreach($comments as $comment) {
230
			$this->assertEquals('Bob', $comment->Name);
231
		}
232
233
		// Test sorting
234
		$comments = DataObject::get('DataObjectTest_TeamComment', '', "\"Name\" ASC");
235
		$this->assertEquals(3, $comments->Count());
236
		$this->assertEquals('Bob', $comments->First()->Name);
237
		$comments = DataObject::get('DataObjectTest_TeamComment', '', "\"Name\" DESC");
238
		$this->assertEquals(3, $comments->Count());
239
		$this->assertEquals('Phil', $comments->First()->Name);
240
241
		// Test limit
242
		$comments = DataObject::get('DataObjectTest_TeamComment', '', "\"Name\" ASC", '', '1,2');
243
		$this->assertEquals(2, $comments->Count());
244
		$this->assertEquals('Joe', $comments->First()->Name);
245
		$this->assertEquals('Phil', $comments->Last()->Name);
246
247
		// Test get_by_id()
248
		$captain1ID = $this->idFromFixture('DataObjectTest_Player', 'captain1');
249
		$captain1 = DataObject::get_by_id('DataObjectTest_Player', $captain1ID);
250
		$this->assertEquals('Captain', $captain1->FirstName);
251
252
		// Test get_one() without caching
253
		$comment1 = DataObject::get_one('DataObjectTest_TeamComment', array(
254
			'"DataObjectTest_TeamComment"."Name"' => 'Joe'
255
		), false);
256
		$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...
257
258
		$comment2 = DataObject::get_one('DataObjectTest_TeamComment', array(
259
			'"DataObjectTest_TeamComment"."Name"' => 'Joe'
260
		), false);
261
		$this->assertNotEquals($comment1->Comment, $comment2->Comment);
262
263
		// Test get_one() with caching
264
		$comment1 = DataObject::get_one('DataObjectTest_TeamComment', array(
265
			'"DataObjectTest_TeamComment"."Name"' => 'Bob'
266
		), true);
267
		$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...
268
269
		$comment2 = DataObject::get_one('DataObjectTest_TeamComment', array(
270
			'"DataObjectTest_TeamComment"."Name"' => 'Bob'
271
		), true);
272
		$this->assertEquals((string)$comment1->Comment, (string)$comment2->Comment);
273
274
		// Test get_one() with order by without caching
275
		$comment = DataObject::get_one('DataObjectTest_TeamComment', '', false, "\"Name\" ASC");
276
		$this->assertEquals('Bob', $comment->Name);
277
278
		$comment = DataObject::get_one('DataObjectTest_TeamComment', '', false, "\"Name\" DESC");
279
		$this->assertEquals('Phil', $comment->Name);
280
281
		// Test get_one() with order by with caching
282
		$comment = DataObject::get_one('DataObjectTest_TeamComment', '', true, '"Name" ASC');
283
		$this->assertEquals('Bob', $comment->Name);
284
		$comment = DataObject::get_one('DataObjectTest_TeamComment', '', true, '"Name" DESC');
285
		$this->assertEquals('Phil', $comment->Name);
286
	}
287
288
	public function testGetCaseInsensitive() {
289
		// Test get_one() with bad case on the classname
290
		// Note: This will succeed only if the underlying DB server supports case-insensitive
291
		// table names (e.g. such as MySQL, but not SQLite3)
292
		if(!(DB::get_conn() instanceof MySQLDatabase)) {
293
			$this->markTestSkipped('MySQL only');
294
		}
295
296
		$subteam1 = DataObject::get_one('dataobjecttest_subteam', array(
297
			'"DataObjectTest_Team"."Title"' => 'Subteam 1'
298
		), true);
299
		$this->assertNotEmpty($subteam1);
300
		$this->assertEquals($subteam1->Title, "Subteam 1");
301
	}
302
303
	public function testGetSubclassFields() {
304
		/* Test that fields / has_one relations from the parent table and the subclass tables are extracted */
305
		$captain1 = $this->objFromFixture("DataObjectTest_Player", "captain1");
306
		// Base field
307
		$this->assertEquals('Captain', $captain1->FirstName);
308
		// Subclass field
309
		$this->assertEquals('007', $captain1->ShirtNumber);
310
		// Subclass has_one relation
311
		$this->assertEquals($this->idFromFixture('DataObjectTest_Team', 'team1'), $captain1->FavouriteTeamID);
312
	}
313
314
	public function testGetRelationClass() {
315
		$obj = new DataObjectTest_Player();
316
		$this->assertEquals(singleton('DataObjectTest_Player')->getRelationClass('FavouriteTeam'),
317
			'DataObjectTest_Team', 'has_one is properly inspected');
318
		$this->assertEquals(singleton('DataObjectTest_Company')->getRelationClass('CurrentStaff'),
319
			'DataObjectTest_Staff', 'has_many is properly inspected');
320
		$this->assertEquals(singleton('DataObjectTest_Team')->getRelationClass('Players'), 'DataObjectTest_Player',
321
			'many_many is properly inspected');
322
		$this->assertEquals(singleton('DataObjectTest_Player')->getRelationClass('Teams'), 'DataObjectTest_Team',
323
			'belongs_many_many is properly inspected');
324
		$this->assertEquals(singleton('DataObjectTest_CEO')->getRelationClass('Company'), 'DataObjectTest_Company',
325
			'belongs_to is properly inspected');
326
		$this->assertEquals(singleton('DataObjectTest_Fan')->getRelationClass('Favourite'), 'SilverStripe\\ORM\\DataObject',
327
			'polymorphic has_one is properly inspected');
328
	}
329
330
	/**
331
	 * Test that has_one relations can be retrieved
332
	 */
333
	public function testGetHasOneRelations() {
334
		$captain1 = $this->objFromFixture("DataObjectTest_Player", "captain1");
335
		$team1ID = $this->idFromFixture('DataObjectTest_Team', 'team1');
336
337
		// There will be a field called (relname)ID that contains the ID of the
338
		// object linked to via the has_one relation
339
		$this->assertEquals($team1ID, $captain1->FavouriteTeamID);
340
341
		// There will be a method called $obj->relname() that returns the object itself
342
		$this->assertEquals($team1ID, $captain1->FavouriteTeam()->ID);
343
344
		// Test that getNonReciprocalComponent can find has_one from the has_many end
345
		$this->assertEquals(
346
			$team1ID,
347
			$captain1->inferReciprocalComponent('DataObjectTest_Team', 'PlayerFans')->ID
348
		);
349
350
		// Check entity with polymorphic has-one
351
		$fan1 = $this->objFromFixture("DataObjectTest_Fan", "fan1");
352
		$this->assertTrue((bool)$fan1->hasValue('Favourite'));
353
354
		// There will be fields named (relname)ID and (relname)Class for polymorphic
355
		// entities
356
		$this->assertEquals($team1ID, $fan1->FavouriteID);
357
		$this->assertEquals('DataObjectTest_Team', $fan1->FavouriteClass);
358
359
		// There will be a method called $obj->relname() that returns the object itself
360
		$favourite = $fan1->Favourite();
361
		$this->assertEquals($team1ID, $favourite->ID);
362
		$this->assertInstanceOf('DataObjectTest_Team', $favourite);
363
364
		// check behaviour of dbObject with polymorphic relations
365
		$favouriteDBObject = $fan1->dbObject('Favourite');
366
		$favouriteValue = $favouriteDBObject->getValue();
367
		$this->assertInstanceOf('SilverStripe\\ORM\\FieldType\\DBPolymorphicForeignKey', $favouriteDBObject);
368
		$this->assertEquals($favourite->ID, $favouriteValue->ID);
369
		$this->assertEquals($favourite->ClassName, $favouriteValue->ClassName);
370
	}
371
372
	/**
373
	 * Simple test to ensure that namespaced classes and polymorphic relations work together
374
	 */
375
	public function testPolymorphicNamespacedRelations() {
376
		$parent = new \DataObjectTest\NamespacedClass();
377
		$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...
378
		$parent->write();
379
380
		$child = new \DataObjectTest\RelationClass();
381
		$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...
382
		$child->write();
383
		$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...
384
385
		$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...
386
		$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...
387
		$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...
388
	}
389
390
	public function testLimitAndCount() {
391
		$players = DataObject::get("DataObjectTest_Player");
392
393
		// There's 4 records in total
394
		$this->assertEquals(4, $players->count());
395
396
		// Testing "##, ##" syntax
397
		$this->assertEquals(4, $players->limit(20)->count());
398
		$this->assertEquals(4, $players->limit(20, 0)->count());
399
		$this->assertEquals(0, $players->limit(20, 20)->count());
400
		$this->assertEquals(2, $players->limit(2, 0)->count());
401
		$this->assertEquals(1, $players->limit(5, 3)->count());
402
	}
403
404
	/**
405
	 * Test writing of database columns which don't correlate to a DBField,
406
	 * e.g. all relation fields on has_one/has_many like "ParentID".
407
	 *
408
	 */
409
	public function testWritePropertyWithoutDBField() {
410
		$obj = $this->objFromFixture('DataObjectTest_Player', 'captain1');
411
		$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...
412
		$obj->write();
413
414
		// reload the page from the database
415
		$savedObj = DataObject::get_by_id('DataObjectTest_Player', $obj->ID);
416
		$this->assertTrue($savedObj->FavouriteTeamID == 99);
417
418
		// Test with porymorphic relation
419
		$obj2 = $this->objFromFixture("DataObjectTest_Fan", "fan1");
420
		$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...
421
		$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...
422
		$obj2->write();
423
424
		$savedObj2 = DataObject::get_by_id('DataObjectTest_Fan', $obj2->ID);
425
		$this->assertTrue($savedObj2->FavouriteID == 99);
426
		$this->assertTrue($savedObj2->FavouriteClass == 'DataObjectTest_Player');
427
	}
428
429
	/**
430
	 * Test has many relationships
431
	 *   - Test getComponents() gets the ComponentSet of the other side of the relation
432
	 *   - Test the IDs on the DataObjects are set correctly
433
	 */
434
	public function testHasManyRelationships() {
435
		$team1 = $this->objFromFixture('DataObjectTest_Team', 'team1');
436
437
		// Test getComponents() gets the ComponentSet of the other side of the relation
438
		$this->assertTrue($team1->Comments()->Count() == 2);
439
440
		$team1Comments = [
441
			['Comment' => 'This is a team comment by Joe'],
442
			['Comment' => 'This is a team comment by Bob'],
443
		];
444
445
		// Test the IDs on the DataObjects are set correctly
446
		$this->assertDOSEquals($team1Comments, $team1->Comments());
447
448
		// Test that has_many can be infered from the has_one via getNonReciprocalComponent
449
		$this->assertDOSEquals(
450
			$team1Comments,
451
			$team1->inferReciprocalComponent('DataObjectTest_TeamComment', 'Team')
452
		);
453
454
		// Test that we can add and remove items that already exist in the database
455
		$newComment = new DataObjectTest_TeamComment();
456
		$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...
457
		$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...
458
		$newComment->write();
459
		$team1->Comments()->add($newComment);
460
		$this->assertEquals($team1->ID, $newComment->TeamID);
461
462
		$comment1 = $this->objFromFixture('DataObjectTest_TeamComment', 'comment1');
463
		$comment2 = $this->objFromFixture('DataObjectTest_TeamComment', 'comment2');
464
		$team1->Comments()->remove($comment2);
465
466
		$team1CommentIDs = $team1->Comments()->sort('ID')->column('ID');
467
		$this->assertEquals(array($comment1->ID, $newComment->ID), $team1CommentIDs);
468
469
		// Test that removing an item from a list doesn't remove it from the same
470
		// relation belonging to a different object
471
		$team1 = $this->objFromFixture('DataObjectTest_Team', 'team1');
472
		$team2 = $this->objFromFixture('DataObjectTest_Team', 'team2');
473
		$team2->Comments()->remove($comment1);
474
		$team1CommentIDs = $team1->Comments()->sort('ID')->column('ID');
475
		$this->assertEquals(array($comment1->ID, $newComment->ID), $team1CommentIDs);
476
	}
477
478
479
	/**
480
	 * Test has many relationships against polymorphic has_one fields
481
	 *   - Test getComponents() gets the ComponentSet of the other side of the relation
482
	 *   - Test the IDs on the DataObjects are set correctly
483
	 */
484
	public function testHasManyPolymorphicRelationships() {
485
		$team1 = $this->objFromFixture('DataObjectTest_Team', 'team1');
486
487
		// Test getComponents() gets the ComponentSet of the other side of the relation
488
		$this->assertTrue($team1->Fans()->Count() == 2);
489
490
		// Test the IDs/Classes on the DataObjects are set correctly
491
		foreach($team1->Fans() as $fan) {
492
			$this->assertEquals($team1->ID, $fan->FavouriteID, 'Fan has the correct FavouriteID');
493
			$this->assertEquals('DataObjectTest_Team', $fan->FavouriteClass, 'Fan has the correct FavouriteClass');
494
		}
495
496
		// Test that we can add and remove items that already exist in the database
497
		$newFan = new DataObjectTest_Fan();
498
		$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...
499
		$newFan->write();
500
		$team1->Fans()->add($newFan);
501
		$this->assertEquals($team1->ID, $newFan->FavouriteID, 'Newly created fan has the correct FavouriteID');
502
		$this->assertEquals(
503
			'DataObjectTest_Team',
504
			$newFan->FavouriteClass,
505
			'Newly created fan has the correct FavouriteClass'
506
		);
507
508
		$fan1 = $this->objFromFixture('DataObjectTest_Fan', 'fan1');
509
		$fan3 = $this->objFromFixture('DataObjectTest_Fan', 'fan3');
510
		$team1->Fans()->remove($fan3);
511
512
		$team1FanIDs = $team1->Fans()->sort('ID')->column('ID');
513
		$this->assertEquals(array($fan1->ID, $newFan->ID), $team1FanIDs);
514
515
		// Test that removing an item from a list doesn't remove it from the same
516
		// relation belonging to a different object
517
		$team1 = $this->objFromFixture('DataObjectTest_Team', 'team1');
518
		$player1 = $this->objFromFixture('DataObjectTest_Player', 'player1');
519
		$player1->Fans()->remove($fan1);
520
		$team1FanIDs = $team1->Fans()->sort('ID')->column('ID');
521
		$this->assertEquals(array($fan1->ID, $newFan->ID), $team1FanIDs);
522
	}
523
524
525
	public function testHasOneRelationship() {
526
		$team1 = $this->objFromFixture('DataObjectTest_Team', 'team1');
527
		$player1 = $this->objFromFixture('DataObjectTest_Player', 'player1');
528
		$player2 = $this->objFromFixture('DataObjectTest_Player', 'player2');
529
		$fan1 = $this->objFromFixture('DataObjectTest_Fan', 'fan1');
530
531
		// Test relation probing
532
		$this->assertFalse((bool)$team1->hasValue('Captain', null, false));
533
		$this->assertFalse((bool)$team1->hasValue('CaptainID', null, false));
534
535
		// Add a captain to team 1
536
		$team1->setField('CaptainID', $player1->ID);
537
		$team1->write();
538
539
		$this->assertTrue((bool)$team1->hasValue('Captain', null, false));
540
		$this->assertTrue((bool)$team1->hasValue('CaptainID', null, false));
541
542
		$this->assertEquals($player1->ID, $team1->Captain()->ID,
543
			'The captain exists for team 1');
544
		$this->assertEquals($player1->ID, $team1->getComponent('Captain')->ID,
545
			'The captain exists through the component getter');
546
547
		$this->assertEquals($team1->Captain()->FirstName, 'Player 1',
548
			'Player 1 is the captain');
549
		$this->assertEquals($team1->getComponent('Captain')->FirstName, 'Player 1',
550
			'Player 1 is the captain');
551
552
		$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...
553
		$team1->write();
554
555
		$this->assertEquals($player2->ID, $team1->Captain()->ID);
556
		$this->assertEquals($player2->ID, $team1->getComponent('Captain')->ID);
557
		$this->assertEquals('Player 2', $team1->Captain()->FirstName);
558
		$this->assertEquals('Player 2', $team1->getComponent('Captain')->FirstName);
559
560
561
		// Set the favourite team for fan1
562
		$fan1->setField('FavouriteID', $team1->ID);
563
		$fan1->setField('FavouriteClass', $team1->class);
564
565
		$this->assertEquals($team1->ID, $fan1->Favourite()->ID, 'The team is assigned to fan 1');
566
		$this->assertInstanceOf($team1->class, $fan1->Favourite(), 'The team is assigned to fan 1');
567
		$this->assertEquals($team1->ID, $fan1->getComponent('Favourite')->ID,
568
			'The team exists through the component getter'
569
		);
570
		$this->assertInstanceOf($team1->class, $fan1->getComponent('Favourite'),
571
			'The team exists through the component getter'
572
		);
573
574
		$this->assertEquals($fan1->Favourite()->Title, 'Team 1',
575
			'Team 1 is the favourite');
576
		$this->assertEquals($fan1->getComponent('Favourite')->Title, 'Team 1',
577
			'Team 1 is the favourite');
578
	}
579
580
	/**
581
	 * @todo Extend type change tests (e.g. '0'==NULL)
582
	 */
583
	public function testChangedFields() {
584
		$obj = $this->objFromFixture('DataObjectTest_Player', 'captain1');
585
		$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...
586
		$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...
587
588
		$this->assertEquals(
589
			$obj->getChangedFields(true, DataObject::CHANGE_STRICT),
590
			array(
591
				'FirstName' => array(
592
					'before' => 'Captain',
593
					'after' => 'Captain-changed',
594
					'level' => DataObject::CHANGE_VALUE
595
				),
596
				'IsRetired' => array(
597
					'before' => 1,
598
					'after' => true,
599
					'level' => DataObject::CHANGE_STRICT
600
				)
601
			),
602
			'Changed fields are correctly detected with strict type changes (level=1)'
603
		);
604
605
		$this->assertEquals(
606
			$obj->getChangedFields(true, DataObject::CHANGE_VALUE),
607
			array(
608
				'FirstName' => array(
609
					'before'=>'Captain',
610
					'after'=>'Captain-changed',
611
					'level' => DataObject::CHANGE_VALUE
612
				)
613
			),
614
			'Changed fields are correctly detected while ignoring type changes (level=2)'
615
		);
616
617
		$newObj = new DataObjectTest_Player();
618
		$newObj->FirstName = "New Player";
619
		$this->assertEquals(
620
			array(
621
				'FirstName' => array(
622
					'before' => null,
623
					'after' => 'New Player',
624
					'level' => DataObject::CHANGE_VALUE
625
				)
626
			),
627
			$newObj->getChangedFields(true, DataObject::CHANGE_VALUE),
628
			'Initialised fields are correctly detected as full changes'
629
		);
630
	}
631
632
	public function testIsChanged() {
633
		$obj = $this->objFromFixture('DataObjectTest_Player', 'captain1');
634
		$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...
635
		$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...
636
		$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...
637
638
		// Now that DB fields are changed, isChanged is true
639
		$this->assertTrue($obj->isChanged('NonDBField'));
640
		$this->assertFalse($obj->isChanged('NonField'));
641
		$this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_STRICT));
642
		$this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_VALUE));
643
		$this->assertTrue($obj->isChanged('IsRetired', DataObject::CHANGE_STRICT));
644
		$this->assertFalse($obj->isChanged('IsRetired', DataObject::CHANGE_VALUE));
645
		$this->assertFalse($obj->isChanged('Email', 1), 'Doesnt change mark unchanged property');
646
		$this->assertFalse($obj->isChanged('Email', 2), 'Doesnt change mark unchanged property');
647
648
		$newObj = new DataObjectTest_Player();
649
		$newObj->FirstName = "New Player";
650
		$this->assertTrue($newObj->isChanged('FirstName', DataObject::CHANGE_STRICT));
651
		$this->assertTrue($newObj->isChanged('FirstName', DataObject::CHANGE_VALUE));
652
		$this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_STRICT));
653
		$this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_VALUE));
654
655
		$newObj->write();
656
		$this->assertFalse($newObj->ischanged());
657
		$this->assertFalse($newObj->isChanged('FirstName', DataObject::CHANGE_STRICT));
658
		$this->assertFalse($newObj->isChanged('FirstName', DataObject::CHANGE_VALUE));
659
		$this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_STRICT));
660
		$this->assertFalse($newObj->isChanged('Email', DataObject::CHANGE_VALUE));
661
662
		$obj = $this->objFromFixture('DataObjectTest_Player', 'captain1');
663
		$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...
664
		$this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_STRICT));
665
		$this->assertTrue($obj->isChanged('FirstName', DataObject::CHANGE_VALUE));
666
667
		/* Test when there's not field provided */
668
		$obj = $this->objFromFixture('DataObjectTest_Player', 'captain2');
669
		$this->assertFalse($obj->isChanged());
670
		$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...
671
		$this->assertFalse($obj->isChanged());
672
		$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...
673
		$this->assertTrue($obj->isChanged());
674
675
		$obj->write();
676
		$this->assertFalse($obj->isChanged());
677
	}
678
679
	public function testRandomSort() {
680
		/* If we perform the same regularly sorted query twice, it should return the same results */
681
		$itemsA = DataObject::get("DataObjectTest_TeamComment", "", "ID");
682
		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...
683
684
		$itemsB = DataObject::get("DataObjectTest_TeamComment", "", "ID");
685
		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...
686
687
		/* Test when there's not field provided */
688
		$obj = $this->objFromFixture('DataObjectTest_Player', 'captain1');
689
		$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...
690
		$this->assertTrue($obj->isChanged());
691
692
		$obj->write();
693
		$this->assertFalse($obj->isChanged());
694
695
		/* If we perform the same random query twice, it shouldn't return the same results */
696
		$itemsA = DataObject::get("DataObjectTest_TeamComment", "", DB::get_conn()->random());
697
		$itemsB = DataObject::get("DataObjectTest_TeamComment", "", DB::get_conn()->random());
698
		$itemsC = DataObject::get("DataObjectTest_TeamComment", "", DB::get_conn()->random());
699
		$itemsD = DataObject::get("DataObjectTest_TeamComment", "", DB::get_conn()->random());
700
		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...
701
		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...
702
		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...
703
		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...
704
705
		// These shouldn't all be the same (run it 4 times to minimise chance of an accidental collision)
706
		// There's about a 1 in a billion chance of an accidental collision
707
		$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...
708
	}
709
710
	public function testWriteSavesToHasOneRelations() {
711
		/* DataObject::write() should save to a has_one relationship if you set a field called (relname)ID */
712
		$team = new DataObjectTest_Team();
713
		$captainID = $this->idFromFixture('DataObjectTest_Player', 'player1');
714
		$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...
715
		$team->write();
716
		$this->assertEquals($captainID,
717
			DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $team->ID")->value());
718
719
		/* After giving it a value, you should also be able to set it back to null */
720
		$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...
721
		$team->write();
722
		$this->assertEquals(0,
723
			DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $team->ID")->value());
724
725
		/* You should also be able to save a blank to it when it's first created */
726
		$team = new DataObjectTest_Team();
727
		$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...
728
		$team->write();
729
		$this->assertEquals(0,
730
			DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $team->ID")->value());
731
732
		/* Ditto for existing records without a value */
733
		$existingTeam = $this->objFromFixture('DataObjectTest_Team', 'team1');
734
		$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...
735
		$existingTeam->write();
736
		$this->assertEquals(0,
737
			DB::query("SELECT \"CaptainID\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $existingTeam->ID")->value());
738
	}
739
740
	public function testCanAccessHasOneObjectsAsMethods() {
741
		/* If you have a has_one relation 'Captain' on $obj, and you set the $obj->CaptainID = (ID), then the
742
		 * object itself should be accessible as $obj->Captain() */
743
		$team = $this->objFromFixture('DataObjectTest_Team', 'team1');
744
		$captainID = $this->idFromFixture('DataObjectTest_Player', 'captain1');
745
746
		$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...
747
		$this->assertNotNull($team->Captain());
748
		$this->assertEquals($captainID, $team->Captain()->ID);
749
750
		// Test for polymorphic has_one relations
751
		$fan = $this->objFromFixture('DataObjectTest_Fan', 'fan1');
752
		$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...
753
		$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...
754
		$this->assertNotNull($fan->Favourite());
755
		$this->assertEquals($team->ID, $fan->Favourite()->ID);
756
		$this->assertInstanceOf($team->class, $fan->Favourite());
757
	}
758
759
	public function testFieldNamesThatMatchMethodNamesWork() {
760
		/* Check that a field name that corresponds to a method on DataObject will still work */
761
		$obj = new DataObjectTest_Fixture();
762
		$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...
763
		$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...
764
		$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...
765
		$obj->write();
766
767
		$this->assertNotNull($obj->ID);
768
		$this->assertEquals('value1',
769
			DB::query("SELECT \"Data\" FROM \"DataObjectTest_Fixture\" WHERE \"ID\" = $obj->ID")->value());
770
		$this->assertEquals('value2',
771
			DB::query("SELECT \"DbObject\" FROM \"DataObjectTest_Fixture\" WHERE \"ID\" = $obj->ID")->value());
772
		$this->assertEquals('value3',
773
			DB::query("SELECT \"Duplicate\" FROM \"DataObjectTest_Fixture\" WHERE \"ID\" = $obj->ID")->value());
774
	}
775
776
	/**
777
	 * @todo Re-enable all test cases for field existence after behaviour has been fixed
778
	 */
779
	public function testFieldExistence() {
780
		$teamInstance = $this->objFromFixture('DataObjectTest_Team', 'team1');
781
		$teamSingleton = singleton('DataObjectTest_Team');
782
783
		$subteamInstance = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam1');
784
		$subteamSingleton = singleton('DataObjectTest_SubTeam');
785
786
		/* hasField() singleton checks */
787
		$this->assertTrue($teamSingleton->hasField('ID'),
788
			'hasField() finds built-in fields in singletons');
789
		$this->assertTrue($teamSingleton->hasField('Title'),
790
			'hasField() finds custom fields in singletons');
791
792
		/* hasField() instance checks */
793
		$this->assertFalse($teamInstance->hasField('NonExistingField'),
794
			'hasField() doesnt find non-existing fields in instances');
795
		$this->assertTrue($teamInstance->hasField('ID'),
796
			'hasField() finds built-in fields in instances');
797
		$this->assertTrue($teamInstance->hasField('Created'),
798
			'hasField() finds built-in fields in instances');
799
		$this->assertTrue($teamInstance->hasField('DatabaseField'),
800
			'hasField() finds custom fields in instances');
801
		//$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...
802
		//'hasField() doesnt find subclass fields in parentclass instances');
803
		$this->assertTrue($teamInstance->hasField('DynamicField'),
804
			'hasField() finds dynamic getters in instances');
805
		$this->assertTrue($teamInstance->hasField('HasOneRelationshipID'),
806
			'hasField() finds foreign keys in instances');
807
		$this->assertTrue($teamInstance->hasField('ExtendedDatabaseField'),
808
			'hasField() finds extended fields in instances');
809
		$this->assertTrue($teamInstance->hasField('ExtendedHasOneRelationshipID'),
810
			'hasField() finds extended foreign keys in instances');
811
		//$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...
812
		//'hasField() includes extended dynamic getters in instances');
813
814
		/* hasField() subclass checks */
815
		$this->assertTrue($subteamInstance->hasField('ID'),
816
			'hasField() finds built-in fields in subclass instances');
817
		$this->assertTrue($subteamInstance->hasField('Created'),
818
			'hasField() finds built-in fields in subclass instances');
819
		$this->assertTrue($subteamInstance->hasField('DatabaseField'),
820
			'hasField() finds custom fields in subclass instances');
821
		$this->assertTrue($subteamInstance->hasField('SubclassDatabaseField'),
822
			'hasField() finds custom fields in subclass instances');
823
		$this->assertTrue($subteamInstance->hasField('DynamicField'),
824
			'hasField() finds dynamic getters in subclass instances');
825
		$this->assertTrue($subteamInstance->hasField('HasOneRelationshipID'),
826
			'hasField() finds foreign keys in subclass instances');
827
		$this->assertTrue($subteamInstance->hasField('ExtendedDatabaseField'),
828
			'hasField() finds extended fields in subclass instances');
829
		$this->assertTrue($subteamInstance->hasField('ExtendedHasOneRelationshipID'),
830
			'hasField() finds extended foreign keys in subclass instances');
831
832
		/* hasDatabaseField() singleton checks */
833
		//$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...
834
		//'hasDatabaseField() finds built-in fields in singletons');
835
		$this->assertTrue($teamSingleton->hasDatabaseField('Title'),
836
			'hasDatabaseField() finds custom fields in singletons');
837
838
		/* hasDatabaseField() instance checks */
839
		$this->assertFalse($teamInstance->hasDatabaseField('NonExistingField'),
840
			'hasDatabaseField() doesnt find non-existing fields in instances');
841
		//$this->assertTrue($teamInstance->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...
842
		//'hasDatabaseField() finds built-in fields in instances');
843
		$this->assertTrue($teamInstance->hasDatabaseField('Created'),
844
			'hasDatabaseField() finds built-in fields in instances');
845
		$this->assertTrue($teamInstance->hasDatabaseField('DatabaseField'),
846
			'hasDatabaseField() finds custom fields in instances');
847
		$this->assertFalse($teamInstance->hasDatabaseField('SubclassDatabaseField'),
848
			'hasDatabaseField() doesnt find subclass fields in parentclass instances');
849
		//$this->assertFalse($teamInstance->hasDatabaseField('DynamicField'),
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...
850
		//'hasDatabaseField() doesnt dynamic getters in instances');
851
		$this->assertTrue($teamInstance->hasDatabaseField('HasOneRelationshipID'),
852
			'hasDatabaseField() finds foreign keys in instances');
853
		$this->assertTrue($teamInstance->hasDatabaseField('ExtendedDatabaseField'),
854
			'hasDatabaseField() finds extended fields in instances');
855
		$this->assertTrue($teamInstance->hasDatabaseField('ExtendedHasOneRelationshipID'),
856
			'hasDatabaseField() finds extended foreign keys in instances');
857
		$this->assertFalse($teamInstance->hasDatabaseField('ExtendedDynamicField'),
858
			'hasDatabaseField() doesnt include extended dynamic getters in instances');
859
860
		/* hasDatabaseField() subclass checks */
861
		$this->assertTrue($subteamInstance->hasDatabaseField('DatabaseField'),
862
			'hasField() finds custom fields in subclass instances');
863
		$this->assertTrue($subteamInstance->hasDatabaseField('SubclassDatabaseField'),
864
			'hasField() finds custom fields in subclass instances');
865
866
	}
867
868
	/**
869
	 * @todo Re-enable all test cases for field inheritance aggregation after behaviour has been fixed
870
	 */
871
	public function testFieldInheritance() {
872
		$teamInstance = $this->objFromFixture('DataObjectTest_Team', 'team1');
873
		$subteamInstance = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam1');
874
875
		$this->assertEquals(
876
			array(
877
				'ID',
878
				'ClassName',
879
				'LastEdited',
880
				'Created',
881
				'Title',
882
				'DatabaseField',
883
				'ExtendedDatabaseField',
884
				'CaptainID',
885
				'FounderID',
886
				'HasOneRelationshipID',
887
				'ExtendedHasOneRelationshipID'
888
			),
889
			array_keys($teamInstance->db()),
890
			'inheritedDatabaseFields() contains all fields defined on instance: base, extended and foreign keys'
891
		);
892
893
		$this->assertEquals(
894
			array(
895
				'ID',
896
				'ClassName',
897
				'LastEdited',
898
				'Created',
899
				'Title',
900
				'DatabaseField',
901
				'ExtendedDatabaseField',
902
				'CaptainID',
903
				'FounderID',
904
				'HasOneRelationshipID',
905
				'ExtendedHasOneRelationshipID'
906
			),
907
			array_keys(DataObject::database_fields('DataObjectTest_Team', false)),
0 ignored issues
show
Unused Code introduced by
The call to DataObject::database_fields() has too many arguments starting with false.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
908
			'databaseFields() contains only fields defined on instance, including base, extended and foreign keys'
909
		);
910
911
		$this->assertEquals(
912
			array(
913
				'ID',
914
				'ClassName',
915
				'LastEdited',
916
				'Created',
917
				'Title',
918
				'DatabaseField',
919
				'ExtendedDatabaseField',
920
				'CaptainID',
921
				'FounderID',
922
				'HasOneRelationshipID',
923
				'ExtendedHasOneRelationshipID',
924
				'SubclassDatabaseField',
925
				'ParentTeamID',
926
			),
927
			array_keys($subteamInstance->db()),
928
			'inheritedDatabaseFields() on subclass contains all fields, including base, extended  and foreign keys'
929
		);
930
931
		$this->assertEquals(
932
			array(
933
				'ID',
934
				'SubclassDatabaseField',
935
				'ParentTeamID',
936
			),
937
			array_keys(DataObject::database_fields('DataObjectTest_SubTeam')),
938
			'databaseFields() on subclass contains only fields defined on instance'
939
		);
940
	}
941
942
	public function testSearchableFields() {
943
		$player = $this->objFromFixture('DataObjectTest_Player', 'captain1');
944
		$fields = $player->searchableFields();
945
		$this->assertArrayHasKey(
946
			'IsRetired',
947
			$fields,
948
			'Fields defined by $searchable_fields static are correctly detected'
949
		);
950
		$this->assertArrayHasKey(
951
			'ShirtNumber',
952
			$fields,
953
			'Fields defined by $searchable_fields static are correctly detected'
954
		);
955
956
		$team = $this->objFromFixture('DataObjectTest_Team', 'team1');
957
		$fields = $team->searchableFields();
958
		$this->assertArrayHasKey(
959
			'Title',
960
			$fields,
961
			'Fields can be inherited from the $summary_fields static, including methods called on fields'
962
		);
963
		$this->assertArrayHasKey(
964
			'Captain.ShirtNumber',
965
			$fields,
966
			'Fields on related objects can be inherited from the $summary_fields static'
967
		);
968
		$this->assertArrayHasKey(
969
			'Captain.FavouriteTeam.Title',
970
			$fields,
971
			'Fields on related objects can be inherited from the $summary_fields static'
972
		);
973
974
		$testObj = new DataObjectTest_Fixture();
975
		$fields = $testObj->searchableFields();
976
		$this->assertEmpty($fields);
977
	}
978
979
	public function testCastingHelper() {
980
		$team = $this->objFromFixture('DataObjectTest_Team', 'team1');
981
982
		$this->assertEquals('Varchar', $team->castingHelper('Title'), 'db field wasn\'t casted correctly');
983
		$this->assertEquals('HTMLVarchar', $team->castingHelper('DatabaseField'), 'db field wasn\'t casted correctly');
984
985
		$sponsor = $team->Sponsors()->first();
986
		$this->assertEquals('Int', $sponsor->castingHelper('SponsorFee'), 'many_many_extraFields not casted correctly');
987
	}
988
989
	public function testSummaryFieldsCustomLabels() {
990
		$team = $this->objFromFixture('DataObjectTest_Team', 'team1');
991
		$summaryFields = $team->summaryFields();
992
993
		$this->assertEquals(
994
			'Custom Title',
995
			$summaryFields['Title'],
996
			'Custom title is preserved'
997
		);
998
999
		$this->assertEquals(
1000
			'Captain\'s shirt number',
1001
			$summaryFields['Captain.ShirtNumber'],
1002
			'Custom title on relation is preserved'
1003
		);
1004
	}
1005
1006
	public function testDataObjectUpdate() {
1007
		/* update() calls can use the dot syntax to reference has_one relations and other methods that return
1008
		 * objects */
1009
		$team1 = $this->objFromFixture('DataObjectTest_Team', 'team1');
1010
		$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...
1011
1012
		$team1->update(array(
1013
			'DatabaseField' => 'Something',
1014
			'Captain.FirstName' => 'Jim',
1015
			'Captain.Email' => '[email protected]',
1016
			'Captain.FavouriteTeam.Title' => 'New and improved team 1',
1017
		));
1018
1019
		/* Test the simple case of updating fields on the object itself */
1020
		$this->assertEquals('Something', $team1->DatabaseField);
1021
1022
		/* Setting Captain.Email and Captain.FirstName will have updated DataObjectTest_Captain.captain1 in
1023
		 * the database.  Although update() doesn't usually write, it does write related records automatically. */
1024
		$captain1 = $this->objFromFixture('DataObjectTest_Player', 'captain1');
1025
		$this->assertEquals('Jim', $captain1->FirstName);
1026
		$this->assertEquals('[email protected]', $captain1->Email);
1027
1028
		/* Jim's favourite team is team 1; we need to reload the object to the the change that setting Captain.
1029
		 * FavouriteTeam.Title made */
1030
		$reloadedTeam1 = $this->objFromFixture('DataObjectTest_Team', 'team1');
1031
		$this->assertEquals('New and improved team 1', $reloadedTeam1->Title);
1032
	}
1033
1034
	public function testDataObjectUpdateNew() {
1035
		/* update() calls can use the dot syntax to reference has_one relations and other methods that return
1036
		 * objects */
1037
		$team1 = $this->objFromFixture('DataObjectTest_Team', 'team1');
1038
		$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...
1039
1040
		$team1->update(array(
1041
			'Captain.FirstName' => 'Jim',
1042
			'Captain.FavouriteTeam.Title' => 'New and improved team 1',
1043
		));
1044
		/* Test that the captain ID has been updated */
1045
		$this->assertGreaterThan(0, $team1->CaptainID);
1046
1047
		/* Fetch the newly created captain */
1048
		$captain1 = DataObjectTest_Player::get()->byID($team1->CaptainID);
1049
		$this->assertEquals('Jim', $captain1->FirstName);
1050
1051
		/* Grab the favourite team and make sure it has the correct values */
1052
		$reloadedTeam1 = $captain1->FavouriteTeam();
1053
		$this->assertEquals($reloadedTeam1->ID, $captain1->FavouriteTeamID);
1054
		$this->assertEquals('New and improved team 1', $reloadedTeam1->Title);
1055
	}
1056
1057
	public function testWritingInvalidDataObjectThrowsException() {
1058
		$validatedObject = new DataObjectTest_ValidatedObject();
1059
1060
		$this->setExpectedException('SilverStripe\\ORM\\ValidationException');
1061
		$validatedObject->write();
1062
	}
1063
1064
	public function testWritingValidDataObjectDoesntThrowException() {
1065
		$validatedObject = new DataObjectTest_ValidatedObject();
1066
		$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...
1067
1068
		$validatedObject->write();
1069
		$this->assertTrue($validatedObject->isInDB(), "Validated object was not saved to database");
1070
	}
1071
1072
	public function testSubclassCreation() {
1073
		/* Creating a new object of a subclass should set the ClassName field correctly */
1074
		$obj = new DataObjectTest_SubTeam();
1075
		$obj->write();
1076
		$this->assertEquals("DataObjectTest_SubTeam",
1077
			DB::query("SELECT \"ClassName\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $obj->ID")->value());
1078
	}
1079
1080
	public function testForceInsert() {
1081
		/* If you set an ID on an object and pass forceInsert = true, then the object should be correctly created */
1082
		$conn = DB::get_conn();
1083
		if(method_exists($conn, 'allowPrimaryKeyEditing')) $conn->allowPrimaryKeyEditing('DataObjectTest_Team', true);
1084
		$obj = new DataObjectTest_SubTeam();
1085
		$obj->ID = 1001;
1086
		$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...
1087
		$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...
1088
		$obj->write(false, true);
1089
		if(method_exists($conn, 'allowPrimaryKeyEditing')) $conn->allowPrimaryKeyEditing('DataObjectTest_Team', false);
1090
1091
		$this->assertEquals("DataObjectTest_SubTeam",
1092
			DB::query("SELECT \"ClassName\" FROM \"DataObjectTest_Team\" WHERE \"ID\" = $obj->ID")->value());
1093
1094
		/* Check that it actually saves to the database with the correct ID */
1095
		$this->assertEquals("1001", DB::query(
1096
			"SELECT \"ID\" FROM \"DataObjectTest_SubTeam\" WHERE \"SubclassDatabaseField\" = 'asdfasdf'")->value());
1097
		$this->assertEquals("1001",
1098
			DB::query("SELECT \"ID\" FROM \"DataObjectTest_Team\" WHERE \"Title\" = 'asdfasdf'")->value());
1099
	}
1100
1101
	public function TestHasOwnTable() {
1102
		/* Test DataObject::has_own_table() returns true if the object has $has_one or $db values */
1103
		$this->assertTrue(DataObject::has_own_table("DataObjectTest_Player"));
1104
		$this->assertTrue(DataObject::has_own_table("DataObjectTest_Team"));
1105
		$this->assertTrue(DataObject::has_own_table("DataObjectTest_Fixture"));
1106
1107
		/* Root DataObject that always have a table, even if they lack both $db and $has_one */
1108
		$this->assertTrue(DataObject::has_own_table("DataObjectTest_FieldlessTable"));
1109
1110
		/* Subclasses without $db or $has_one don't have a table */
1111
		$this->assertFalse(DataObject::has_own_table("DataObjectTest_FieldlessSubTable"));
1112
1113
		/* Return false if you don't pass it a subclass of DataObject */
1114
		$this->assertFalse(DataObject::has_own_table("SilverStripe\\ORM\\DataObject"));
1115
		$this->assertFalse(DataObject::has_own_table("ViewableData"));
1116
		$this->assertFalse(DataObject::has_own_table("ThisIsntADataObject"));
1117
	}
1118
1119
	public function testMerge() {
1120
		// test right merge of subclasses
1121
		$left = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam1');
1122
		$right = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam2_with_player_relation');
1123
		$leftOrigID = $left->ID;
1124
		$left->merge($right, 'right', false, false);
0 ignored issues
show
Bug introduced by
It seems like $right defined by $this->objFromFixture('D..._with_player_relation') on line 1122 can be null; however, SilverStripe\ORM\DataObject::merge() does not accept null, maybe add an additional type check?

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1125
		$this->assertEquals(
1126
			$left->Title,
1127
			'Subteam 2',
1128
			'merge() with "right" priority overwrites fields with existing values on subclasses'
1129
		);
1130
		$this->assertEquals(
1131
			$left->ID,
1132
			$leftOrigID,
1133
			'merge() with "right" priority doesnt overwrite database ID'
1134
		);
1135
1136
		// test overwriteWithEmpty flag on existing left values
1137
		$left = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam2_with_player_relation');
1138
		$right = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam3_with_empty_fields');
1139
		$left->merge($right, 'right', false, true);
0 ignored issues
show
Bug introduced by
It seems like $right defined by $this->objFromFixture('D...am3_with_empty_fields') on line 1138 can be null; however, SilverStripe\ORM\DataObject::merge() does not accept null, maybe add an additional type check?

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1140
		$this->assertEquals(
1141
			$left->Title,
1142
			'Subteam 3',
1143
			'merge() with $overwriteWithEmpty overwrites non-empty fields on left object'
1144
		);
1145
1146
		// test overwriteWithEmpty flag on empty left values
1147
		$left = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam1');
1148
		// $SubclassDatabaseField is empty on here
1149
		$right = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam2_with_player_relation');
1150
		$left->merge($right, 'right', false, true);
0 ignored issues
show
Bug introduced by
It seems like $right defined by $this->objFromFixture('D..._with_player_relation') on line 1149 can be null; however, SilverStripe\ORM\DataObject::merge() does not accept null, maybe add an additional type check?

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1151
		$this->assertEquals(
1152
			$left->SubclassDatabaseField,
1153
			NULL,
1154
			'merge() with $overwriteWithEmpty overwrites empty fields on left object'
1155
		);
1156
1157
		// @todo test "left" priority flag
1158
		// @todo test includeRelations flag
1159
		// @todo test includeRelations in combination with overwriteWithEmpty
1160
		// @todo test has_one relations
1161
		// @todo test has_many and many_many relations
1162
	}
1163
1164
	public function testPopulateDefaults() {
1165
		$obj = new DataObjectTest_Fixture();
1166
		$this->assertEquals(
1167
			$obj->MyFieldWithDefault,
1168
			'Default Value',
1169
			'Defaults are populated for in-memory object from $defaults array'
1170
		);
1171
1172
		$this->assertEquals(
1173
			$obj->MyFieldWithAltDefault,
1174
			'Default Value',
1175
			'Defaults are populated from overloaded populateDefaults() method'
1176
		);
1177
	}
1178
1179
	protected function makeAccessible($object, $method) {
1180
		$reflectionMethod = new ReflectionMethod($object, $method);
1181
		$reflectionMethod->setAccessible(true);
1182
		return $reflectionMethod;
1183
	}
1184
1185
	public function testValidateModelDefinitionsFailsWithArray() {
1186
		Config::nest();
1187
1188
		$object = new DataObjectTest_Team;
1189
		$method = $this->makeAccessible($object, 'validateModelDefinitions');
1190
1191
		Config::inst()->update('DataObjectTest_Team', 'has_one', array('NotValid' => array('NoArraysAllowed')));
1192
		$this->setExpectedException('LogicException');
1193
1194
		try {
1195
			$method->invoke($object);
1196
		} catch(Exception $e) {
1197
			Config::unnest(); // Catch the exception so we can unnest config before failing the test
1198
			throw $e;
1199
		}
1200
	}
1201
1202
	public function testValidateModelDefinitionsFailsWithIntKey() {
1203
		Config::nest();
1204
1205
		$object = new DataObjectTest_Team;
1206
		$method = $this->makeAccessible($object, 'validateModelDefinitions');
1207
1208
		Config::inst()->update('DataObjectTest_Team', 'has_many', array(12 => 'DataObjectTest_Player'));
1209
		$this->setExpectedException('LogicException');
1210
1211
		try {
1212
			$method->invoke($object);
1213
		} catch(Exception $e) {
1214
			Config::unnest(); // Catch the exception so we can unnest config before failing the test
1215
			throw $e;
1216
		}
1217
	}
1218
1219
	public function testValidateModelDefinitionsFailsWithIntValue() {
1220
		Config::nest();
1221
1222
		$object = new DataObjectTest_Team;
1223
		$method = $this->makeAccessible($object, 'validateModelDefinitions');
1224
1225
		Config::inst()->update('DataObjectTest_Team', 'many_many', array('Players' => 12));
1226
		$this->setExpectedException('LogicException');
1227
1228
		try {
1229
			$method->invoke($object);
1230
		} catch(Exception $e) {
1231
			Config::unnest(); // Catch the exception so we can unnest config before failing the test
1232
			throw $e;
1233
		}
1234
	}
1235
1236
	/**
1237
	 * many_many_extraFields is allowed to have an array value, so shouldn't throw an exception
1238
	 */
1239
	public function testValidateModelDefinitionsPassesWithExtraFields() {
1240
		Config::nest();
1241
1242
		$object = new DataObjectTest_Team;
1243
		$method = $this->makeAccessible($object, 'validateModelDefinitions');
1244
1245
		Config::inst()->update('DataObjectTest_Team', 'many_many_extraFields',
1246
			array('Relations' => array('Price' => 'Int')));
1247
1248
		try {
1249
			$method->invoke($object);
1250
		} catch(Exception $e) {
1251
			Config::unnest();
1252
			$this->fail('Exception should not be thrown');
1253
			throw $e;
1254
		}
1255
1256
		Config::unnest();
1257
	}
1258
1259
	public function testNewClassInstance() {
1260
		$dataObject = $this->objFromFixture('DataObjectTest_Team', 'team1');
1261
		$changedDO = $dataObject->newClassInstance('DataObjectTest_SubTeam');
1262
		$changedFields = $changedDO->getChangedFields();
1263
1264
		// Don't write the record, it will reset changed fields
1265
		$this->assertInstanceOf('DataObjectTest_SubTeam', $changedDO);
1266
		$this->assertEquals($changedDO->ClassName, 'DataObjectTest_SubTeam');
1267
		$this->assertContains('ClassName', array_keys($changedFields));
1268
		$this->assertEquals($changedFields['ClassName']['before'], 'DataObjectTest_Team');
1269
		$this->assertEquals($changedFields['ClassName']['after'], 'DataObjectTest_SubTeam');
1270
1271
		$changedDO->write();
1272
1273
		$this->assertInstanceOf('DataObjectTest_SubTeam', $changedDO);
1274
		$this->assertEquals($changedDO->ClassName, 'DataObjectTest_SubTeam');
1275
	}
1276
1277
	public function testMultipleManyManyWithSameClass() {
1278
		$team = $this->objFromFixture('DataObjectTest_Team', 'team1');
1279
		$company2 = $this->objFromFixture('DataObjectTest_EquipmentCompany', 'equipmentcompany2');
1280
		$sponsors = $team->Sponsors();
1281
		$equipmentSuppliers = $team->EquipmentSuppliers();
1282
1283
		// Check that DataObject::many_many() works as expected
1284
		list($class, $targetClass, $parentField, $childField, $joinTable) = $team->manyManyComponent('Sponsors');
0 ignored issues
show
Unused Code introduced by
The assignment to $parentField is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $childField is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
1285
		$this->assertEquals('DataObjectTest_Team', $class,
1286
			'DataObject::many_many() didn\'t find the correct base class');
1287
		$this->assertEquals('DataObjectTest_EquipmentCompany', $targetClass,
1288
			'DataObject::many_many() didn\'t find the correct target class for the relation');
1289
		$this->assertEquals('DataObjectTest_EquipmentCompany_SponsoredTeams', $joinTable,
1290
			'DataObject::many_many() didn\'t find the correct relation table');
1291
1292
		// Check that ManyManyList still works
1293
		$this->assertEquals(2, $sponsors->count(), 'Rows are missing from relation');
1294
		$this->assertEquals(1, $equipmentSuppliers->count(), 'Rows are missing from relation');
1295
1296
		// Check everything works when no relation is present
1297
		$teamWithoutSponsor = $this->objFromFixture('DataObjectTest_Team', 'team3');
1298
		$this->assertInstanceOf('SilverStripe\\ORM\\ManyManyList', $teamWithoutSponsor->Sponsors());
1299
		$this->assertEquals(0, $teamWithoutSponsor->Sponsors()->count());
1300
1301
		// Test that belongs_many_many can be infered from with getNonReciprocalComponent
1302
		$this->assertDOSEquals(
1303
			[
1304
				['Name' => 'Company corp'],
1305
				['Name' => 'Team co.'],
1306
			],
1307
			$team->inferReciprocalComponent('DataObjectTest_EquipmentCompany', 'SponsoredTeams')
1308
		);
1309
1310
		// Test that many_many can be infered from getNonReciprocalComponent
1311
		$this->assertDOSEquals(
1312
			[
1313
				['Title' => 'Team 1'],
1314
				['Title' => 'Team 2'],
1315
				['Title' => 'Subteam 1'],
1316
			],
1317
			$company2->inferReciprocalComponent('DataObjectTest_Team', 'Sponsors')
1318
		);
1319
1320
		// Check many_many_extraFields still works
1321
		$equipmentCompany = $this->objFromFixture('DataObjectTest_EquipmentCompany', 'equipmentcompany1');
1322
		$equipmentCompany->SponsoredTeams()->add($teamWithoutSponsor, array('SponsorFee' => 1000));
1323
		$sponsoredTeams = $equipmentCompany->SponsoredTeams();
1324
		$this->assertEquals(1000, $sponsoredTeams->byID($teamWithoutSponsor->ID)->SponsorFee,
1325
			'Data from many_many_extraFields was not stored/extracted correctly');
1326
1327
		// Check subclasses correctly inherit multiple many_manys
1328
		$subTeam = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam1');
1329
		$this->assertEquals(2, $subTeam->Sponsors()->count(),
1330
			'Child class did not inherit multiple many_manys');
1331
		$this->assertEquals(1, $subTeam->EquipmentSuppliers()->count(),
1332
			'Child class did not inherit multiple many_manys');
1333
		// Team 2 has one EquipmentCompany sponsor and one SubEquipmentCompany
1334
		$team2 = $this->objFromFixture('DataObjectTest_Team', 'team2');
1335
		$this->assertEquals(2, $team2->Sponsors()->count(),
1336
			'Child class did not inherit multiple belongs_many_manys');
1337
1338
		// Check many_many_extraFields also works from the belongs_many_many side
1339
		$sponsors = $team2->Sponsors();
1340
		$sponsors->add($equipmentCompany, array('SponsorFee' => 750));
1341
		$this->assertEquals(750, $sponsors->byID($equipmentCompany->ID)->SponsorFee,
1342
			'Data from many_many_extraFields was not stored/extracted correctly');
1343
1344
		$subEquipmentCompany = $this->objFromFixture('DataObjectTest_SubEquipmentCompany', 'subequipmentcompany1');
1345
		$subTeam->Sponsors()->add($subEquipmentCompany, array('SponsorFee' => 1200));
1346
		$this->assertEquals(1200, $subTeam->Sponsors()->byID($subEquipmentCompany->ID)->SponsorFee,
1347
			'Data from inherited many_many_extraFields was not stored/extracted correctly');
1348
1349
	}
1350
1351
	public function testManyManyExtraFields() {
1352
		$player = $this->objFromFixture('DataObjectTest_Player', 'player1');
1353
		$team = $this->objFromFixture('DataObjectTest_Team', 'team1');
1354
1355
		// Get all extra fields
1356
		$teamExtraFields = $team->manyManyExtraFields();
1357
		$this->assertEquals(array(
1358
			'Players' => array('Position' => 'Varchar(100)')
1359
		), $teamExtraFields);
1360
1361
		// Ensure fields from parent classes are included
1362
		$subTeam = singleton('DataObjectTest_SubTeam');
1363
		$teamExtraFields = $subTeam->manyManyExtraFields();
1364
		$this->assertEquals(array(
1365
			'Players' => array('Position' => 'Varchar(100)'),
1366
			'FormerPlayers' => array('Position' => 'Varchar(100)')
1367
		), $teamExtraFields);
1368
1369
		// Extra fields are immediately available on the Team class (defined in $many_many_extraFields)
1370
		$teamExtraFields = $team->manyManyExtraFieldsForComponent('Players');
1371
		$this->assertEquals($teamExtraFields, array(
1372
			'Position' => 'Varchar(100)'
1373
		));
1374
1375
		// We'll have to go through the relation to get the extra fields on Player
1376
		$playerExtraFields = $player->manyManyExtraFieldsForComponent('Teams');
1377
		$this->assertEquals($playerExtraFields, array(
1378
			'Position' => 'Varchar(100)'
1379
		));
1380
1381
		// Iterate through a many-many relationship and confirm that extra fields are included
1382
		$newTeam = new DataObjectTest_Team();
1383
		$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...
1384
		$newTeam->write();
1385
		$newTeamID = $newTeam->ID;
1386
1387
		$newPlayer = new DataObjectTest_Player();
1388
		$newPlayer->FirstName = "Sam";
1389
		$newPlayer->Surname = "Minnee";
1390
		$newPlayer->write();
1391
1392
		// The idea of Sam as a prop is essentially humourous.
1393
		$newTeam->Players()->add($newPlayer, array("Position" => "Prop"));
1394
1395
		// Requery and uncache everything
1396
		$newTeam->flushCache();
1397
		$newTeam = DataObject::get_by_id('DataObjectTest_Team', $newTeamID);
1398
1399
		// Check that the Position many_many_extraField is extracted.
1400
		$player = $newTeam->Players()->First();
1401
		$this->assertEquals('Sam', $player->FirstName);
1402
		$this->assertEquals("Prop", $player->Position);
1403
1404
		// Check that ordering a many-many relation by an aggregate column doesn't fail
1405
		$player = $this->objFromFixture('DataObjectTest_Player', 'player2');
1406
		$player->Teams()->sort("count(DISTINCT \"DataObjectTest_Team_Players\".\"DataObjectTest_PlayerID\") DESC");
1407
	}
1408
1409
	/**
1410
	 * Check that the queries generated for many-many relation queries can have unlimitedRowCount
1411
	 * called on them.
1412
	 */
1413
	public function testManyManyUnlimitedRowCount() {
1414
		$player = $this->objFromFixture('DataObjectTest_Player', 'player2');
1415
		// TODO: What's going on here?
1416
		$this->assertEquals(2, $player->Teams()->dataQuery()->query()->unlimitedRowCount());
1417
	}
1418
1419
	/**
1420
	 * Tests that singular_name() generates sensible defaults.
1421
	 */
1422
	public function testSingularName() {
1423
		$assertions = array(
1424
			'DataObjectTest_Player'       => 'Data Object Test Player',
1425
			'DataObjectTest_Team'         => 'Data Object Test Team',
1426
			'DataObjectTest_Fixture'      => 'Data Object Test Fixture',
1427
			'DataObjectTest\NamespacedClass' => 'Namespaced Class',
1428
		);
1429
1430
		foreach($assertions as $class => $expectedSingularName) {
1431
			$this->assertEquals(
1432
				$expectedSingularName,
1433
				singleton($class)->singular_name(),
1434
				"Assert that the singular_name for '$class' is correct."
1435
			);
1436
		}
1437
	}
1438
1439
	/**
1440
	 * Tests that plural_name() generates sensible defaults.
1441
	 */
1442
	public function testPluralName() {
1443
		$assertions = array(
1444
			'DataObjectTest_Player'       => 'Data Object Test Players',
1445
			'DataObjectTest_Team'         => 'Data Object Test Teams',
1446
			'DataObjectTest_Fixture'      => 'Data Object Test Fixtures',
1447
			'DataObjectTest_Play'         => 'Data Object Test Plays',
1448
			'DataObjectTest_Bogey'        => 'Data Object Test Bogeys',
1449
			'DataObjectTest_Ploy'         => 'Data Object Test Ploys',
1450
		);
1451
1452
		foreach($assertions as $class => $expectedPluralName) {
1453
			$this->assertEquals(
1454
				$expectedPluralName,
1455
				singleton($class)->plural_name(),
1456
				"Assert that the plural_name for '$class' is correct."
1457
			);
1458
		}
1459
	}
1460
1461
	public function testHasDatabaseField() {
1462
		$team = singleton('DataObjectTest_Team');
1463
		$subteam = singleton('DataObjectTest_SubTeam');
1464
1465
		$this->assertTrue(
1466
			$team->hasDatabaseField('Title'),
1467
			"hasOwnDatabaseField() works with \$db fields"
1468
		);
1469
		$this->assertTrue(
1470
			$team->hasDatabaseField('CaptainID'),
1471
			"hasOwnDatabaseField() works with \$has_one fields"
1472
		);
1473
		$this->assertFalse(
1474
			$team->hasDatabaseField('NonExistentField'),
1475
			"hasOwnDatabaseField() doesn't detect non-existend fields"
1476
		);
1477
		$this->assertTrue(
1478
			$team->hasDatabaseField('ExtendedDatabaseField'),
1479
			"hasOwnDatabaseField() works with extended fields"
1480
		);
1481
		$this->assertFalse(
1482
			$team->hasDatabaseField('SubclassDatabaseField'),
1483
			"hasOwnDatabaseField() doesn't pick up fields in subclasses on parent class"
1484
		);
1485
1486
		$this->assertTrue(
1487
			$subteam->hasDatabaseField('SubclassDatabaseField'),
1488
			"hasOwnDatabaseField() picks up fields in subclasses"
1489
		);
1490
1491
	}
1492
1493
	public function testFieldTypes() {
1494
		$obj = new DataObjectTest_Fixture();
1495
		$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...
1496
		$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...
1497
		$obj->write();
1498
		$obj->flushCache();
1499
1500
		$obj = DataObject::get_by_id('DataObjectTest_Fixture', $obj->ID);
1501
		$this->assertEquals('1988-01-02', $obj->DateField);
1502
		$this->assertEquals('1988-03-04 06:30:00', $obj->DatetimeField);
1503
	}
1504
1505
	public function testTwoSubclassesWithTheSameFieldNameWork() {
1506
		// Create two objects of different subclasses, setting the values of fields that are
1507
		// defined separately in each subclass
1508
		$obj1 = new DataObjectTest_SubTeam();
1509
		$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...
1510
		$obj2 = new OtherSubclassWithSameField();
1511
		$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...
1512
1513
		// Write them to the database
1514
		$obj1->write();
1515
		$obj2->write();
1516
1517
		// Check that the values of those fields are properly read from the database
1518
		$values = DataObject::get("DataObjectTest_Team", "\"DataObjectTest_Team\".\"ID\" IN
1519
			($obj1->ID, $obj2->ID)")->column("SubclassDatabaseField");
1520
		$this->assertEquals(array_intersect($values, array('obj1', 'obj2')), $values);
1521
	}
1522
1523
	public function testClassNameSetForNewObjects() {
1524
		$d = new DataObjectTest_Player();
1525
		$this->assertEquals('DataObjectTest_Player', $d->ClassName);
1526
	}
1527
1528
	public function testHasValue() {
1529
		$team = new DataObjectTest_Team();
1530
		$this->assertFalse($team->hasValue('Title', null, false));
1531
		$this->assertFalse($team->hasValue('DatabaseField', null, false));
1532
1533
		$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...
1534
		$this->assertTrue($team->hasValue('Title', null, false));
1535
		$this->assertFalse($team->hasValue('DatabaseField', null, false));
1536
1537
		$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...
1538
		$this->assertTrue (
1539
			$team->hasValue('Title', null, false),
1540
			'Test that an empty paragraph is a value for non-HTML fields.'
1541
		);
1542
1543
		$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...
1544
		$this->assertTrue($team->hasValue('Title', null, false));
1545
		$this->assertTrue($team->hasValue('DatabaseField', null, false));
1546
	}
1547
1548
	public function testHasMany() {
1549
		$company = new DataObjectTest_Company();
1550
1551
		$this->assertEquals (
1552
			array (
1553
				'CurrentStaff'     => 'DataObjectTest_Staff',
1554
				'PreviousStaff'    => 'DataObjectTest_Staff'
1555
			),
1556
			$company->hasMany(),
1557
			'has_many strips field name data by default.'
1558
		);
1559
1560
		$this->assertEquals (
1561
			'DataObjectTest_Staff',
1562
			$company->hasManyComponent('CurrentStaff'),
1563
			'has_many strips field name data by default on single relationships.'
1564
		);
1565
1566
		$this->assertEquals (
1567
			array (
1568
				'CurrentStaff'     => 'DataObjectTest_Staff.CurrentCompany',
1569
				'PreviousStaff'    => 'DataObjectTest_Staff.PreviousCompany'
1570
			),
1571
			$company->hasMany(null, false),
1572
			'has_many returns field name data when $classOnly is false.'
1573
		);
1574
1575
		$this->assertEquals (
1576
			'DataObjectTest_Staff.CurrentCompany',
1577
			$company->hasManyComponent('CurrentStaff', false),
1578
			'has_many returns field name data on single records when $classOnly is false.'
1579
		);
1580
	}
1581
1582
	public function testGetRemoteJoinField() {
1583
		$company = new DataObjectTest_Company();
1584
1585
		$staffJoinField = $company->getRemoteJoinField('CurrentStaff', 'has_many', $polymorphic);
1586
		$this->assertEquals('CurrentCompanyID', $staffJoinField);
1587
		$this->assertFalse($polymorphic, 'DataObjectTest_Company->CurrentStaff is not polymorphic');
1588
		$previousStaffJoinField = $company->getRemoteJoinField('PreviousStaff', 'has_many', $polymorphic);
1589
		$this->assertEquals('PreviousCompanyID', $previousStaffJoinField);
1590
		$this->assertFalse($polymorphic, 'DataObjectTest_Company->PreviousStaff is not polymorphic');
1591
1592
		$ceo = new DataObjectTest_CEO();
1593
1594
		$this->assertEquals('CEOID', $ceo->getRemoteJoinField('Company', 'belongs_to', $polymorphic));
1595
		$this->assertFalse($polymorphic, 'DataObjectTest_CEO->Company is not polymorphic');
1596
		$this->assertEquals('PreviousCEOID', $ceo->getRemoteJoinField('PreviousCompany', 'belongs_to', $polymorphic));
1597
		$this->assertFalse($polymorphic, 'DataObjectTest_CEO->PreviousCompany is not polymorphic');
1598
1599
		$team = new DataObjectTest_Team();
1600
1601
		$this->assertEquals('Favourite', $team->getRemoteJoinField('Fans', 'has_many', $polymorphic));
1602
		$this->assertTrue($polymorphic, 'DataObjectTest_Team->Fans is polymorphic');
1603
		$this->assertEquals('TeamID', $team->getRemoteJoinField('Comments', 'has_many', $polymorphic));
1604
		$this->assertFalse($polymorphic, 'DataObjectTest_Team->Comments is not polymorphic');
1605
	}
1606
1607
	public function testBelongsTo() {
1608
		$company = new DataObjectTest_Company();
1609
		$ceo     = new DataObjectTest_CEO();
1610
1611
		$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...
1612
		$company->write();
1613
		$ceo->write();
1614
1615
		// Test belongs_to assignment
1616
		$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...
1617
		$company->write();
1618
1619
		$this->assertEquals($company->ID, $ceo->Company()->ID, 'belongs_to returns the right results.');
1620
1621
		// Test belongs_to can be infered via getNonReciprocalComponent
1622
		// Note: Will be returned as has_many since the belongs_to is ignored.
1623
		$this->assertDOSEquals(
1624
			[['Name' => 'New Company']],
1625
			$ceo->inferReciprocalComponent('DataObjectTest_Company', 'CEO')
1626
		);
1627
1628
		// Test has_one to a belongs_to can be infered via getNonReciprocalComponent
1629
		$this->assertEquals(
1630
			$ceo->ID,
1631
			$company->inferReciprocalComponent('DataObjectTest_CEO', 'Company')->ID
1632
		);
1633
1634
		// Test automatic creation of class where no assigment exists
1635
		$ceo = new DataObjectTest_CEO();
1636
		$ceo->write();
1637
1638
		$this->assertTrue (
1639
			$ceo->Company() instanceof DataObjectTest_Company,
1640
			'DataObjects across belongs_to relations are automatically created.'
1641
		);
1642
		$this->assertEquals($ceo->ID, $ceo->Company()->CEOID, 'Remote IDs are automatically set.');
1643
1644
		// Write object with components
1645
		$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...
1646
		$ceo->write(false, false, false, true);
1647
		$this->assertTrue($ceo->Company()->isInDB(), 'write() writes belongs_to components to the database.');
1648
1649
		$newCEO = DataObject::get_by_id('DataObjectTest_CEO', $ceo->ID);
1650
		$this->assertEquals (
1651
			$ceo->Company()->ID, $newCEO->Company()->ID, 'belongs_to can be retrieved from the database.'
1652
		);
1653
	}
1654
1655
	public function testBelongsToPolymorphic() {
1656
		$company = new DataObjectTest_Company();
1657
		$ceo     = new DataObjectTest_CEO();
1658
1659
		$company->write();
1660
		$ceo->write();
1661
1662
		// Test belongs_to assignment
1663
		$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...
1664
		$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...
1665
		$company->write();
1666
1667
		$this->assertEquals($company->ID, $ceo->CompanyOwned()->ID, 'belongs_to returns the right results.');
1668
		$this->assertEquals($company->class, $ceo->CompanyOwned()->class, 'belongs_to returns the right results.');
1669
1670
		// Test automatic creation of class where no assigment exists
1671
		$ceo = new DataObjectTest_CEO();
1672
		$ceo->write();
1673
1674
		$this->assertTrue (
1675
			$ceo->CompanyOwned() instanceof DataObjectTest_Company,
1676
			'DataObjects across polymorphic belongs_to relations are automatically created.'
1677
		);
1678
		$this->assertEquals($ceo->ID, $ceo->CompanyOwned()->OwnerID, 'Remote IDs are automatically set.');
1679
		$this->assertInstanceOf($ceo->CompanyOwned()->OwnerClass, $ceo, 'Remote class is automatically  set');
1680
1681
		// Write object with components
1682
		$ceo->write(false, false, false, true);
1683
		$this->assertTrue($ceo->CompanyOwned()->isInDB(), 'write() writes belongs_to components to the database.');
1684
1685
		$newCEO = DataObject::get_by_id('DataObjectTest_CEO', $ceo->ID);
1686
		$this->assertEquals (
1687
			$ceo->CompanyOwned()->ID,
1688
			$newCEO->CompanyOwned()->ID,
1689
			'polymorphic belongs_to can be retrieved from the database.'
1690
		);
1691
	}
1692
1693
	/**
1694
	 * @expectedException LogicException
1695
	 */
1696
	public function testInvalidate() {
1697
		$do = new DataObjectTest_Fixture();
1698
		$do->write();
1699
1700
		$do->delete();
1701
1702
		$do->delete(); // Prohibit invalid object manipulation
1703
		$do->write();
1704
		$do->duplicate();
1705
	}
1706
1707
	public function testToMap() {
1708
		$obj = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam1');
1709
1710
		$map = $obj->toMap();
1711
1712
		$this->assertArrayHasKey('ID', $map, 'Contains base fields');
1713
		$this->assertArrayHasKey('Title', $map, 'Contains fields from parent class');
1714
		$this->assertArrayHasKey('SubclassDatabaseField', $map, 'Contains fields from concrete class');
1715
1716
		$this->assertEquals($obj->ID, $map['ID'],
1717
			'Contains values from base fields');
1718
		$this->assertEquals($obj->Title, $map['Title'],
1719
			'Contains values from parent class fields');
1720
		$this->assertEquals($obj->SubclassDatabaseField, $map['SubclassDatabaseField'],
1721
			'Contains values from concrete class fields');
1722
1723
		$newObj = new DataObjectTest_SubTeam();
1724
		$this->assertArrayHasKey('Title', $map, 'Contains null fields');
1725
	}
1726
1727
	public function testIsEmpty() {
1728
		$objEmpty = new DataObjectTest_Team();
1729
		$this->assertTrue($objEmpty->isEmpty(), 'New instance without populated defaults is empty');
1730
1731
		$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...
1732
		$this->assertFalse($objEmpty->isEmpty(), 'Zero value in attribute considered non-empty');
1733
	}
1734
1735
	public function testRelField() {
1736
		$captain = $this->objFromFixture('DataObjectTest_Player', 'captain1');
1737
		// Test traversal of a single has_one
1738
		$this->assertEquals("Team 1", $captain->relField('FavouriteTeam.Title'));
1739
		// Test direct field access
1740
		$this->assertEquals("Captain", $captain->relField('FirstName'));
1741
1742
		$player = $this->objFromFixture('DataObjectTest_Player', 'player2');
1743
		// Test that we can traverse more than once, and that arbitrary methods are okay
1744
		$this->assertEquals("Team 1", $player->relField('Teams.First.Title'));
1745
1746
		$newPlayer = new DataObjectTest_Player();
1747
		$this->assertNull($newPlayer->relField('Teams.First.Title'));
1748
1749
		// Test that relField works on db field manipulations
1750
		$comment = $this->objFromFixture('DataObjectTest_TeamComment', 'comment3');
1751
		$this->assertEquals("PHIL IS A UNIQUE GUY, AND COMMENTS ON TEAM2" , $comment->relField('Comment.UpperCase'));
1752
	}
1753
1754
	public function testRelObject() {
1755
		$captain = $this->objFromFixture('DataObjectTest_Player', 'captain1');
1756
1757
		// Test traversal of a single has_one
1758
		$this->assertInstanceOf('SilverStripe\\ORM\\FieldType\\DBVarchar', $captain->relObject('FavouriteTeam.Title'));
1759
		$this->assertEquals("Team 1", $captain->relObject('FavouriteTeam.Title')->getValue());
1760
1761
		// Test direct field access
1762
		$this->assertInstanceOf('SilverStripe\\ORM\\FieldType\\DBBoolean', $captain->relObject('IsRetired'));
1763
		$this->assertEquals(1, $captain->relObject('IsRetired')->getValue());
1764
1765
		$player = $this->objFromFixture('DataObjectTest_Player', 'player2');
1766
		// Test that we can traverse more than once, and that arbitrary methods are okay
1767
		$this->assertInstanceOf('SilverStripe\\ORM\\FieldType\\DBVarchar', $player->relObject('Teams.First.Title'));
1768
		$this->assertEquals("Team 1", $player->relObject('Teams.First.Title')->getValue());
1769
	}
1770
1771
	public function testLateStaticBindingStyle() {
1772
		// Confirm that DataObjectTest_Player::get() operates as excepted
1773
		$this->assertEquals(4, DataObjectTest_Player::get()->Count());
1774
		$this->assertInstanceOf('DataObjectTest_Player', DataObjectTest_Player::get()->First());
1775
1776
		// You can't pass arguments to LSB syntax - use the DataList methods instead.
1777
		$this->setExpectedException('InvalidArgumentException');
1778
		DataObjectTest_Player::get(null, "\"ID\" = 1");
1779
1780
	}
1781
1782
	public function testBrokenLateStaticBindingStyle() {
1783
		// If you call DataObject::get() you have to pass a first argument
1784
		$this->setExpectedException('InvalidArgumentException');
1785
		DataObject::get();
1786
1787
	}
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 $has_one = array (
2012
		'CurrentCompany'  => 'DataObjectTest_Company',
2013
		'PreviousCompany' => 'DataObjectTest_Company'
2014
	);
2015
}
2016
2017
class DataObjectTest_CEO extends DataObjectTest_Staff {
2018
	private static $belongs_to = array (
2019
		'Company'         => 'DataObjectTest_Company.CEO',
2020
		'PreviousCompany' => 'DataObjectTest_Company.PreviousCEO',
2021
		'CompanyOwned'    => 'DataObjectTest_Company.Owner'
2022
	);
2023
}
2024
2025
class DataObjectTest_TeamComment extends DataObject implements TestOnly {
2026
	private static $db = array(
2027
		'Name' => 'Varchar',
2028
		'Comment' => 'Text'
2029
	);
2030
2031
	private static $has_one = array(
2032
		'Team' => 'DataObjectTest_Team'
2033
	);
2034
2035
	private static $default_sort = '"Name" ASC';
2036
}
2037
2038
class DataObjectTest_Fan extends DataObject implements TestOnly {
2039
2040
	private static $db = array(
2041
		'Name' => 'Varchar(255)',
2042
		'Email' => 'Varchar',
2043
	);
2044
2045
	private static $has_one = array(
2046
		'Favourite' => 'SilverStripe\\ORM\\DataObject', // Polymorphic relation
2047
		'SecondFavourite' => 'SilverStripe\\ORM\\DataObject'
2048
	);
2049
}
2050
2051
class DataObjectTest_ExtendedTeamComment extends DataObjectTest_TeamComment {
2052
	private static $db = array(
2053
		'Comment' => 'HTMLText'
2054
	);
2055
}
2056
2057
class DataObjectTest_Play extends DataObject implements TestOnly {}
2058
class DataObjectTest_Ploy extends DataObject implements TestOnly {}
2059
class DataObjectTest_Bogey extends DataObject implements TestOnly {}
2060
2061
DataObjectTest_Team::add_extension('DataObjectTest_Team_Extension');
2062
2063