Completed
Push — master ( 8a8424...13f8fb )
by tsms
28s
created

StateMachineBehaviorTest   B

Complexity

Total Complexity 38

Size/Duplication

Total Lines 632
Duplicated Lines 7.28 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
wmc 38
lcom 1
cbo 4
dl 46
loc 632
rs 7.2413
c 0
b 0
f 0

38 Methods

Rating   Name   Duplication   Size   Complexity  
A setUp() 0 6 1
A testGetAllTransitions() 0 3 1
A testAvailableStates() 0 3 1
A testTransitionById() 0 13 1
A testIgnoreValidationOnTransition() 0 9 1
A testValidateOnTransition() 0 7 1
A testStateListener() 0 21 1
A testCanTransitionById() 0 16 1
A testFindAllByState() 23 23 1
A testFindCountByState() 0 10 1
A testFindFirstByState() 23 23 1
A testInitialState() 0 4 1
A testIsMethodsById() 0 16 1
B testIsMethods() 0 25 1
A testOnMethods() 0 16 1
A testBadMethodCall() 0 4 1
A whenParked() 0 3 1
A testWhenMethods() 0 12 1
A testBubble() 0 13 1
A testInvalidTransition() 0 5 1
A testVehicleTitle() 0 15 1
A testCreateVehicle() 0 10 1
A testAddToPrepareArrayNoRoles() 0 49 1
A testAddToPrepareArrayWithRoles() 0 54 1
B testAddToPrepareArrayWithDependsAndRoles() 0 77 1
A testToDot() 0 3 1
B testCreateDotFileForRoles() 0 45 1
A testCallable() 0 7 1
A testExistingCallable() 0 8 1
A testUnhandled() 0 4 1
A testInvalidOnStateChange() 0 4 1
A testOnStateChange() 0 11 1
A testRules() 0 19 1
A testRulesWithCanTransitionById() 0 19 1
A testRuleWithCallback() 0 13 1
A testInvalidRules() 0 6 1
A testWrongRole() 0 4 1
A tearDown() 0 4 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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 4 and the first side effect is on line 2.

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
App::uses('StateMachineBehavior', 'StateMachine.Model/Behavior');
3
4
class BaseVehicle extends CakeTestModel {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
5
6
	public $useTable = 'vehicles';
7
8
	public $actsAs = array('StateMachine.StateMachine');
9
10
	public $initialState = 'parked';
11
12
	public $transitions = array(
13
		'ignite' => array(
14
			'parked' => 'idling',
15
			'stalled' => 'stalled'
16
		),
17
		'park' => array(
18
			'idling' => 'parked',
19
			'first_gear' => 'parked'
20
		),
21
		'shift_up' => array(
22
			'idling' => 'first_gear',
23
			'first_gear' => 'second_gear',
24
			'second_gear' => 'third_gear'
25
		),
26
		'shift_down' => array(
27
			'first_gear' => 'idling',
28
			'second_gear' => 'first_gear',
29
			'third_gear' => 'second_gear'
30
		),
31
		'crash' => array(
32
			'first_gear' => 'stalled',
33
			'second_gear' => 'stalled',
34
			'third_gear' => 'stalled'
35
		),
36
		'repair' => array(
37
			'stalled' => 'parked'
38
		),
39
		'idle' => array(
40
			'first_gear' => 'idling'
41
		),
42
		'turn_off' => array(
43
			'all' => 'parked'
44
		),
45
		'baz' => array()
46
	);
47
}
48
49
class Vehicle extends BaseVehicle {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
50
51
	public function onStateChange($newState) {
52
	}
53
54
	public function onStateIdling($newState) {
55
	}
56
57
	public function onBeforeTransition($currentState, $previousState, $transition) {
58
	}
59
60
	public function onAfterTransition($currentState, $previousState, $transition) {
61
	}
62
63
	public function onBeforeIgnite($currentState, $previousState, $transition) {
64
	}
65
66
}
67
68
class RulesVehicle extends BaseVehicle {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
69
70
	public $transitionRules = array(
71
		'hardwire' => array(
72
			'role' => array('thief'),
73
		),
74
		'ignite' => array(
75
			'role' => array('driver'),
76
			'depends' => 'has_key'
77
		),
78
		'park' => array(
79
			'role' => array('driver', 'thief'),
80
			'depends' => 'available_parking'
81
		),
82
		'repair' => array(
83
			'role' => array('mechanic'),
84
			'depends' => 'has_tools'
85
		)
86
	);
87
88
	public function __construct($id = false, $table = null, $ds = null) {
89
		$this->transitions += array(
90
			'hardwire' => array(
91
				'parked' => 'idling',
92
				'stalled' => 'stalled'
93
			)
94
		);
95
96
		parent::__construct($id, $table, $ds);
97
	}
98
99
	public function hasKey($role) {
100
		if ($role == 'driver') {
101
			return true;
102
		}
103
104
		return false;
105
	}
106
107
	public function availableParking($role) {
108
		return $role == 'thief';
109
	}
110
111
}
112
113
class ValidationsVehicle extends Vehicle {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
114
115
	public $validate = array(
116
		'title' => array(
117
			'custom' => array(
118
				'rule' => array('custom', '/toyota yaris/i')
119
			)
120
		)
121
	);
122
123
}
124
125
class StateMachineBehaviorTest extends CakeTestCase {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
126
127
	public $fixtures = array(
128
		'plugin.state_machine.vehicle'
129
	);
130
131
	public $Vehicle;
132
133
	public $StateMachine;
134
135
	public function setUp() {
136
		parent::setUp();
137
138
		$this->Vehicle = new Vehicle(1);
139
		$this->StateMachine = $this->Vehicle->Behaviors->StateMachine;
140
	}
141
142
	public function testGetAllTransitions() {
143
		$this->assertCount(9, $this->Vehicle->getAllTransitions());
144
	}
145
146
	public function testAvailableStates() {
147
		$this->assertCount(6, $this->Vehicle->getAvailableStates());
148
	}
149
150
	public function testTransitionById() {
151
		$this->Vehicle->ignite(1);
152
		$this->assertEquals("idling", $this->Vehicle->getCurrentState(1));
153
		$this->assertEquals("parked", $this->Vehicle->getPreviousState(1));
154
		$this->assertEquals("ignite", $this->Vehicle->getLastTransition(1));
155
		$this->assertEquals("", $this->Vehicle->getLastRole(1));
156
		$this->Vehicle->shiftUp(1);
157
		$this->assertEquals("first_gear", $this->Vehicle->getCurrentState(1));
158
		$this->assertEquals("idling", $this->Vehicle->getPreviousState(1));
159
		$this->Vehicle->shiftUp(1);
160
		$this->assertEquals("second_gear", $this->Vehicle->getCurrentState(1));
161
		$this->assertEquals("first_gear", $this->Vehicle->getPreviousState(1));
162
	}
163
164
	public function testIgnoreValidationOnTransition() {
165
		$this->Vehicle = new ValidationsVehicle(1);
166
167
		$this->assertFalse($this->Vehicle->ignite());
168
		$this->assertEquals("parked", $this->Vehicle->getCurrentState());
169
170
		$this->assertTrue($this->Vehicle->ignite(null, null, false));
171
		$this->assertEquals("idling", $this->Vehicle->getCurrentState());
172
	}
173
174
	public function testValidateOnTransition() {
175
		$this->Vehicle = new ValidationsVehicle(1);
176
		$this->assertFalse($this->Vehicle->ignite());
177
178
		$this->Vehicle = new ValidationsVehicle(2);
179
		$this->assertTrue($this->Vehicle->ignite());
180
	}
181
182
	public function testStateListener() {
183
		// test state listener on transition failed
184
		$this->Vehicle = $this->getMock('ValidationsVehicle', array(
185
			'onStateChange', 'onStateIdling', 'onBeforeTransition', 'onAfterTransition'));
186
		$this->Vehicle->id = 1;
187
		$this->Vehicle->expects($this->once())->method('onBeforeTransition');
188
		$this->Vehicle->expects($this->never())->method('onAfterTransition');
189
		$this->Vehicle->expects($this->never())->method('onStateChange');
190
		$this->Vehicle->expects($this->never())->method('onStateIdling');
191
		$this->assertFalse($this->Vehicle->ignite());
192
193
		// test state listener on transition success
194
		$this->Vehicle = $this->getMock('ValidationsVehicle', array(
195
			'onStateChange', 'onStateIdling', 'onBeforeTransition', 'onAfterTransition'));
196
		$this->Vehicle->id = 2;
197
		$this->Vehicle->expects($this->once())->method('onBeforeTransition');
198
		$this->Vehicle->expects($this->once())->method('onAfterTransition');
199
		$this->Vehicle->expects($this->once())->method('onStateChange');
200
		$this->Vehicle->expects($this->once())->method('onStateIdling');
201
		$this->assertTrue($this->Vehicle->ignite());
202
	}
203
204
	public function testCanTransitionById() {
205
		$this->assertTrue($this->Vehicle->is('parked'));
206
207
		$this->assertEquals($this->Vehicle->can('shift_up', 1), $this->Vehicle->canShiftUp(1));
208
		$this->assertFalse($this->Vehicle->can('shift_up', 1));
209
210
		$this->assertTrue($this->Vehicle->can('ignite', 1));
211
		$this->Vehicle->ignite();
212
		$this->assertEquals("idling", $this->Vehicle->getCurrentState());
213
214
		$this->assertEquals($this->Vehicle->can('shift_up', 1), $this->Vehicle->canShiftUp(1));
215
		$this->assertTrue($this->Vehicle->canShiftUp(1));
216
		$this->assertFalse($this->Vehicle->canShiftDown(1));
217
218
		$this->assertFalse($this->Vehicle->canShiftUp(2));
219
	}
220
221 View Code Duplication
	public function testFindAllByState() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
222
		$this->assertFalse($this->Vehicle->findAllByState());
223
		$this->assertFalse($this->Vehicle->findAllByState('illegal_state_should_not_be_possible'));
224
		$this->assertFalse($this->Vehicle->findAllByState(array('illegal_state_should_not_be_possible', 'parked')));
225
		$this->assertCount(2, $this->Vehicle->findAllByState('parked'));
226
		$this->assertCount(1, $this->Vehicle->findAllByState('parked', array('conditions' => array('Vehicle.title' => 'Audi Q4'))));
227
		$this->assertCount(4, $this->Vehicle->findAllByState('all'));
228
		$this->assertCount(1, $this->Vehicle->findAllByState('idling'));
229
		$this->assertCount(3, $this->Vehicle->findAllByState(array('idling', 'parked')));
230
231
		// test with transition array
232
		$testTransitions = $this->Vehicle->findAllByState('parked');
233
		$this->assertTrue(is_array($testTransitions[0]['Vehicle']['Transitions']));
234
		$this->assertCount(2, $testTransitions[0]['Vehicle']['Transitions']);
235
236
		// test without transitions array
237
		$testTransitions = $this->Vehicle->findAllByState('parked', array(), false);
238
		$this->assertFalse(isset($testTransitions[0]['Vehicle']['Transitions']));
239
240
		$this->Vehicle = new RulesVehicle(1);
241
		$testTransitions = $this->Vehicle->findAllByState('parked', array(), true, 'driver');
242
		$this->assertEqual(array('ignite', 'turn_off'), $testTransitions[0]['RulesVehicle']['Transitions']);
243
	}
