Completed
Push — master ( a6ff96...8afff1 )
by Daniel
10:22
created

VersionedOwnershipTest::testFindOwnersLive()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 57
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 33
nc 1
nop 0
dl 0
loc 57
rs 9.6818
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
use SilverStripe\Dev\Debug;
4
use SilverStripe\ORM\DataList;
5
use SilverStripe\ORM\Versioning\ChangeSet;
6
use SilverStripe\ORM\Versioning\ChangeSetItem;
7
use SilverStripe\ORM\Versioning\Versioned;
8
use SilverStripe\ORM\DataObject;
9
use SilverStripe\ORM\FieldType\DBDatetime;
10
use SilverStripe\Dev\SapphireTest;
11
use SilverStripe\Dev\TestOnly;
12
13
/**
14
 * Tests ownership API of versioned DataObjects
15
 */
16
class VersionedOwnershipTest extends SapphireTest {
17
18
	protected $extraDataObjects = array(
19
		'VersionedOwnershipTest_Object',
20
		'VersionedOwnershipTest_Subclass',
21
		'VersionedOwnershipTest_Related',
22
		'VersionedOwnershipTest_Attachment',
23
		'VersionedOwnershipTest_RelatedMany',
24
		'VersionedOwnershipTest_Page',
25
		'VersionedOwnershipTest_Banner',
26
		'VersionedOwnershipTest_Image',
27
		'VersionedOwnershipTest_CustomRelation',
28
	);
29
30
	protected static $fixture_file = 'VersionedOwnershipTest.yml';
31
32
	public function setUp() {
33
		parent::setUp();
34
35
		Versioned::set_stage(Versioned::DRAFT);
36
37
		// Automatically publish any object named *_published
38
		foreach($this->getFixtureFactory()->getFixtures() as $class => $fixtures) {
39
			foreach($fixtures as $name => $id) {
40
				if(stripos($name, '_published') !== false) {
41
					/** @var Versioned|DataObject $object */
42
					$object = DataObject::get($class)->byID($id);
43
					$object->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
0 ignored issues
show
Bug introduced by
The method copyVersionToStage does only exist in SilverStripe\ORM\Versioning\Versioned, but not in SilverStripe\ORM\DataObject.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
44
				}
45
			}
46
		}
47
	}
48
49
	/**
50
	 * Virtual "sleep" that doesn't actually slow execution, only advances DBDateTime::now()
51
	 *
52
	 * @param int $minutes
53
	 */
54
	protected function sleep($minutes) {
55
		$now = DBDatetime::now();
56
		$date = DateTime::createFromFormat('Y-m-d H:i:s', $now->getValue());
57
		$date->modify("+{$minutes} minutes");
58
		DBDatetime::set_mock_now($date->format('Y-m-d H:i:s'));
59
	}
60
61
	/**
62
	 * Test basic findOwned() in stage mode
63
	 */
64
	public function testFindOwned() {
65
		/** @var VersionedOwnershipTest_Subclass $subclass1 */
66
		$subclass1 = $this->objFromFixture('VersionedOwnershipTest_Subclass', 'subclass1_published');
67
		$this->assertDOSEquals(
68
			[
69
				['Title' => 'Related 1'],
70
				['Title' => 'Attachment 1'],
71
				['Title' => 'Attachment 2'],
72
				['Title' => 'Attachment 5'],
73
				['Title' => 'Related Many 1'],
74
				['Title' => 'Related Many 2'],
75
				['Title' => 'Related Many 3'],
76
			],
77
			$subclass1->findOwned()
78
		);
79
80
		// Non-recursive search
81
		$this->assertDOSEquals(
82
			[
83
				['Title' => 'Related 1'],
84
				['Title' => 'Related Many 1'],
85
				['Title' => 'Related Many 2'],
86
				['Title' => 'Related Many 3'],
87
			],
88
			$subclass1->findOwned(false)
89
		);
90
91
		/** @var VersionedOwnershipTest_Subclass $subclass2 */
92
		$subclass2 = $this->objFromFixture('VersionedOwnershipTest_Subclass', 'subclass2_published');
93
		$this->assertDOSEquals(
94
			[
95
				['Title' => 'Related 2'],
96
				['Title' => 'Attachment 3'],
97
				['Title' => 'Attachment 4'],
98
				['Title' => 'Attachment 5'],
99
				['Title' => 'Related Many 4'],
100
			],
101
			$subclass2->findOwned()
102
		);
103
104
		// Non-recursive search
105
		$this->assertDOSEquals(
106
			[
107
				['Title' => 'Related 2'],
108
				['Title' => 'Related Many 4'],
109
			],
110
			$subclass2->findOwned(false)
111
		);
112
113
		/** @var VersionedOwnershipTest_Related $related1 */
114
		$related1 = $this->objFromFixture('VersionedOwnershipTest_Related', 'related1');
115
		$this->assertDOSEquals(
116
			[
117
				['Title' => 'Attachment 1'],
118
				['Title' => 'Attachment 2'],
119
				['Title' => 'Attachment 5'],
120
			],
121
			$related1->findOwned()
122
		);
123
124
		/** @var VersionedOwnershipTest_Related $related2 */
125
		$related2 = $this->objFromFixture('VersionedOwnershipTest_Related', 'related2_published');
126
		$this->assertDOSEquals(
127
			[
128
				['Title' => 'Attachment 3'],
129
				['Title' => 'Attachment 4'],
130
				['Title' => 'Attachment 5'],
131
			],
132
			$related2->findOwned()
133
		);
134
	}
135
136
	/**
137
	 * Test findOwners
138
	 */
139
	public function testFindOwners() {
140
		/** @var VersionedOwnershipTest_Attachment $attachment1 */
141
		$attachment1 = $this->objFromFixture('VersionedOwnershipTest_Attachment', 'attachment1');
142
		$this->assertDOSEquals(
143
			[
144
				['Title' => 'Related 1'],
145
				['Title' => 'Subclass 1'],
146
			],
147
			$attachment1->findOwners()
148
		);
149
150
		// Non-recursive search
151
		$this->assertDOSEquals(
152
			[
153
				['Title' => 'Related 1'],
154
			],
155
			$attachment1->findOwners(false)
156
		);
157
158
		/** @var VersionedOwnershipTest_Attachment $attachment5 */
159
		$attachment5 = $this->objFromFixture('VersionedOwnershipTest_Attachment', 'attachment5_published');
160
		$this->assertDOSEquals(
161
			[
162
				['Title' => 'Related 1'],
163
				['Title' => 'Related 2'],
164
				['Title' => 'Subclass 1'],
165
				['Title' => 'Subclass 2'],
166
			],
167
			$attachment5->findOwners()
168
		);
169
170
		// Non-recursive
171
		$this->assertDOSEquals(
172
			[
173
				['Title' => 'Related 1'],
174
				['Title' => 'Related 2'],
175
			],
176
			$attachment5->findOwners(false)
177
		);
178
179
		/** @var VersionedOwnershipTest_Related $related1 */
180
		$related1 = $this->objFromFixture('VersionedOwnershipTest_Related', 'related1');
181
		$this->assertDOSEquals(
182
			[
183
				['Title' => 'Subclass 1'],
184
			],
185
			$related1->findOwners()
186
		);
187
	}
188
189
	/**
190
	 * Test findOwners on Live stage
191
	 */
192
	public function testFindOwnersLive() {
193
		// Modify a few records on stage
194
		$related2 = $this->objFromFixture('VersionedOwnershipTest_Related', 'related2_published');
195
		$related2->Title .= ' Modified';
0 ignored issues
show
Documentation introduced by
The property Title 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...
196
		$related2->write();
197
		$attachment3 = $this->objFromFixture('VersionedOwnershipTest_Attachment', 'attachment3_published');
198
		$attachment3->Title .= ' Modified';
0 ignored issues
show
Documentation introduced by
The property Title 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...
199
		$attachment3->write();
200
		$attachment4 = $this->objFromFixture('VersionedOwnershipTest_Attachment', 'attachment4_published');
201
		$attachment4->delete();
202
		$subclass2ID = $this->idFromFixture('VersionedOwnershipTest_Subclass', 'subclass2_published');
203
204
		// Check that stage record is ok
205
		/** @var VersionedOwnershipTest_Subclass $subclass2Stage */
206
		$subclass2Stage = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', 'Stage')->byID($subclass2ID);
207
		$this->assertDOSEquals(
208
			[
209
				['Title' => 'Related 2 Modified'],
210
				['Title' => 'Attachment 3 Modified'],
211
				['Title' => 'Attachment 5'],
212
				['Title' => 'Related Many 4'],
213
			],
214
			$subclass2Stage->findOwned()
215
		);
216
217
		// Non-recursive
218
		$this->assertDOSEquals(
219
			[
220
				['Title' => 'Related 2 Modified'],
221
				['Title' => 'Related Many 4'],
222
			],
223
			$subclass2Stage->findOwned(false)
224
		);
225
226
		// Live records are unchanged
227
		/** @var VersionedOwnershipTest_Subclass $subclass2Live */
228
		$subclass2Live = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', 'Live')->byID($subclass2ID);
229
		$this->assertDOSEquals(
230
			[
231
				['Title' => 'Related 2'],
232
				['Title' => 'Attachment 3'],
233
				['Title' => 'Attachment 4'],
234
				['Title' => 'Attachment 5'],
235
				['Title' => 'Related Many 4'],
236
			],
237
			$subclass2Live->findOwned()
238
		);
239
240
		// Test non-recursive
241
		$this->assertDOSEquals(
242
			[
243
				['Title' => 'Related 2'],
244
				['Title' => 'Related Many 4'],
245
			],
246
			$subclass2Live->findOwned(false)
247
		);
248
	}
249
250
	/**
251
	 * Test that objects are correctly published recursively
252
	 */
253
	public function testRecursivePublish() {
254
		/** @var VersionedOwnershipTest_Subclass $parent */
255
		$parent = $this->objFromFixture('VersionedOwnershipTest_Subclass', 'subclass1_published');
256
		$parentID = $parent->ID;
257
		$banner1 = $this->objFromFixture('VersionedOwnershipTest_RelatedMany', 'relatedmany1_published');
258
		$banner2 = $this->objFromFixture('VersionedOwnershipTest_RelatedMany', 'relatedmany2_published');
259
		$banner2ID = $banner2->ID;
260
261
		// Modify, Add, and Delete banners on stage
262
		$banner1->Title = 'Renamed Banner 1';
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
263
		$banner1->write();
264
265
		$banner2->delete();
266
267
		$banner4 = new VersionedOwnershipTest_RelatedMany();
268
		$banner4->Title = 'New Banner';
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<VersionedOwnershipTest_RelatedMany>. 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...
269
		$parent->Banners()->add($banner4);
270
271
		// Check state of objects before publish
272
		$oldLiveBanners = [
273
			['Title' => 'Related Many 1'],
274
			['Title' => 'Related Many 2'], // Will be unlinked (but not deleted)
275
			// `Related Many 3` isn't published
276
		];
277
		$newBanners = [
278
			['Title' => 'Renamed Banner 1'], // Renamed
279
			['Title' => 'Related Many 3'], // Published without changes
280
			['Title' => 'New Banner'], // Created
281
		];
282
		$parentDraft = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::DRAFT)
283
			->byID($parentID);
284
		$this->assertDOSEquals($newBanners, $parentDraft->Banners());
285
		$parentLive = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::LIVE)
