Completed
Push — master ( 38025f...2e259d )
by Damian
19s
created

ChangeSetTest::testAddObject()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 12
rs 9.4285
cc 1
eloc 8
nc 1
nop 0
1
<?php
2
3
/**
4
 * Provides a set of targettable permissions for tested models
5
 *
6
 * @mixin Versioned
7
 * @mixin DataObject
8
 */
9
trait ChangeSetTest_Permissions {
10
	public function canEdit($member = null) {
11
		return $this->can(__FUNCTION__, $member);
12
	}
13
14
	public function canDelete($member = null) {
15
		return $this->can(__FUNCTION__, $member);
16
	}
17
18
	public function canCreate($member = null, $context = array()) {
19
		return $this->can(__FUNCTION__, $member, $context);
20
	}
21
22
	public function canPublish($member = null, $context = array()) {
23
		return $this->can(__FUNCTION__, $member, $context);
24
	}
25
26
	public function canUnpublish($member = null, $context = array()) {
27
		return $this->can(__FUNCTION__, $member, $context);
28
	}
29
30
	public function can($perm, $member = null, $context = array()) {
0 ignored issues
show
Unused Code introduced by
The parameter $context is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
31
		$perms = [
32
			"PERM_{$perm}",
33
			'CAN_ALL',
34
		];
35
		return Permission::checkMember($member, $perms);
36
	}
37
}
38
39
/**
40
 * @mixin Versioned
41
 */
42
class ChangeSetTest_Base extends DataObject implements TestOnly {
43
	use ChangeSetTest_Permissions;
44
45
	private static $db = [
46
		'Foo' => 'Int',
47
	];
48
49
	private static $has_many = [
50
		'Mids' => 'ChangeSetTest_Mid',
51
	];
52
53
	private static $owns = [
54
		'Mids',
55
	];
56
57
	private static $extensions = [
58
		"Versioned",
59
	];
60
}
61
62
/**
63
 * @mixin Versioned
64
 */
65
class ChangeSetTest_Mid extends DataObject implements TestOnly {
66
	use ChangeSetTest_Permissions;
67
68
	private static $db = [
69
		'Bar' => 'Int',
70
	];
71
72
	private static $has_one = [
73
		'Base' => 'ChangeSetTest_Base',
74
		'End' => 'ChangeSetTest_End',
75
	];
76
77
	private static $owns = [
78
		'End',
79
	];
80
81
	private static $extensions = [
82
		"Versioned",
83
	];
84
}
85
86
/**
87
 * @mixin Versioned
88
 */
89
class ChangeSetTest_End extends DataObject implements TestOnly {
90
	use ChangeSetTest_Permissions;
91
92
	private static $db = [
93
		'Baz' => 'Int',
94
	];
95
96
	private static $extensions = [
97
		"Versioned",
98
	];
99
}
100
101
/**
102
 * @mixin Versioned
103
 */
104
class ChangeSetTest_EndChild extends ChangeSetTest_End implements TestOnly {
105
106
	private static $db = [
107
		'Qux' => 'Int',
108
	];
109
}
110
111
/**
112
 * Test {@see ChangeSet} and {@see ChangeSetItem} models
113
 */