244
245
	public function testFindCountByState() {
246
		$this->assertFalse($this->Vehicle->findCountByState());
247
		$this->assertFalse($this->Vehicle->findCountByState('illegal_state_should_not_be_possible'));
248
		$this->assertFalse($this->Vehicle->findCountByState(array('illegal_state_should_not_be_possible', 'parked')));
249
		$this->assertEqual(2, $this->Vehicle->findCountByState('parked'));
250
		$this->assertEqual(1, $this->Vehicle->findCountByState('parked', array('conditions' => array('Vehicle.title' => 'Audi Q4'))));
251
		$this->assertEqual(4, $this->Vehicle->findCountByState('all'));
252
		$this->assertEqual(1, $this->Vehicle->findCountByState('idling'));
253
		$this->assertEqual(3, $this->Vehicle->findCountByState(array('idling', 'parked')));
254
	}
255
256 View Code Duplication
	public function testFindFirstByState() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
257
		$this->assertFalse($this->Vehicle->findFirstByState());
258
		$this->assertFalse($this->Vehicle->findFirstByState('illegal_state_should_not_be_possible'));
259
		$this->assertFalse($this->Vehicle->findFirstByState(array('illegal_state_should_not_be_possible', 'parked')));
260
		$this->assertCount(1, $this->Vehicle->findFirstByState('parked'));