286
			->byID($parentID);
287
		$this->assertDOSEquals($oldLiveBanners, $parentLive->Banners());
288
289
		// On publishing of owner, all children should now be updated
290
		$now = DBDatetime::now();
291
		DBDatetime::set_mock_now($now); // Lock 'now' to predictable time
292
		$parent->publishRecursive();
293
294
		// Now check each object has the correct state
295
		$parentDraft = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::DRAFT)
296
			->byID($parentID);
297
		$this->assertDOSEquals($newBanners, $parentDraft->Banners());
298
		$parentLive = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::LIVE)
299
			->byID($parentID);
300
		$this->assertDOSEquals($newBanners, $parentLive->Banners());
301
302
		// Check that the deleted banner hasn't actually been deleted from the live stage,
303
		// but in fact has been unlinked.
304
		$banner2Live = Versioned::get_by_stage('VersionedOwnershipTest_RelatedMany', Versioned::LIVE)
305
			->byID($banner2ID);
306
		$this->assertEmpty($banner2Live->PageID);
307
308
		// Test that a changeset was created
309
		/** @var ChangeSet $changeset */
310
		$changeset = ChangeSet::get()->sort('"ChangeSet"."ID" DESC')->first();
311
		$this->assertNotEmpty($changeset);
312
313
		// Test that this changeset is inferred