114
class ChangeSetTest extends SapphireTest {
115
116
	protected static $fixture_file = 'ChangeSetTest.yml';
117
118
	protected $extraDataObjects = [
119
		'ChangeSetTest_Base',
120
		'ChangeSetTest_Mid',
121
		'ChangeSetTest_End',
122
		'ChangeSetTest_EndChild',
123
	];
124
125
	/**
126
	 * Automatically publish all objects
127
	 */
128
	protected function publishAllFixtures() {
129
		$this->logInWithPermission('ADMIN');
130
		foreach($this->fixtureFactory->getFixtures() as $class => $fixtures) {
131
			foreach ($fixtures as $handle => $id) {
132
				/** @var Versioned|DataObject $object */
133
				$object = $this->objFromFixture($class, $handle);
134
				$object->publishSingle();
0 ignored issues
show
Bug introduced by
The method publishSingle does only exist in Versioned, but not in 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...
135
			}
136
		}
137
	}
138
139
	/**
140
	 * Check that the changeset includes the given items
141
	 *
142
	 * @param ChangeSet $cs
143
	 * @param array $match Array of object fixture keys with change type values
144
	 */
145
	protected function assertChangeSetLooksLike($cs, $match) {
146
		$items = $cs->Changes()->toArray();
147
148
		foreach($match as $key => $mode) {
149
			list($class, $identifier) = explode('.', $key);
150
			$object = $this->objFromFixture($class, $identifier);
151
152
			foreach($items as $i => $item) {
153
				if ($item->ObjectClass == ClassInfo::baseDataClass($object) && $item->ObjectID == $object->ID && $item->Added == $mode) {
0 ignored issues
show
Bug introduced by
It seems like $object defined by $this->objFromFixture($class, $identifier) on line 150 can be null; however, ClassInfo::baseDataClass() 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...
154
					unset($items[$i]);
155
					continue 2;
156
				}
157
			}
158
159
			throw new PHPUnit_Framework_ExpectationFailedException(
160
				'Change set didn\'t include expected item',
161
				new \SebastianBergmann\Comparator\ComparisonFailure(array('Class' => $class, 'ID' => $object->ID, 'Added' => $mode), null, "$key => $mode", '')
162
			);
163
		}
164
165
		if (count($items)) {
166
			$extra = [];
167
			foreach ($items as $item) $extra[] = ['Class' => $item->ObjectClass, 'ID' => $item->ObjectID, 'Added' => $item->Added, 'ChangeType' => $item->getChangeType()];
168
			throw new PHPUnit_Framework_ExpectationFailedException(
169
				'Change set included items that weren\'t expected',
170
				new \SebastianBergmann\Comparator\ComparisonFailure(array(), $extra, '', print_r($extra, true))
171
			);
172
		}
173
	}
174
	
175
	public function testAddObject() {
176
		$cs = new ChangeSet();
177
		$cs->write();
178
179
		$cs->addObject($this->objFromFixture('ChangeSetTest_End', 'end1'));
0 ignored issues
show
Bug introduced by
It seems like $this->objFromFixture('C...geSetTest_End', 'end1') can be null; however, addObject() 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...
180
		$cs->addObject($this->objFromFixture('ChangeSetTest_EndChild', 'endchild1'));
0 ignored issues
show
Bug introduced by
It seems like $this->objFromFixture('C...EndChild', 'endchild1') can be null; however, addObject() 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...
181
182
		$this->assertChangeSetLooksLike($cs, [
183
			'ChangeSetTest_End.end1' => ChangeSetItem::EXPLICITLY,
184
			'ChangeSetTest_EndChild.endchild1' => ChangeSetItem::EXPLICITLY
185
		]);
186
	}
187
188
	public function testRepeatedSyncIsNOP() {
189
		$this->publishAllFixtures();
190
191
		$cs = new ChangeSet();
192
		$cs->write();
193
194
		$base = $this->objFromFixture('ChangeSetTest_Base', 'base');
195
		$cs->addObject($base);
0 ignored issues
show
Bug introduced by
It seems like $base defined by $this->objFromFixture('C...eSetTest_Base', 'base') on line 194 can be null; however, ChangeSet::addObject() 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...
196
197
		$cs->sync();
198
		$this->assertChangeSetLooksLike($cs, [
199
			'ChangeSetTest_Base.base' => ChangeSetItem::EXPLICITLY
200
		]);
201
202
		$cs->sync();
203
		$this->assertChangeSetLooksLike($cs, [
204
			'ChangeSetTest_Base.base' => ChangeSetItem::EXPLICITLY
205
		]);
206
	}
207
208
	public function testSync() {
209
		$this->publishAllFixtures();
210
211
		$cs = new ChangeSet();
212
		$cs->write();
213
214
		$base = $this->objFromFixture('ChangeSetTest_Base', 'base');
215
216
		$cs->addObject($base);
0 ignored issues
show
Bug introduced by
It seems like $base defined by $this->objFromFixture('C...eSetTest_Base', 'base') on line 214 can be null; however, ChangeSet::addObject() 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...
217
		$cs->sync();
218
219
		$this->assertChangeSetLooksLike($cs, [
220
			'ChangeSetTest_Base.base' => ChangeSetItem::EXPLICITLY
221
		]);
222
223
		$end = $this->objFromFixture('ChangeSetTest_End', 'end1');
224
		$end->Baz = 3;
0 ignored issues
show
Documentation introduced by
The property Baz does not exist on object<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...
225
		$end->write();
226
227
		$cs->sync();
228
229
		$this->assertChangeSetLooksLike($cs, [
230
			'ChangeSetTest_Base.base' => ChangeSetItem::EXPLICITLY,
231
			'ChangeSetTest_End.end1' => ChangeSetItem::IMPLICITLY
232
		]);
233
234
		$baseItem = ChangeSetItem::get_for_object($base)->first();
0 ignored issues
show
Bug introduced by
It seems like $base defined by $this->objFromFixture('C...eSetTest_Base', 'base') on line 214 can be null; however, ChangeSetItem::get_for_object() 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...
235
		$endItem = ChangeSetItem::get_for_object($end)->first();
236
237
		$this->assertEquals(
238
			[$baseItem->ID],
239
			$endItem->ReferencedBy()->column("ID")
240
		);
241
242
		$this->assertDOSEquals([
243
			[
244
				'Added' => ChangeSetItem::EXPLICITLY,
245
				'ObjectClass' => 'ChangeSetTest_Base',
246
				'ObjectID' => $base->ID,
247
				'ChangeSetID' => $cs->ID
248
			]
249
		], $endItem->ReferencedBy());
250
	}
251
252
	/**
253
	 * Test that sync includes implicit items
254
	 */
255
	public function testIsSynced() {
256
		$this->publishAllFixtures();
257
258
		$cs = new ChangeSet();
259
		$cs->write();
260
261
		$base = $this->objFromFixture('ChangeSetTest_Base', 'base');
262
		$cs->addObject($base);
0 ignored issues
show
Bug introduced by
It seems like $base defined by $this->objFromFixture('C...eSetTest_Base', 'base') on line 261 can be null; however, ChangeSet::addObject() 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...
263
264
		$cs->sync();
265
		$this->assertChangeSetLooksLike($cs, [
266
			'ChangeSetTest_Base.base' => ChangeSetItem::EXPLICITLY
267
		]);
268
		$this->assertTrue($cs->isSynced());
269
270
		$end = $this->objFromFixture('ChangeSetTest_End', 'end1');
271
		$end->Baz = 3;
0 ignored issues
show
Documentation introduced by
The property Baz does not exist on object<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...
272
		$end->write();
273
		$this->assertFalse($cs->isSynced());
274
275
		$cs->sync();
276
277
		$this->assertChangeSetLooksLike($cs, [
278
			'ChangeSetTest_Base.base' => ChangeSetItem::EXPLICITLY,
279
			'ChangeSetTest_End.end1' => ChangeSetItem::IMPLICITLY
280
		]);
281
		$this->assertTrue($cs->isSynced());
282
	}
283
284
	public function testCanPublish() {
285
		// Create changeset containing all items (unpublished)
286
		$this->logInWithPermission('ADMIN');
287
		$changeSet = new ChangeSet();
288
		$changeSet->write();
289
		$base = $this->objFromFixture('ChangeSetTest_Base', 'base');
290
		$changeSet->addObject($base);
0 ignored issues
show
Bug introduced by
It seems like $base defined by $this->objFromFixture('C...eSetTest_Base', 'base') on line 289 can be null; however, ChangeSet::addObject() 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...
291
		$changeSet->sync();
292
		$this->assertEquals(5, $changeSet->Changes()->count());
293
294
		// Test un-authenticated user cannot publish
295
		Session::clear("loggedInAs");
296
		$this->assertFalse($changeSet->canPublish());
297
298
		// User with only one of the necessary permissions cannot publish
299
		$this->logInWithPermission('CMS_ACCESS_CampaignAdmin');
300
		$this->assertFalse($changeSet->canPublish());
301
		$this->logInWithPermission('PERM_canPublish');
302
		$this->assertFalse($changeSet->canPublish());
303
304
		// Test user with the necessary minimum permissions can login
305
		$this->logInWithPermission([
306
			'CMS_ACCESS_CampaignAdmin',
307
			'PERM_canPublish'
308
		]);
309
		$this->assertTrue($changeSet->canPublish());
310
	}
311
312
	public function testCanRevert() {
313
		$this->markTestSkipped("Requires ChangeSet::revert to be implemented first");
314
	}
315
316
	public function testCanEdit() {
317
		// Create changeset containing all items (unpublished)
318
		$this->logInWithPermission('ADMIN');
319
		$changeSet = new ChangeSet();
320
		$changeSet->write();
321
		$base = $this->objFromFixture('ChangeSetTest_Base', 'base');
322
		$changeSet->addObject($base);
0 ignored issues
show
Bug introduced by
It seems like $base defined by $this->objFromFixture('C...eSetTest_Base', 'base') on line 321 can be null; however, ChangeSet::addObject() 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...
323
		$changeSet->sync();
324
		$this->assertEquals(5, $changeSet->Changes()->count());
325
326
		// Check canEdit
327
		Session::clear("loggedInAs");
328
		$this->assertFalse($changeSet->canEdit());
329
		$this->logInWithPermission('SomeWrongPermission');
330
		$this->assertFalse($changeSet->canEdit());
331
		$this->logInWithPermission('CMS_ACCESS_CampaignAdmin');
332
		$this->assertTrue($changeSet->canEdit());
333
	}
334
335
	public function testCanCreate() {
336
		// Check canCreate
337
		Session::clear("loggedInAs");
338
		$this->assertFalse(ChangeSet::singleton()->canCreate());
339
		$this->logInWithPermission('SomeWrongPermission');
340
		$this->assertFalse(ChangeSet::singleton()->canCreate());
341
		$this->logInWithPermission('CMS_ACCESS_CampaignAdmin');
342
		$this->assertTrue(ChangeSet::singleton()->canCreate());
343
	}
344
345
	public function testCanDelete() {
346
		// Create changeset containing all items (unpublished)
347
		$this->logInWithPermission('ADMIN');
348
		$changeSet = new ChangeSet();
349
		$changeSet->write();
350
		$base = $this->objFromFixture('ChangeSetTest_Base', 'base');
351
		$changeSet->addObject($base);
0 ignored issues
show
Bug introduced by
It seems like $base defined by $this->objFromFixture('C...eSetTest_Base', 'base') on line 350 can be null; however, ChangeSet::addObject() 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...
352
		$changeSet->sync();
353
		$this->assertEquals(5, $changeSet->Changes()->count());
354
355
		// Check canDelete
356
		Session::clear("loggedInAs");
357
		$this->assertFalse($changeSet->canDelete());
358
		$this->logInWithPermission('SomeWrongPermission');
359
		$this->assertFalse($changeSet->canDelete());
360
		$this->logInWithPermission('CMS_ACCESS_CampaignAdmin');
361
		$this->assertTrue($changeSet->canDelete());
362
	}
363
364
	public function testCanView() {
365
		// Create changeset containing all items (unpublished)
366
		$this->logInWithPermission('ADMIN');
367
		$changeSet = new ChangeSet();
368
		$changeSet->write();
369
		$base = $this->objFromFixture('ChangeSetTest_Base', 'base');
370
		$changeSet->addObject($base);
0 ignored issues
show
Bug introduced by
It seems like $base defined by $this->objFromFixture('C...eSetTest_Base', 'base') on line 369 can be null; however, ChangeSet::addObject() 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...
371
		$changeSet->sync();
372
		$this->assertEquals(5, $changeSet->Changes()->count());
373
374
		// Check canView
375
		Session::clear("loggedInAs");
376
		$this->assertFalse($changeSet->canView());
377
		$this->logInWithPermission('SomeWrongPermission');
378
		$this->assertFalse($changeSet->canView());
379
		$this->logInWithPermission('CMS_ACCESS_CampaignAdmin');
380
		$this->assertTrue($changeSet->canView());
381
	}
382
383
	public function testPublish() {
384
		$this->publishAllFixtures();
385
386
		$base = $this->objFromFixture('ChangeSetTest_Base', 'base');
387
		$baseID = $base->ID;
388
		$baseBefore = $base->Version;
0 ignored issues
show
Documentation introduced by
The property Version does not exist on object<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...
389
		$end1 = $this->objFromFixture('ChangeSetTest_End', 'end1');
390
		$end1ID = $end1->ID;
391
		$end1Before = $end1->Version;
0 ignored issues
show
Documentation introduced by
The property Version does not exist on object<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...
392
393
		// Create a new changest
394
		$changeset = new ChangeSet();
395
		$changeset->write();
396
		$changeset->addObject($base);
397
		$changeset->addObject($end1);
398
399
		// Make a lot of changes
400
		// - ChangeSetTest_Base.base modified
401
		// - ChangeSetTest_End.end1 deleted
402
		// - new ChangeSetTest_Mid added
403
		$base->Foo = 343;
0 ignored issues
show
Documentation introduced by
The property Foo does not exist on object<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...
404
		$base->write();
405
		$baseAfter = $base->Version;
0 ignored issues
show
Documentation introduced by
The property Version does not exist on object<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...
406
		$midNew = new ChangeSetTest_Mid();
407
		$midNew->Bar = 39;
0 ignored issues
show
Documentation introduced by
The property Bar does not exist on object<ChangeSetTest_Mid>. 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...
408
		$midNew->write();
409
		$midNewID = $midNew->ID;
410
		$midNewAfter = $midNew->Version;
0 ignored issues
show
Documentation introduced by
The property Version does not exist on object<ChangeSetTest_Mid>. 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...
411
		$end1->delete();
412
413
		$changeset->addObject($midNew);
414
415
		// Publish
416
		$this->logInWithPermission('ADMIN');
417
		$this->assertTrue($changeset->canPublish());
418
		$this->assertTrue($changeset->isSynced());
419
		$changeset->publish();
420
		$this->assertEquals(ChangeSet::STATE_PUBLISHED, $changeset->State);
421
422
		// Check each item has the correct before/after version applied
423
		$baseChange = $changeset->Changes()->filter([
424
			'ObjectClass' => 'ChangeSetTest_Base',
425
			'ObjectID' => $baseID,
426
		])->first();
427
		$this->assertEquals((int)$baseBefore, (int)$baseChange->VersionBefore);
428
		$this->assertEquals((int)$baseAfter, (int)$baseChange->VersionAfter);
429
		$this->assertEquals((int)$baseChange->VersionBefore + 1, (int)$baseChange->VersionAfter);
430
		$this->assertEquals(
431
			(int)$baseChange->VersionAfter,
432
			(int)Versioned::get_versionnumber_by_stage('ChangeSetTest_Base', Versioned::LIVE, $baseID)
433
		);
434
435
		$end1Change = $changeset->Changes()->filter([
436
			'ObjectClass' => 'ChangeSetTest_End',
437
			'ObjectID' => $end1ID,
438
		])->first();
439
		$this->assertEquals((int)$end1Before, (int)$end1Change->VersionBefore);
440
		$this->assertEquals(0, (int)$end1Change->VersionAfter);
441
		$this->assertEquals(
442
			0,
443
			(int)Versioned::get_versionnumber_by_stage('ChangeSetTest_End', Versioned::LIVE, $end1ID)
444
		);
445
446
		$midNewChange = $changeset->Changes()->filter([
447
			'ObjectClass' => 'ChangeSetTest_Mid',
448
			'ObjectID' => $midNewID,
449
		])->first();
450
		$this->assertEquals(0, (int)$midNewChange->VersionBefore);
451
		$this->assertEquals((int)$midNewAfter, (int)$midNewChange->VersionAfter);
452
		$this->assertEquals(
453
			(int)$midNewAfter,
454
			(int)Versioned::get_versionnumber_by_stage('ChangeSetTest_Mid', Versioned::LIVE, $midNewID)
455
		);
456
457
		// Test trying to re-publish is blocked
458
		$this->setExpectedException(
459
			'BadMethodCallException',
460
			"ChangeSet can't be published if it has been already published or reverted."
461
		);
462
		$changeset->publish();
463
	}
464
465
}
466