261
		$this->assertCount(1, $this->Vehicle->findFirstByState('parked', array('conditions' => array('Vehicle.title' => 'Audi Q4'))));
262
		$this->assertCount(1, $this->Vehicle->findFirstByState('all'));
263
		$this->assertCount(1, $this->Vehicle->findFirstByState('idling'));
264
		$this->assertCount(1, $this->Vehicle->findFirstByState(array('idling', 'parked')));
265
266
		// test with transition array
267
		$testTransitions = $this->Vehicle->findFirstByState('parked');
268
		$this->assertTrue(is_array($testTransitions[0]['Vehicle']['Transitions']));
269
		$this->assertCount(2, $testTransitions[0]['Vehicle']['Transitions']);
270
271
		// test without transitions array
272
		$testTransitions = $this->Vehicle->findFirstByState('parked', array(), false);
273
		$this->assertFalse(isset($testTransitions[0]['Vehicle']['Transitions']));
274
275
		$this->Vehicle = new RulesVehicle(1);
276
		$testTransitions = $this->Vehicle->findFirstByState('parked', array(), true, 'driver');
277
		$this->assertEqual(array('ignite', 'turn_off'), $testTransitions[0]['RulesVehicle']['Transitions']);
278
	}
279
280
	public function testInitialState() {
281
		$this->assertEquals("parked", $this->Vehicle->getCurrentState());
282
		$this->assertEquals('parked', $this->Vehicle->getStates('turn_off'));
283
	}