314
		$this->assertTrue((bool)$changeset->IsInferred);
315
		$this->assertEquals(
316
			"Generated by publish of 'Subclass 1' at ".$now->Nice(),
317
			$changeset->getTitle()
318
		);
319
320
		// Test that this changeset contains all items
321
		$this->assertDOSContains(
322
			[
323
				[
324
					'ObjectID' => $parent->ID,
325
					'ObjectClass' => $parent->baseClass(),
326
					'Added' => ChangeSetItem::EXPLICITLY
327
				],
328
				[
329
					'ObjectID' => $banner1->ID,
330
					'ObjectClass' => $banner1->baseClass(),
331
					'Added' => ChangeSetItem::IMPLICITLY
332
				],
333
				[
334
					'ObjectID' => $banner4->ID,
335
					'ObjectClass' => $banner4->baseClass(),
336
					'Added' => ChangeSetItem::IMPLICITLY
337
				]
338
			],
339
			$changeset->Changes()
340
		);
341
342
		// Objects that are unlinked should not need to be a part of the changeset
343
		$this->assertNotDOSContains(
344
			[[ 'ObjectID' => $banner2ID, 'ObjectClass' => $banner2->baseClass() ]],
345
			$changeset->Changes()
346
		);
347
	}
348
349
	/**
350
	 * Test that owning objects get unpublished as needed
351
	 */