284
285
	public function testIsMethodsById() {
286
		$this->assertEquals("parked", $this->Vehicle->getCurrentState());
287
		$this->assertEquals($this->Vehicle->isParked(1), $this->Vehicle->is('parked', 1));
288
		$this->assertTrue($this->Vehicle->isParked(1));
289
290
		$this->Vehicle->ignite();
291
292
		$this->assertEquals("idling", $this->Vehicle->getCurrentState());
293
		$this->assertEquals($this->Vehicle->isIdling(1), $this->Vehicle->is('idling', 1));
294
		$this->assertTrue($this->Vehicle->isIdling(1));
295
296
		$this->assertEquals("parked", $this->Vehicle->getCurrentState(2));
297
		$this->assertEquals($this->Vehicle->isParked(2), $this->Vehicle->is('parked', 2));
298
		$this->assertTrue($this->Vehicle->isParked(2));
299
		$this->assertFalse($this->Vehicle->isIdling(2));
300
	}
301
302
	public function testIsMethods() {
303
		$this->assertEquals($this->Vehicle->isParked(), $this->Vehicle->is('parked'));
304
		$this->assertEquals($this->Vehicle->isIdling(), $this->Vehicle->is('idling'));
305
		$this->assertEquals($this->Vehicle->isStalled(), $this->Vehicle->is('stalled'));
306
		$this->assertEquals($this->Vehicle->isIdling(), $this->Vehicle->is('idling'));
307
308
		$this->assertEquals($this->Vehicle->canShiftUp(), $this->Vehicle->can('shift_up'));
309
		$this->assertFalse($this->Vehicle->canShiftUp());
310
311
		$this->assertTrue($this->Vehicle->canIgnite());
312
		$this->Vehicle->ignite();
313
		$this->assertEquals("idling", $this->Vehicle->getCurrentState());
314
315
		$this->assertTrue($this->Vehicle->canShiftUp());
316
		$this->assertFalse($this->Vehicle->canShiftDown());
317
318
		$this->assertTrue($this->Vehicle->isIdling());
319
		$this->assertFalse($this->Vehicle->canCrash());
320
		$this->Vehicle->shiftUp();
321
		$this->Vehicle->crash();
322
		$this->assertEquals("stalled", $this->Vehicle->getCurrentState());
323
		$this->assertTrue($this->Vehicle->isStalled());
324
		$this->Vehicle->repair();
325
		$this->assertTrue($this->Vehicle->isParked());
326
	}
327
328
	public function testOnMethods() {
329
		$scope = $this;
330
		$this->Vehicle->onIgnite('before', function ($currentState, $previousState, $transition) use ($scope) {
331
			$scope->assertEquals("parked", $currentState);
332
			$scope->assertNull($previousState);
333
			$scope->assertEquals("ignite", $transition);
334
		});
335
336
		$this->Vehicle->on('ignite', 'after', function ($currentState, $previousState, $transition) use ($scope) {
337
			$scope->assertEquals("idling", $currentState);
338
			$scope->assertEquals("parked", $previousState);
339
			$scope->assertEquals("ignite", $transition);
340
		});
341
342
		$this->Vehicle->ignite();
343
	}
344
345
	public function testBadMethodCall() {
346
		$this->setExpectedException('PDOException');
347
		$this->Vehicle->isFoobar();
348
	}
349
350
	public function whenParked() {
351
		$this->assertEquals('parked', $this->Vehicle->getCurrentState());
352
	}
353
354
	public function testWhenMethods() {
355
		$this->Vehicle->whenStalled(function () {
356
			$this->assertEquals("stalled", $this->Vehicle->getCurrentState());
357
		});
358
359
		$this->Vehicle->when('parked', array($this, 'whenParked'));
360
361
		$this->Vehicle->ignite();
362
		$this->Vehicle->shiftUp();
363
		$this->Vehicle->crash();
364
		$this->Vehicle->repair();
365
	}