352
	public function testRecursiveUnpublish() {
353
		// Unsaved objects can't be unpublished
354
		$unsaved = new VersionedOwnershipTest_Subclass();
355
		$this->assertFalse($unsaved->doUnpublish());
356
357
		// Draft-only objects can't be unpublished
358
		/** @var VersionedOwnershipTest_RelatedMany $banner3Unpublished */
359
		$banner3Unpublished = $this->objFromFixture('VersionedOwnershipTest_RelatedMany', 'relatedmany3');
360
		$this->assertFalse($banner3Unpublished->doUnpublish());
361
362
		// First test: mid-level unpublish; We expect that owners should be unpublished, but not
363
		// owned objects, nor other siblings shared by the same owner.
364
		$related2 = $this->objFromFixture('VersionedOwnershipTest_Related', 'related2_published');
365
		/** @var VersionedOwnershipTest_Attachment $attachment3 */
366
		$attachment3 = $this->objFromFixture('VersionedOwnershipTest_Attachment', 'attachment3_published');
367
		/** @var VersionedOwnershipTest_RelatedMany $relatedMany4 */
368
		$relatedMany4 = $this->objFromFixture('VersionedOwnershipTest_RelatedMany', 'relatedmany4_published');
369
		/** @var VersionedOwnershipTest_Related $related2 */
370
		$this->assertTrue($related2->doUnpublish());
371
		$subclass2 = $this->objFromFixture('VersionedOwnershipTest_Subclass', 'subclass2_published');
372
373
		/** @var VersionedOwnershipTest_Subclass $subclass2 */
374
		$this->assertFalse($subclass2->isPublished()); // Owner IS unpublished
375
		$this->assertTrue($attachment3->isPublished()); // Owned object is NOT unpublished
376
		$this->assertTrue($relatedMany4->isPublished()); // Owned object by owner is NOT unpublished
377
378
		// Second test: multi-level unpublish should recursively cascade down all owning objects
379
		// Publish related2 again
380
		$subclass2->publishRecursive();
381
		$this->assertTrue($subclass2->isPublished());
382
		$this->assertTrue($related2->isPublished());
383
		$this->assertTrue($attachment3->isPublished());
384
385
		// Unpublish leaf node
386
		$this->assertTrue($attachment3->doUnpublish());
387
388
		// Now all owning objects (only) are unpublished
389
		$this->assertFalse($attachment3->isPublished()); // Unpublished because we just unpublished it
390
		$this->assertFalse($related2->isPublished()); // Unpublished because it owns attachment3
391
		$this->assertFalse($subclass2->isPublished()); // Unpublished ecause it owns related2
392
		$this->assertTrue($relatedMany4->isPublished()); // Stays live because recursion only affects owners not owned.
393
	}