366
367
	public function testBubble() {
368
		$scope = $this;
369
		$this->Vehicle->on('ignite', 'before', function () use ($scope) {
370
			$scope->assertEquals("parked", $scope->Vehicle->getCurrentState());
371
		}, false);
372
373
		$this->Vehicle->on('transition', 'before', function () use ($scope) {
374
			// this should never be called
375
			$scope->assertTrue(false);
376
		});
377
378
		$this->Vehicle->ignite();
379
	}
380
381
	public function testInvalidTransition() {
382
		$this->assertFalse($this->Vehicle->getStates('foobar'));
383
		$this->assertFalse($this->Vehicle->getStates('baz'));
384
		$this->assertFalse($this->Vehicle->baz());
385
	}
386
387
	public function testVehicleTitle() {
388
		$this->Vehicle = new Vehicle(3);
389
390
		$this->assertEquals("Opel Astra", $this->Vehicle->field('title'));
391
		$this->assertEquals("idling", $this->Vehicle->getCurrentState());
392
		$this->Vehicle->shiftUp();
393
		$this->assertEquals("first_gear", $this->Vehicle->getCurrentState());
394
395
		$this->Vehicle = new Vehicle(4);
396
		$this->assertEquals("Nissan Leaf", $this->Vehicle->field('title'));
397
		$this->assertEquals("stalled", $this->Vehicle->getCurrentState());
398
		$this->assertTrue($this->Vehicle->canRepair());
399
		$this->assertTrue($this->Vehicle->repair());
400
		$this->assertEquals("parked", $this->Vehicle->getCurrentState());
401
	}
402
403
	public function testCreateVehicle() {
404
		$this->Vehicle->create();
405
		$this->Vehicle->save(array(
406
			'Vehicle' => array(
407
				'title' => 'Toybota'
408
			)
409
		));
410
		$this->Vehicle->id = $this->Vehicle->getLastInsertID();
411
		$this->assertEquals($this->Vehicle->initialState, $this->Vehicle->getCurrentState());
412
	}
413
414
	public function testAddToPrepareArrayNoRoles() {
415
		$this->Vehicle = new Vehicle(1);
416
417
		$dataArrayToFill = array();
418
		$dataToAdd = array(
419
			'stateFrom' => 'initial',
420
			'stateTo' => 'second',
421
			'transition' => 'launch'
422
		);
423
424
		// Case 1 - illegal parameters
425
		$this->assertFalse($this->Vehicle->addToPrepareArray(null, $dataArrayToFill));
426
		$this->assertFalse($this->Vehicle->addToPrepareArray(array(), $dataArrayToFill));
427
428
		// Case 2 - adding first array
429
		$expected[] = $dataToAdd;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$expected was never initialized. Although not strictly required by PHP, it is generally a good practice to add $expected = 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...
430
		$dataArrayToFill = $this->Vehicle->addToPrepareArray($dataToAdd, $dataArrayToFill);
431
		$this->assertEqual($expected, $dataArrayToFill);
432
433
		// Case 3 - adding second array
434
		$dataToAdd = array(
435
			'stateFrom' => 'second',
436
			'stateTo' => 'third',
437
			'transition' => 'launch2',
438
			'not_care' => 'anyvalue'
439
		);
440
		$expected[] = $dataToAdd;
441
		$dataArrayToFill = $this->Vehicle->addToPrepareArray($dataToAdd, $dataArrayToFill);
442
		$this->assertEqual($expected, $dataArrayToFill);
443
444
		// Case 4 - adding same array should not increase
445
		$dataArrayToFill = $this->Vehicle->addToPrepareArray($dataToAdd, $dataArrayToFill);
446
		$this->assertEqual($expected, $dataArrayToFill);
447
448
		// Case 5 - adding depends
449
		$dataToAdd = array(
450
			'stateFrom' => 'second',
451
			'stateTo' => 'third',
452
			'transition' => 'launch2',
453
			'depends' => 'hasValid'
454
		);
455
		$expected[] = $dataToAdd;
456
		$dataArrayToFill = $this->Vehicle->addToPrepareArray($dataToAdd, $dataArrayToFill);
457
		$this->assertEqual($expected, $dataArrayToFill);
458
459
		// Case 6 - adding same depends should skip it
460
		$dataArrayToFill = $this->Vehicle->addToPrepareArray($dataToAdd, $dataArrayToFill);
461
		$this->assertEqual($expected, $dataArrayToFill);
462
	}