394
395
	public function testRecursiveArchive() {
396
		// When archiving an object, any published owners should be unpublished at the same time
397
		// but NOT achived
398
399
		/** @var VersionedOwnershipTest_Attachment $attachment3 */
400
		$attachment3 = $this->objFromFixture('VersionedOwnershipTest_Attachment', 'attachment3_published');
401
		$attachment3ID = $attachment3->ID;
402
		$this->assertTrue($attachment3->doArchive());
403
404
		// This object is on neither stage nor live
405
		$stageAttachment = Versioned::get_by_stage('VersionedOwnershipTest_Attachment', Versioned::DRAFT)
406
			->byID($attachment3ID);
407
		$liveAttachment = Versioned::get_by_stage('VersionedOwnershipTest_Attachment', Versioned::LIVE)
408
			->byID($attachment3ID);
409
		$this->assertEmpty($stageAttachment);
410
		$this->assertEmpty($liveAttachment);
411
412
		// Owning object is unpublished only
413
		/** @var VersionedOwnershipTest_Related $stageOwner */
414
		$stageOwner = $this->objFromFixture('VersionedOwnershipTest_Related', 'related2_published');
415
		$this->assertTrue($stageOwner->isOnDraft());
416
		$this->assertFalse($stageOwner->isPublished());
417
418
		// Bottom level owning object is also unpublished
419
		/** @var VersionedOwnershipTest_Subclass $stageTopOwner */
420
		$stageTopOwner = $this->objFromFixture('VersionedOwnershipTest_Subclass', 'subclass2_published');
421
		$this->assertTrue($stageTopOwner->isOnDraft());
422
		$this->assertFalse($stageTopOwner->isPublished());
423
	}
424
425
	public function testRecursiveRevertToLive() {
426
		/** @var VersionedOwnershipTest_Subclass $parent */
427
		$parent = $this->objFromFixture('VersionedOwnershipTest_Subclass', 'subclass1_published');
428
		$parentID = $parent->ID;
429
		$banner1 = $this->objFromFixture('VersionedOwnershipTest_RelatedMany', 'relatedmany1_published');
430
		$banner2 = $this->objFromFixture('VersionedOwnershipTest_RelatedMany', 'relatedmany2_published');
431
		$banner2ID = $banner2->ID;
432
433
		// Modify, Add, and Delete banners on stage
434
		$banner1->Title = 'Renamed Banner 1';
0 ignored issues
show
Documentation introduced by
The property Title 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...
435
		$banner1->write();
436
437
		$banner2->delete();
438
439
		$banner4 = new VersionedOwnershipTest_RelatedMany();
440
		$banner4->Title = 'New Banner';
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<VersionedOwnershipTest_RelatedMany>. 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...
441
		$banner4->write();
442
		$parent->Banners()->add($banner4);
443
444
		// Check state of objects before publish
445
		$liveBanners = [
446
			['Title' => 'Related Many 1'],
447
			['Title' => 'Related Many 2'],
448
		];
449
		$modifiedBanners = [
450
			['Title' => 'Renamed Banner 1'], // Renamed
451
			['Title' => 'Related Many 3'], // Published without changes
452
			['Title' => 'New Banner'], // Created
453
		];
454
		$parentDraft = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::DRAFT)
455
			->byID($parentID);
456
		$this->assertDOSEquals($modifiedBanners, $parentDraft->Banners());
457
		$parentLive = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::LIVE)
458
			->byID($parentID);
459
		$this->assertDOSEquals($liveBanners, $parentLive->Banners());
460
461
		// When reverting parent, all records should be put back on stage
462
		$this->assertTrue($parent->doRevertToLive());
463
464
		// Now check each object has the correct state
465
		$parentDraft = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::DRAFT)
466
			->byID($parentID);
467
		$this->assertDOSEquals($liveBanners, $parentDraft->Banners());
468
		$parentLive = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::LIVE)
469
			->byID($parentID);
470
		$this->assertDOSEquals($liveBanners, $parentLive->Banners());
471
472
		// Check that the newly created banner, even though it still exist, has been
473
		// unlinked from the reverted draft record
474
		/** @var VersionedOwnershipTest_RelatedMany $banner4Draft */
475
		$banner4Draft = Versioned::get_by_stage('VersionedOwnershipTest_RelatedMany', Versioned::DRAFT)
476
			->byID($banner4->ID);
477
		$this->assertTrue($banner4Draft->isOnDraft());
478
		$this->assertFalse($banner4Draft->isPublished());
479
		$this->assertEmpty($banner4Draft->PageID);
480
	}
481
482
	/**
483
	 * Test that rolling back to a single version works recursively
484
	 */
485
	public function testRecursiveRollback() {
486
		/** @var VersionedOwnershipTest_Subclass $subclass2 */
487
		$this->sleep(1);
488
		$subclass2 = $this->objFromFixture('VersionedOwnershipTest_Subclass', 'subclass2_published');
489
490
		// Create a few new versions
491
		$versions = [];
492
		for($version = 1; $version <= 3; $version++) {
493
			// Write owned objects
494
			$this->sleep(1);
495
			foreach($subclass2->findOwned(true) as $obj) {
496
				$obj->Title .= " - v{$version}";
497
				$obj->write();
498
			}
499
			// Write parent
500
			$this->sleep(1);
501
			$subclass2->Title .= " - v{$version}";
0 ignored issues
show
Documentation introduced by
The property Title 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...
502
			$subclass2->write();
503
			$versions[$version] = $subclass2->Version;
0 ignored issues
show
Documentation introduced by
The property Version 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...
504
		}
505
506
507
		// Check reverting to first version
508
		$this->sleep(1);
509
		$subclass2->doRollbackTo($versions[1]);
510
		/** @var VersionedOwnershipTest_Subclass $subclass2Draft */
511
		$subclass2Draft = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::DRAFT)
512
			->byID($subclass2->ID);
513
		$this->assertEquals('Subclass 2 - v1', $subclass2Draft->Title);
514
		$this->assertDOSEquals(
515
			[
516
				['Title' => 'Related 2 - v1'],
517
				['Title' => 'Attachment 3 - v1'],
518
				['Title' => 'Attachment 4 - v1'],
519
				['Title' => 'Attachment 5 - v1'],
520
				['Title' => 'Related Many 4 - v1'],
521
			],
522
			$subclass2Draft->findOwned(true)
523
		);
524
525
		// Check rolling forward to a later version
526
		$this->sleep(1);
527
		$subclass2->doRollbackTo($versions[3]);
528
		/** @var VersionedOwnershipTest_Subclass $subclass2Draft */
529
		$subclass2Draft = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::DRAFT)
530
			->byID($subclass2->ID);
531
		$this->assertEquals('Subclass 2 - v1 - v2 - v3', $subclass2Draft->Title);
532
		$this->assertDOSEquals(
533
			[
534
				['Title' => 'Related 2 - v1 - v2 - v3'],
535
				['Title' => 'Attachment 3 - v1 - v2 - v3'],
536
				['Title' => 'Attachment 4 - v1 - v2 - v3'],
537
				['Title' => 'Attachment 5 - v1 - v2 - v3'],
538
				['Title' => 'Related Many 4 - v1 - v2 - v3'],
539
			],
540
			$subclass2Draft->findOwned(true)
541
		);
542
543
		// And rolling back one version
544
		$this->sleep(1);
545
		$subclass2->doRollbackTo($versions[2]);
546
		/** @var VersionedOwnershipTest_Subclass $subclass2Draft */
547
		$subclass2Draft = Versioned::get_by_stage('VersionedOwnershipTest_Subclass', Versioned::DRAFT)
548
			->byID($subclass2->ID);
549
		$this->assertEquals('Subclass 2 - v1 - v2', $subclass2Draft->Title);
550
		$this->assertDOSEquals(
551
			[
552
				['Title' => 'Related 2 - v1 - v2'],
553
				['Title' => 'Attachment 3 - v1 - v2'],
554
				['Title' => 'Attachment 4 - v1 - v2'],
555
				['Title' => 'Attachment 5 - v1 - v2'],
556
				['Title' => 'Related Many 4 - v1 - v2'],
557
			],
558
			$subclass2Draft->findOwned(true)
559
		);
560
	}
561
562
	/**
563
	 * Test that you can find owners without owned_by being defined explicitly
564
	 */