463
464
	public function testAddToPrepareArrayWithRoles() {
465
		$this->Vehicle = new Vehicle(1);
466
		$expected = $dataArrayToFill = array();
467
468
		// Case 1 - adding role without depends, we reset the data
469
		$dataToAdd = array(
470
			'stateFrom' => 'initial',
471
			'stateTo' => 'second',
472
			'transition' => 'launch',
473
			'roles' => array('role1')
474
		);
475
		$dataArrayToFill[] = $expected[] = $dataToAdd;
476
		$dataArrayToFill = $this->Vehicle->addToPrepareArray($dataToAdd, $dataArrayToFill);
477
		//debug($dataArrayToFill);
478
		$this->assertEqual($expected, $dataArrayToFill);
479
480
		// Case 2 - adding same role twice shoud not duplicate
481
		$dataArrayToFill = $this->Vehicle->addToPrepareArray($dataToAdd, $dataArrayToFill);
482
		$this->assertEqual($expected, $dataArrayToFill);
483
484
		// Case 3 - Adding same state&Transition but with additional role
485
		$dataToAdd = array(
486
			'stateFrom' => 'initial',
487
			'stateTo' => 'second',
488
			'transition' => 'launch',
489
			'roles' => array('role2')
490
		);
491
		$expected = array();
492
		$expected[] = array(
493
			'stateFrom' => 'initial',
494
			'stateTo' => 'second',
495
			'transition' => 'launch',
496
			'roles' => array('role1', 'role2')
497
		);
498
		$dataArrayToFill = $this->Vehicle->addToPrepareArray($dataToAdd, $dataArrayToFill);
499
		$this->assertEqual($expected, $dataArrayToFill);
500
501
		// Case 4 - Adding same state&Transition but with additional role
502
		$dataToAdd = array(
503
			'stateFrom' => 'initial',
504
			'stateTo' => 'second',
505
			'transition' => 'launch',
506
			'roles' => array('role3', 'role4')
507
		);
508
		$expected = array();
509
		$expected[] = array(
510
			'stateFrom' => 'initial',
511
			'stateTo' => 'second',
512
			'transition' => 'launch',
513
			'roles' => array('role1', 'role2', 'role3', 'role4')
514
		);
515
		$dataArrayToFill = $this->Vehicle->addToPrepareArray($dataToAdd, $dataArrayToFill);
516
		$this->assertEqual($expected, $dataArrayToFill);
517
	}
518
519
	public function testAddToPrepareArrayWithDependsAndRoles() {
520
		$this->Vehicle = new Vehicle(1);
521
		$expected = $dataArrayToFill = array();
522
523
		// Case 1 - adding role without depends, we reset the data
524
		$dataToAdd = array(
525
			'stateFrom' => 'initial',
526
			'stateTo' => 'second',
527
			'transition' => 'launch',
528
			'depends' => 'is_allowed',
529
			'roles' => array('role1')
530
		);
531
		$dataArrayToFill[] = $expected[] = $dataToAdd;
532
		$dataArrayToFill = $this->Vehicle->addToPrepareArray($dataToAdd, $dataArrayToFill);
533
		//debug($dataArrayToFill);
534
		$this->assertEqual($expected, $dataArrayToFill);
535
536
		// Case 2 - adding same role twice shoud not duplicate
537
		$dataArrayToFill = $this->Vehicle->addToPrepareArray($dataToAdd, $dataArrayToFill);
538
		$this->assertEqual($expected, $dataArrayToFill);
539
540
		// Case 3 - Adding same state&Transition&depends but with additional role
541
		$dataToAdd = array(
542
			'stateFrom' => 'initial',
543
			'stateTo' => 'second',
544
			'transition' => 'launch',
545
			'depends' => 'is_allowed',
546
			'roles' => array('role2')
547
		);
548
		$expected = array();
549
		$expected[] = array(
550
			'stateFrom' => 'initial',
551
			'stateTo' => 'second',
552
			'transition' => 'launch',
553
			'depends' => 'is_allowed',
554
			'roles' => array('role1', 'role2')
555
		);
556
		$dataArrayToFill = $this->Vehicle->addToPrepareArray($dataToAdd, $dataArrayToFill);
557
		$this->assertEqual($expected, $dataArrayToFill);
558
559
		// Case 4 - Adding same state&Transition&depends but with additional role
560
		$dataToAdd = array(
561
			'stateFrom' => 'initial',
562
			'stateTo' => 'second',
563
			'transition' => 'launch',
564
			'depends' => 'is_allowed',
565
			'roles' => array('role3', 'role4')
566
		);
567
		$expected = array();
568
		$expected[] = array(
569
			'stateFrom' => 'initial',
570
			'stateTo' => 'second',
571
			'transition' => 'launch',
572
			'depends' => 'is_allowed',
573
			'roles' => array('role1', 'role2', 'role3', 'role4')
574
		);
575
		$dataArrayToFill = $this->Vehicle->addToPrepareArray($dataToAdd, $dataArrayToFill);
576
		$this->assertEqual($expected, $dataArrayToFill);
577
578
		// Case 5 - Adding same state&Transition&depends but with different depends
579
		$dataToAdd = array(
580
			'stateFrom' => 'initial',
581
			'stateTo' => 'second',
582
			'transition' => 'launch',
583
			'depends' => 'different_is_allowed',
584
			'roles' => array('role3', 'role4')
585
		);
586
		$expected[] = array(
587
			'stateFrom' => 'initial',
588
			'stateTo' => 'second',
589
			'transition' => 'launch',
590
			'depends' => 'different_is_allowed',
591
			'roles' => array('role3', 'role4')
592
		);
593
		$dataArrayToFill = $this->Vehicle->addToPrepareArray($dataToAdd, $dataArrayToFill);
594
		$this->assertEqual($expected, $dataArrayToFill);
595
	}
596
597
	public function testToDot() {
598
		$this->Vehicle->toDot();
599
	}
600
601
	public function testCreateDotFileForRoles() {
602
		$this->Vehicle = new RulesVehicle(1);
603
604
		$expected = <<<EOT
605
digraph finite_state_machine {
606
	fontsize=12;
607
	node [shape = oval, style=filled, color = "lightgrey"];
608
	style=filled;
609
	label="Statemachine for RulesVehicle role(s) : Driver, Thief"
610
	"Parked" [ color = green ];
611
	"Parked" -> "Idling" [ style = bold, fontsize = 9, arrowType = normal, label = "Ignite by (Driver)
612
if Has Key" color = "blue"];
613
	"Stalled" -> "Stalled" [ style = bold, fontsize = 9, arrowType = normal, label = "Ignite by (Driver)
614
if Has Key" color = "blue"];
615
	"Idling" -> "Parked" [ style = bold, fontsize = 9, arrowType = normal, label = "Park by All
616
if Available Parking" ];
617
	"First Gear" -> "Parked" [ style = bold, fontsize = 9, arrowType = normal, label = "Park by All
618
if Available Parking" ];
619
	"Idling" -> "First Gear" [ style = bold, fontsize = 9, arrowType = normal, label = "Shift Up by All" ];
620
	"First Gear" -> "Second Gear" [ style = bold, fontsize = 9, arrowType = normal, label = "Shift Up by All" ];
621
	"Second Gear" -> "Third Gear" [ style = bold, fontsize = 9, arrowType = normal, label = "Shift Up by All" ];
622
	"First Gear" -> "Idling" [ style = bold, fontsize = 9, arrowType = normal, label = "Shift Down by All" ];
623
	"Second Gear" -> "First Gear" [ style = bold, fontsize = 9, arrowType = normal, label = "Shift Down by All" ];
624
	"Third Gear" -> "Second Gear" [ style = bold, fontsize = 9, arrowType = normal, label = "Shift Down by All" ];
625
	"First Gear" -> "Stalled" [ style = bold, fontsize = 9, arrowType = normal, label = "Crash by All" ];
626
	"Second Gear" -> "Stalled" [ style = bold, fontsize = 9, arrowType = normal, label = "Crash by All" ];
627
	"Third Gear" -> "Stalled" [ style = bold, fontsize = 9, arrowType = normal, label = "Crash by All" ];
628
	"First Gear" -> "Idling" [ style = bold, fontsize = 9, arrowType = normal, label = "Idle by All" ];
629
	"All" -> "Parked" [ style = bold, fontsize = 9, arrowType = normal, label = "Turn Off by All" ];
630
	"Parked" -> "Idling" [ style = bold, fontsize = 9, arrowType = normal, label = "Hardwire by (Thief)" color = "red"];
631
	"Stalled" -> "Stalled" [ style = bold, fontsize = 9, arrowType = normal, label = "Hardwire by (Thief)" color = "red"];
632
}
633
634
EOT;
635
		$this->assertEqual($expected, $this->Vehicle->createDotFileForRoles(array(
636
				'driver' => array(
637
					'color' => 'blue'),
638
				'thief' => array(
639
					'color' => 'red')
640
			), array(
641
				'color' => 'lightgrey',
642
				'activeColor' => 'green'
643
			)
644
		));
645
	}
646
647
	public function testCallable() {
648
		$this->Vehicle->addMethod('whatIsMyName', function (Model $model, $method, $name) {
649
			return $model->alias . '-' . $method . '-' . $name;
650
		});
651
652
		$this->assertEquals("Vehicle-whatIsMyName-Toybota", $this->Vehicle->whatIsMyName("Toybota"));
653
	}
654
655
	public function testExistingCallable() {
656
		$this->Vehicle->addMethod('foobar', function () {
657
		});
658
659
		$this->setExpectedException('InvalidArgumentException');
660
		$this->Vehicle->addMethod('foobar', function () {
661
		});
662
	}
663
664
	public function testUnhandled() {
665
		$this->setExpectedException('PDOException');
666
		$this->assertEquals(array("unhandled"), $this->Vehicle->handleMethodCall("foobar"));
667
	}
668
669
	public function testInvalidOnStateChange() {
670
		$this->Vehicle = new BaseVehicle(1);
671
		$this->Vehicle->ignite();
672
	}
673
674
	public function testOnStateChange() {
675
		$this->Vehicle = $this->getMock('Vehicle', array(
676
			'onStateChange', 'onStateIdling', 'onBeforeTransition', 'onAfterTransition'));
677
		$this->Vehicle->id = 1;
678
		$this->Vehicle->expects($this->once())->method('onBeforeTransition');
679
		$this->Vehicle->expects($this->once())->method('onAfterTransition');
680
		$this->Vehicle->expects($this->once())->method('onStateChange');
681
		$this->Vehicle->expects($this->once())->method('onStateIdling');
682
683
		$this->assertTrue($this->Vehicle->ignite());
684
	}
685
686
	public function testRules() {
687
		$this->Vehicle = new RulesVehicle(1);
688
689
		$this->assertTrue($this->Vehicle->canIgnite(null, 'driver'));
690
		$this->assertFalse($this->Vehicle->canIgnite(null, 'thief'));
691
		$this->assertTrue($this->Vehicle->canHardwire(null, 'thief'));
692
		$this->assertFalse($this->Vehicle->canHardwire(null, 'driver'));
693
694
		$this->Vehicle->ignite(null, 'driver');
695
696
		$this->Vehicle->ignite(1, 'driver');
697
		$this->assertEquals("idling", $this->Vehicle->getCurrentState(1));
698
		$this->assertEquals("parked", $this->Vehicle->getPreviousState(1));
699
		$this->assertEquals("ignite", $this->Vehicle->getLastTransition(1));
700
		$this->assertEquals("driver", $this->Vehicle->getLastRole(1));
701
702
		$this->assertFalse($this->Vehicle->canPark(null, 'driver'));
703
		$this->assertTrue($this->Vehicle->canPark(null, 'thief'));
704
	}
705
706
	public function testRulesWithCanTransitionById() {
707
		$this->Vehicle = new RulesVehicle(1);
708
709
		$this->assertTrue($this->Vehicle->canIgnite(1, 'driver'));
710
		$this->assertFalse($this->Vehicle->canIgnite(1, 'thief'));
711
		$this->assertTrue($this->Vehicle->canHardwire(1, 'thief'));
712
		$this->assertFalse($this->Vehicle->canHardwire(1, 'driver'));
713
714
		$this->Vehicle->ignite(null, 'driver');
715
716
		$this->Vehicle->ignite(1, 'driver');
717
		$this->assertEquals("idling", $this->Vehicle->getCurrentState(1));
718
		$this->assertEquals("parked", $this->Vehicle->getPreviousState(1));
719
		$this->assertEquals("ignite", $this->Vehicle->getLastTransition(1));
720
		$this->assertEquals("driver", $this->Vehicle->getLastRole(1));
721
722
		$this->assertFalse($this->Vehicle->canPark(1, 'driver'));
723
		$this->assertTrue($this->Vehicle->canPark(1, 'thief'));
724
	}
725
726
	public function testRuleWithCallback() {
727
		$this->Vehicle = new RulesVehicle(1);
728
		$this->Vehicle->ignite(null, 'driver');
729
		$this->Vehicle->shiftUp();
730
		$this->Vehicle->crash();
731
732
		$this->Vehicle->addMethod('hasTools', function ($role) {
733
			return $role == 'mechanic';
734
		});
735
736
		$this->assertTrue($this->Vehicle->canRepair(null, 'mechanic'));
737
		$this->assertTrue($this->Vehicle->repair(null, 'mechanic'));
738
	}
739
740
	public function testInvalidRules() {
741
		$this->setExpectedException('InvalidArgumentException');
742
743
		$this->Vehicle = new RulesVehicle(1);
744
		$this->Vehicle->ignite();
745
	}
746
747
	public function testWrongRole() {
748
		$this->Vehicle = new RulesVehicle(1);
749
		$this->assertFalse($this->Vehicle->ignite(null, 'thief'));
750
	}
751
752
	public function tearDown() {
753
		parent::tearDown();
754
		unset($this->Vehicle, $this->StateMachine);
755
	}
756
}
757