565
	public function testInferedOwners() {
566
		// Make sure findOwned() works
567
		/** @var VersionedOwnershipTest_Page $page1 */
568
		$page1 = $this->objFromFixture('VersionedOwnershipTest_Page', 'page1_published');
569
		/** @var VersionedOwnershipTest_Page $page2 */
570
		$page2 = $this->objFromFixture('VersionedOwnershipTest_Page', 'page2_published');
571
		$this->assertDOSEquals(
572
			[
573
				['Title' => 'Banner 1'],
574
				['Title' => 'Image 1'],
575
				['Title' => 'Custom 1'],
576
			],
577
			$page1->findOwned()
578
		);
579
		$this->assertDOSEquals(
580
			[
581
				['Title' => 'Banner 2'],
582
				['Title' => 'Banner 3'],
583
				['Title' => 'Image 1'],
584
				['Title' => 'Image 2'],
585
				['Title' => 'Custom 2'],
586
			],
587
			$page2->findOwned()
588
		);
589
590
		// Check that findOwners works
591
		/** @var VersionedOwnershipTest_Image $image1 */
592
		$image1 = $this->objFromFixture('VersionedOwnershipTest_Image', 'image1_published');
593
		/** @var VersionedOwnershipTest_Image $image2 */
594
		$image2 = $this->objFromFixture('VersionedOwnershipTest_Image', 'image2_published');
595
596
		$this->assertDOSEquals(
597
			[
598
				['Title' => 'Banner 1'],
599
				['Title' => 'Banner 2'],
600
				['Title' => 'Page 1'],
601
				['Title' => 'Page 2'],
602
			],
603
			$image1->findOwners()
604
		);
605
		$this->assertDOSEquals(
606
			[
607
				['Title' => 'Banner 1'],
608
				['Title' => 'Banner 2'],
609
			],
610
			$image1->findOwners(false)
611
		);
612
		$this->assertDOSEquals(
613
			[
614
				['Title' => 'Banner 3'],
615
				['Title' => 'Page 2'],
616
			],
617
			$image2->findOwners()
618
		);
619
		$this->assertDOSEquals(
620
			[
621
				['Title' => 'Banner 3'],
622
			],
623
			$image2->findOwners(false)
624
		);
625
626
		// Test custom relation can findOwners()
627
		/** @var VersionedOwnershipTest_CustomRelation $custom1 */
628
		$custom1 = $this->objFromFixture('VersionedOwnershipTest_CustomRelation', 'custom1_published');
629
		$this->assertDOSEquals(
630
			[['Title' => 'Page 1']],
631
			$custom1->findOwners()
632
		);
633
634
	}
635
636
}
637
638
/**
639
 * @mixin Versioned
640
 */
641
class VersionedOwnershipTest_Object extends DataObject implements TestOnly {
642
	private static $extensions = array(
643
		Versioned::class,
644
	);
645
646
	private static $db = array(
647
		'Title' => 'Varchar(255)',
648
		'Content' => 'Text',
649
	);
650
}
651
652
/**
653
 * Object which:
654
 * - owns a has_one object
655
 * - owns has_many objects
656
 */
657
class VersionedOwnershipTest_Subclass extends VersionedOwnershipTest_Object implements TestOnly {
658
	private static $db = array(
659
		'Description' => 'Text',
660
	);
661
662
	private static $has_one = array(
663
		'Related' => 'VersionedOwnershipTest_Related',
664
	);
665
666
	private static $has_many = array(
667
		'Banners' => 'VersionedOwnershipTest_RelatedMany'
668
	);
669
670
	private static $owns = array(
671
		'Related',
672
		'Banners',
673
	);
674
}
675
676
/**
677
 * Object which:
678
 * - owned by has_many objects
679
 * - owns many_many Objects
680
 *
681
 * @mixin Versioned
682
 */
683
class VersionedOwnershipTest_Related extends DataObject implements TestOnly {
684
	private static $extensions = array(
685
		Versioned::class,
686
	);
687
688
	private static $db = array(
689
		'Title' => 'Varchar(255)',
690
	);
691
692
	private static $has_many = array(
693
		'Parents' => 'VersionedOwnershipTest_Subclass.Related',
694
	);
695
696
	private static $owned_by = array(
697
		'Parents',
698
	);
699
700
	private static $many_many = array(
701
		// Note : Currently unversioned, take care
702
		'Attachments' => 'VersionedOwnershipTest_Attachment',
703
	);
704
705
	private static $owns = array(
706
		'Attachments',
707
	);
708
}
709
710
/**
711
 * Object which is owned by a has_one object
712
 *
713
 * @mixin Versioned
714
 */
715
class VersionedOwnershipTest_RelatedMany extends DataObject implements TestOnly {
716
	private static $extensions = array(
717
		Versioned::class,
718
	);
719
720
	private static $db = array(
721
		'Title' => 'Varchar(255)',
722
	);
723
724
	private static $has_one = array(
725
		'Page' => 'VersionedOwnershipTest_Subclass'
726
	);
727
728
	private static $owned_by = array(
729
		'Page'
730
	);
731
}
732
733
/**
734
 * @mixin Versioned
735
 */
736
class VersionedOwnershipTest_Attachment extends DataObject implements TestOnly {
737
738
	private static $extensions = array(
739
		Versioned::class,
740
	);
741
742
	private static $db = array(
743
		'Title' => 'Varchar(255)',
744
	);
745
746
	private static $belongs_many_many = array(
747
		'AttachedTo' => 'VersionedOwnershipTest_Related.Attachments'
748
	);
749
750
	private static $owned_by = array(
751
		'AttachedTo'
752
	);
753
}
754
755
/**
756
 * Page which owns a lits of banners
757
 *
758
 * @mixin Versioned
759
 */
760
class VersionedOwnershipTest_Page extends DataObject implements TestOnly {
761
	private static $extensions = array(
762
		Versioned::class,
763
	);
764
765
	private static $db = array(
766
		'Title' => 'Varchar(255)',
767
	);
768
769
	private static $many_many = array(
770
		'Banners' => 'VersionedOwnershipTest_Banner',
771
	);
772
773
	private static $owns = array(
774
		'Banners',
775
		'Custom'
776
	);
777
778
	/**
779
	 * All custom objects with the same number. E.g. 'Page 1' owns 'Custom 1'
780
	 *
781
	 * @return DataList
782
	 */
783
	public function Custom() {
784
		$title = str_replace('Page', 'Custom', $this->Title);
785
		return VersionedOwnershipTest_CustomRelation::get()
786
			->filter('Title', $title);
787
	}
788
}
789
790
/**
791
 * Banner which doesn't declare its belongs_many_many, but owns an Image
792
 *
793
 * @mixin Versioned
794
 */
795
class VersionedOwnershipTest_Banner extends DataObject implements TestOnly {
796
	private static $extensions = array(
797
		Versioned::class,
798
	);
799
800
	private static $db = array(
801
		'Title' => 'Varchar(255)',
802
	);
803
804
	private static $has_one = array(
805
		'Image' => 'VersionedOwnershipTest_Image',
806
	);
807
808
	private static $owns = array(
809
		'Image',
810
	);
811
}
812
813
814
/**
815
 * Object which is owned via a custom PHP method rather than DB relation
816
 *
817
 * @mixin Versioned
818
 */
819
class VersionedOwnershipTest_CustomRelation extends DataObject implements TestOnly {
820
	private static $extensions = array(
821
		Versioned::class,
822
	);
823
824
	private static $db = array(
825
		'Title' => 'Varchar(255)',
826
	);
827
828
	private static $owned_by = array(
829
		'Pages'
830
	);
831
832
	/**
833
	 * All pages with the same number. E.g. 'Page 1' owns 'Custom 1'
834
	 *
835
	 * @return DataList
836
	 */
837
	public function Pages() {
838
		$title = str_replace('Custom', 'Page', $this->Title);
839
		return VersionedOwnershipTest_Page::get()->filter('Title', $title);
840
	}
841
842
}
843
844
/**
845
 * Simple versioned dataobject
846
 *
847
 * @mixin Versioned
848
 */
849
class VersionedOwnershipTest_Image extends DataObject implements TestOnly {
850
	private static $extensions = array(
851
		Versioned::class,
852
	);
853
854
	private static $db = array(
855
		'Title' => 'Varchar(255)',
856
	);
857
}
858