1
|
|
|
<?php |
|
|
|
|
2
|
|
|
/** |
3
|
|
|
* StateMachineBehavior |
4
|
|
|
* |
5
|
|
|
* A finite state machine is a machine that cannot move between states unless |
6
|
|
|
* a specific transition fired. It has a specified amount of legal directions it can |
7
|
|
|
* take from each state. It also supports state listeners and transition listeners. |
8
|
|
|
* |
9
|
|
|
* @author David Steinsland |
10
|
|
|
*/ |
11
|
|
|
App::uses('Model', 'Model'); |
12
|
|
|
App::uses('Inflector', 'Utility'); |
13
|
|
|
|
14
|
|
|
class StateMachineBehavior extends ModelBehavior { |
|
|
|
|
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* Allows us to support writing both: is('parked') and isParked() |
18
|
|
|
* |
19
|
|
|
* @var array |
20
|
|
|
*/ |
21
|
|
|
public $mapMethods = array( |
22
|
|
|
'/when([A-Z][a-zA-Z0-9]+)/' => 'when', |
23
|
|
|
'/on([A-Z][a-zA-Z0-9]+)/' => 'on' |
24
|
|
|
); |
25
|
|
|
|
26
|
|
|
protected $_defaultSettings = array( |
27
|
|
|
'transition_listeners' => array( |
28
|
|
|
'transition' => array( |
29
|
|
|
'before' => array(), |
30
|
|
|
'after' => array() |
31
|
|
|
) |
32
|
|
|
), |
33
|
|
|
'state_listeners' => array(), |
34
|
|
|
'methods' => array() |
35
|
|
|
); |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* Array of all configured states. Initialized by self::setup() |
39
|
|
|
* @var array |
40
|
|
|
*/ |
41
|
|
|
protected $_availableStates = array(); |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* Adds a available state |
45
|
|
|
* |
46
|
|
|
* @param string $state The state to be added. |
47
|
|
|
* @return void |
48
|
|
|
*/ |
49
|
|
|
protected function _addAvailableState($state) { |
50
|
|
|
if ($state != 'All' && !in_array($state, $this->_availableStates)) { |
51
|
|
|
$this->_availableStates[] = Inflector::camelize($state); |
52
|
|
|
} |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* Sets up all the methods that builds up the state machine. |
57
|
|
|
* StateMachine->is<State> i.e. StateMachine->isParked() |
58
|
|
|
* StateMachine->can<Transition> i.e. StateMachine->canShiftGear() |
59
|
|
|
* StateMachine-><transition> i.e. StateMachine->shiftGear(); |
60
|
|
|
* |
61
|
|
|
* @param Model $model The model being used |
62
|
|
|
* @param array $config Configuration for the Behavior |
63
|
|
|
* @return void |
64
|
|
|
*/ |
65
|
|
|
public function setup(Model $model, $config = array()) { |
66
|
|
|
if (!isset($this->settings[$model->alias])) { |
67
|
|
|
$this->settings[$model->alias] = $this->_defaultSettings; |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
foreach ($model->transitions as $transition => $states) { |
71
|
|
|
foreach ($states as $stateFrom => $stateTo) { |
72
|
|
|
$this->_addAvailableState(Inflector::camelize($stateFrom)); |
73
|
|
|
$this->_addAvailableState(Inflector::camelize($stateTo)); |
74
|
|
|
foreach (array( |
75
|
|
|
'is' . Inflector::camelize($stateFrom), |
76
|
|
|
'is' . Inflector::camelize($stateTo) |
77
|
|
|
) as $methodName) { |
78
|
|
|
if (!$this->_hasMethod($model, $methodName)) { |
79
|
|
|
$this->mapMethods['/' . $methodName . '$/'] = 'is'; |
80
|
|
|
} |
81
|
|
|
} |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
$this->mapMethods['/^can' . Inflector::camelize($transition) . '$/'] = 'can'; |
85
|
|
|
|
86
|
|
|
$transitionFunction = Inflector::variable($transition); |
87
|
|
|
$this->mapMethods['/^' . $transitionFunction . '$/'] = 'transition'; |
88
|
|
|
} |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* Adds a user defined callback |
93
|
|
|
* {{{ |
94
|
|
|
* $this->Vehicle->addMethod('myMethod', function() {}); |
95
|
|
|
* $data = $this->Vehicle->myMethod(); |
96
|
|
|
* }}} |
97
|
|
|
* |
98
|
|
|
* @param Model $model The model being acted on |
99
|
|
|
* @param string $method The method na,e |
100
|
|
|
* @param string $cb The callback to execute |
101
|
|
|
* @throws InvalidArgumentException If the method already is registered |
102
|
|
|
* @return void |
103
|
|
|
*/ |
104
|
|
|
public function addMethod(Model $model, $method, $cb) { |
105
|
|
|
if ($this->_hasMethod($model, $method)) { |
106
|
|
|
throw new InvalidArgumentException("A method with the same name is already registered"); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
$this->settings[$model->alias]['methods'][$method] = $cb; |
110
|
|
|
$this->mapMethods['/' . $method . '/'] = 'handleMethodCall'; |
111
|
|
|
|
112
|
|
|
// force model to re-load Behavior, so that the mapMethods are working correctly |
113
|
|
|
$model->Behaviors->load('Statemachine.StateMachine'); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* Handles user defined method calls, which are implemented using closures. |
118
|
|
|
* |
119
|
|
|
* @param Model $model The model being acted on |
120
|
|
|
* @param string $method The method name |
121
|
|
|
* @return mixed The return value of the callback, or an array if the method doesn't exist |
122
|
|
|
*/ |
123
|
|
|
public function handleMethodCall(Model $model, $method) { |
124
|
|
|
if (!isset($this->settings[$model->alias]['methods'][$method])) { |
125
|
|
|
return array('unhandled'); |
126
|
|
|
} |
127
|
|
|
return call_user_func_array($this->settings[$model->alias]['methods'][$method], func_get_args()); |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* Updates the model's state when a $model->save() call is performed |
132
|
|
|
* |
133
|
|
|
* @param Model $model The model being acted on |
134
|
|
|
* @param bool $created Whether or not the model was created |
135
|
|
|
* @param array $options Options passed to save |
136
|
|
|
* @return bool |
137
|
|
|
*/ |
138
|
|
|
public function afterSave(Model $model, $created, $options = array()) { |
139
|
|
|
if ($created) { |
140
|
|
|
$model->saveField('state', $model->initialState); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
return true; |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* returns all transitions defined in model |
148
|
|
|
* |
149
|
|
|
* @param Model $model The model being acted on |
150
|
|
|
* @return array array of transitions |
151
|
|
|
* @author Frode Marton Meling |
152
|
|
|
*/ |
153
|
|
|
public function getAllTransitions($model) { |
154
|
|
|
$transitionArray = array(); |
155
|
|
|
foreach ($model->transitions as $transition => $data) { |
156
|
|
|
$transitionArray[] = $transition; |
157
|
|
|
} |
158
|
|
|
return $transitionArray; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* Returns an array of all configured states |
163
|
|
|
* |
164
|
|
|
* @return array |
165
|
|
|
*/ |
166
|
|
|
public function getAvailableStates() { |
167
|
|
|
return $this->_availableStates; |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
/** |
171
|
|
|
* checks if $state or Array of states are valid ones |
172
|
|
|
* |
173
|
|
|
* @param string|array $state a string representation of state or a array of states |
174
|
|
|
* @return bool |
175
|
|
|
* @author Frode Marton Meling |
176
|
|
|
*/ |
177
|
|
|
protected function _validState($state) { |
178
|
|
|
$availableStatesIncludingAll = array_merge(array('All'), $this->_availableStates); |
179
|
|
|
if (!is_array($state)) { |
180
|
|
|
return in_array(Inflector::camelize($state), $availableStatesIncludingAll); |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
foreach ($state as $singleState) { |
184
|
|
|
if (!in_array(Inflector::camelize($singleState), $availableStatesIncludingAll)) { |
185
|
|
|
return false; |
186
|
|
|
} |
187
|
|
|
} |
188
|
|
|
return true; |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
/** |
192
|
|
|
* Finds all records in a specific state. Supports additional conditions, but will overwrite conditions with state |
193
|
|
|
* |
194
|
|
|
* @param Model $model The model being acted on |
195
|
|
|
* @param string $type find type (ref. CakeModel) |
196
|
|
|
* @param array|string $state The state to find. this will be checked for validity. |
197
|
|
|
* @param array $params Regular $params array for CakeModel->find |
198
|
|
|
* @return array Returns datarray of $model records or false. Will return false if state is not set, or state is not configured in model |
199
|
|
|
* @author Frode Marton Meling |
200
|
|
|
*/ |
201
|
|
|
protected function _findByState(Model $model, $type, $state = null, $params = array()) { |
202
|
|
|
if ($state === null || !$this->_validState($state)) { |
203
|
|
|
return false; |
|
|
|
|
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
if (is_array($state) || Inflector::camelize($state) != 'All') { |
207
|
|
|
$params['conditions']["{$model->alias}.state"] = $state; |
208
|
|
|
} |
209
|
|
|
return $model->find($type, $params); |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
/** |
213
|
|
|
* This function will add all availble (runnable) transitions on a model and add it to the dataArray given to the function. |
214
|
|
|
* |
215
|
|
|
* @param Model $model The model being acted on |
216
|
|
|
* @param array $modelRows The model dataArray. this is an array of Models returned from a model->find. |
217
|
|
|
* @param string $role if specified, the function will limit the transitions based on a role |
218
|
|
|
* @return array Returns datarray of $model with the available transitions inserted |
219
|
|
|
* @author Frode Marton Meling |
220
|
|
|
*/ |
221
|
|
|
protected function _addTransitionsToArray($model, $modelRows, $role) { |
222
|
|
|
if (!isset($modelRows) || $modelRows == false) { |
223
|
|
|
return $modelRows; |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
$allTransitions = $this->getAllTransitions($model); |
227
|
|
|
foreach ($modelRows as $key => $modelRow) { |
228
|
|
|
$model->id = $modelRow[$model->alias]['id']; |
229
|
|
|
// Note! We need this empty array if no transitions are availble. then we do not need to test if array exist in views. |
230
|
|
|
$modelRows[$key][$model->alias]['Transitions'] = array(); |
231
|
|
|
foreach ($allTransitions as $transition) { |
232
|
|
|
if ($model->can($transition, $model->id, $role)) { |
233
|
|
|
$modelRows[$key][$model->alias]['Transitions'][] = $transition; |
234
|
|
|
} |
235
|
|
|
} |
236
|
|
|
} |
237
|
|
|
return $modelRows; |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
/** |
241
|
|
|
* Finds all records in a specific state. Supports additional conditions, but will overwrite conditions with state |
242
|
|
|
* |
243
|
|
|
* @param Model $model The model being acted on |
244
|
|
|
* @param array|string $state The state to find. this will be checked for validity. |
245
|
|
|
* @param array $params Regular $params array for CakeModel->find |
246
|
|
|
* @param bool $withTransitions Whether or not to add available transitions to records, in its current state |
247
|
|
|
* @param string $role The rule executing the transition |
248
|
|
|
* @return array Returns datarray of $model records or false. Will return false if state is not set, or state is not configured in model |
249
|
|
|
* @author Frode Marton Meling |
250
|
|
|
*/ |
251
|
|
|
public function findAllByState(Model $model, $state = null, $params = array(), $withTransitions = true, $role = null) { |
252
|
|
|
$modelRows = $this->_findByState($model, 'all', $state, $params); |
253
|
|
|
return ($withTransitions) ? $this->_addTransitionsToArray($model, $modelRows, $role) : $modelRows; |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
/** |
257
|
|
|
* Finds first record in a specific state. Supports additional conditions, but will overwrite conditions with state |
258
|
|
|
* |
259
|
|
|
* @param Model $model The model being acted on |
260
|
|
|
* @param array|string $state The state to find. this will be checked for validity. |
261
|
|
|
* @param array $params Regular $params array for CakeModel->find |
262
|
|
|
* @param bool $withTransitions Whether or not to add available transitions to records, in its current state |
263
|
|
|
* @param string $role The rule executing the transition |
264
|
|
|
* @return array Returns datarray of $model records or false. Will return false if state is not set, or state is not configured in model |
265
|
|
|
* @author Frode Marton Meling |
266
|
|
|
*/ |
267
|
|
|
public function findFirstByState(Model $model, $state = null, $params = array(), $withTransitions = true, $role = null) { |
268
|
|
|
$modelRow = $this->_findByState($model, 'first', $state, $params); |
269
|
|
|
return ($withTransitions) ? $this->_addTransitionsToArray($model, ($modelRow) ? array($modelRow) : false, $role) : $modelRow; |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
/** |
273
|
|
|
* Finds count of records in a specific state. Supports additional conditions, but will overwrite conditions with state |
274
|
|
|
* |
275
|
|
|
* @param Model $model The model being acted on |
276
|
|
|
* @param array|string $state The state to find. this will be checked for validity. |
277
|
|
|
* @param array $params Regular $params array for CakeModel->find |
278
|
|
|
* @return array Returns datarray of $model records or false. Will return false if state is not set, or state is not configured in model |
279
|
|
|
* @author Frode Marton Meling |
280
|
|
|
*/ |
281
|
|
|
public function findCountByState(Model $model, $state = null, $params = array()) { |
282
|
|
|
return $this->_findByState($model, 'count', $state, $params); |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
/** |
286
|
|
|
* Allows moving from one state to another. |
287
|
|
|
* {{{ |
288
|
|
|
* $this->Model->transition('shift_gear'); |
289
|
|
|
* // or |
290
|
|
|
* $this->Model->shiftGear(); |
291
|
|
|
* }}} |
292
|
|
|
* |
293
|
|
|
* @param Model $model The model being acted on |
294
|
|
|
* @param string $transition The transition being initiated |
295
|
|
|
* @param int $id table id field to find object |
296
|
|
|
* @param string $role The rule executing the transition |
297
|
|
|
* @param bool $validate whether or not validation being checked |
298
|
|
|
* @return bool Returns true if the transition be executed, otherwise false |
299
|
|
|
*/ |
300
|
|
|
public function transition(Model $model, $transition, $id = null, $role = null, $validate = true) { |
301
|
|
|
if ($id === null) { |
302
|
|
|
$id = $model->getID(); |
303
|
|
|
} |
304
|
|
|
if ($id === false) { |
305
|
|
|
return false; |
306
|
|
|
} |
307
|
|
|
$model->id = $id; |
308
|
|
|
$transition = Inflector::underscore($transition); |
309
|
|
|
$state = $this->getStates($model, $transition); |
310
|
|
|
if (!$state || $this->_checkRoleAgainstRule($model, $role, $transition) === false) { |
311
|
|
|
return false; |
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
$this->_callTransitionListeners($model, $transition, 'before'); |
315
|
|
|
|
316
|
|
|
$model->read(null, $model->id); |
317
|
|
|
$model->set('previous_state', $model->getCurrentState()); |
318
|
|
|
$model->set('last_transition', $transition); |
319
|
|
|
$model->set('last_role', $role); |
320
|
|
|
$model->set('state', $state); |
321
|
|
|
$retval = $model->save(null, $validate); |
322
|
|
|
|
323
|
|
|
if ($retval) { |
324
|
|
|
$this->_callTransitionListeners($model, $transition, 'after'); |
325
|
|
|
|
326
|
|
|
$stateListeners = array(); |
327
|
|
|
if (isset($this->settings[$model->alias]['state_listeners'][$state])) { |
328
|
|
|
$stateListeners = $this->settings[$model->alias]['state_listeners'][$state]; |
329
|
|
|
} |
330
|
|
|
|
331
|
|
|
foreach (array( |
332
|
|
|
'onState' . Inflector::camelize($state), |
333
|
|
|
'onStateChange' |
334
|
|
|
) as $method) { |
335
|
|
|
if (method_exists($model, $method)) { |
336
|
|
|
$stateListeners[] = array($model, $method); |
337
|
|
|
} |
338
|
|
|
} |
339
|
|
|
|
340
|
|
|
foreach ($stateListeners as $cb) { |
341
|
|
|
call_user_func($cb, $state); |
342
|
|
|
} |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
return (bool)$retval; |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
/** |
349
|
|
|
* Checks whether the state machine is in the given state |
350
|
|
|
* |
351
|
|
|
* @param Model $model The model being acted on |
352
|
|
|
* @param string $state The state being checked |
353
|
|
|
* @param int $id The id of the item to check |
354
|
|
|
* @return bool whether or not the state machine is in the given state |
355
|
|
|
* @throws BadMethodCallException when method does not exists |
356
|
|
|
*/ |
357
|
|
|
public function is(Model $model, $state, $id = null) { |
358
|
|
|
if ($id === null) { |
359
|
|
|
$id = $model->getID(); |
360
|
|
|
} |
361
|
|
|
if ($id === false) { |
362
|
|
|
return false; |
363
|
|
|
} |
364
|
|
|
$model->id = $id; |
365
|
|
|
return $this->getCurrentState($model) === $this->_deFormalizeMethodName($state); |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
/** |
369
|
|
|
* Checks whether or not the machine is able to perform transition, in its current state |
370
|
|
|
* |
371
|
|
|
* @param Model $model The model being acted on |
372
|
|
|
* @param string $transition The transition being checked |
373
|
|
|
* @param int $id The id of the item to check |
374
|
|
|
* @param string $role The role which should execute the transition |
375
|
|
|
* @return bool whether or not the machine can perform the transition |
376
|
|
|
* @throws BadMethodCallException when method does not exists |
377
|
|
|
*/ |
378
|
|
|
public function can(Model $model, $transition, $id = null, $role = null) { |
379
|
|
|
if ($id === null) { |
380
|
|
|
$id = $model->getID(); |
381
|
|
|
} |
382
|
|
|
if ($id === false) { |
383
|
|
|
return false; |
384
|
|
|
} |
385
|
|
|
$model->id = $id; |
386
|
|
|
$transition = $this->_deFormalizeMethodName($transition); |
387
|
|
|
if (!$this->getStates($model, $transition) || $this->_checkRoleAgainstRule($model, $role, $transition) === false) { |
388
|
|
|
return false; |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
return true; |
392
|
|
|
} |
393
|
|
|
|
394
|
|
|
/** |
395
|
|
|
* Registers a callback function to be called when the machine leaves one state. |
396
|
|
|
* The callback is fired either before or after the given transition. |
397
|
|
|
* |
398
|
|
|
* @param Model $model The model being acted on |
399
|
|
|
* @param string $transition The transition to listen to |
400
|
|
|
* @param string $triggerType Either before or after |
401
|
|
|
* @param string $cb The callback function that will be called |
402
|
|
|
* @param bool $bubble Whether or not to bubble other listeners |
403
|
|
|
* @return void |
404
|
|
|
*/ |
405
|
|
|
public function on(Model $model, $transition, $triggerType, $cb, $bubble = true) { |
406
|
|
|
$this->settings[$model->alias]['transition_listeners'][Inflector::underscore($transition)][$triggerType][] = array( |
407
|
|
|
'cb' => $cb, |
408
|
|
|
'bubble' => $bubble |
409
|
|
|
); |
410
|
|
|
} |
411
|
|
|
|
412
|
|
|
/** |
413
|
|
|
* Registers a callback that will be called when the state machine enters the given |
414
|
|
|
* state. |
415
|
|
|
* |
416
|
|
|
* @param Model $model The model being acted on |
417
|
|
|
* @param string $state The state which the machine should enter |
418
|
|
|
* @param string $cb The callback function that will be called |
419
|
|
|
* @return void |
420
|
|
|
*/ |
421
|
|
|
public function when(Model $model, $state, $cb) { |
422
|
|
|
$this->settings[$model->alias]['state_listeners'][Inflector::underscore($state)][] = $cb; |
423
|
|
|
} |
424
|
|
|
|
425
|
|
|
/** |
426
|
|
|
* Returns the states the machine would be in, after the given transition |
427
|
|
|
* |
428
|
|
|
* @param Model $model The model being acted on |
429
|
|
|
* @param string $transition The transition name |
430
|
|
|
* @return mixed False if the transition doesnt yield any states, or an array of states |
431
|
|
|
*/ |
432
|
|
|
public function getStates(Model $model, $transition) { |
433
|
|
|
if (!isset($model->transitions[$transition])) { |
434
|
|
|
// transition doesn't exist |
435
|
|
|
return false; |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
// get the states the machine can move from and to |
439
|
|
|
$states = $model->transitions[$transition]; |
440
|
|
|
$currentState = $model->getCurrentState(); |
441
|
|
|
|
442
|
|
|
if (isset($states[$currentState])) { |
443
|
|
|
return $states[$currentState]; |
444
|
|
|
} |
445
|
|
|
|
446
|
|
|
if (isset($states['all'])) { |
447
|
|
|
return $states['all']; |
448
|
|
|
} |
449
|
|
|
|
450
|
|
|
return false; |
451
|
|
|
} |
452
|
|
|
|
453
|
|
|
/** |
454
|
|
|
* Returns the current state of the machine |
455
|
|
|
* |
456
|
|
|
* @param Model $model The model being acted on |
457
|
|
|
* @param int $id The id of the item to check |
458
|
|
|
* @return string The current state of the machine |
459
|
|
|
*/ |
460
|
|
|
public function getCurrentState(Model $model, $id = null) { |
461
|
|
|
if ($id === null) { |
462
|
|
|
$id = $model->getID(); |
463
|
|
|
} |
464
|
|
|
if ($id === false) { |
465
|
|
|
return false; |
|
|
|
|
466
|
|
|
} |
467
|
|
|
$model->id = $id; |
468
|
|
|
return (($model->field('state') != null)) ? $model->field('state') : $model->initialState; |
469
|
|
|
} |
470
|
|
|
|
471
|
|
|
/** |
472
|
|
|
* Returns the previous state of the machine |
473
|
|
|
* |
474
|
|
|
* @param Model $model The model being acted on |
475
|
|
|
* @param int $id The id of the item to check |
476
|
|
|
* @return string The previous state of the machine |
477
|
|
|
*/ |
478
|
|
View Code Duplication |
public function getPreviousState(Model $model, $id = null) { |
|
|
|
|
479
|
|
|
if ($id === null) { |
480
|
|
|
$id = $model->getID(); |
481
|
|
|
} |
482
|
|
|
if ($id === false) { |
483
|
|
|
return false; |
|
|
|
|
484
|
|
|
} |
485
|
|
|
$model->id = $id; |
486
|
|
|
return $model->field('previous_state'); |
487
|
|
|
} |
488
|
|
|
|
489
|
|
|
/** |
490
|
|
|
* Returns the last transition ran |
491
|
|
|
* |
492
|
|
|
* @param Model $model The model being acted on |
493
|
|
|
* @param int $id The id of the item to check |
494
|
|
|
* @return string The transition last ran of the machine |
495
|
|
|
*/ |
496
|
|
View Code Duplication |
public function getLastTransition(Model $model, $id = null) { |
|
|
|
|
497
|
|
|
if ($id === null) { |
498
|
|
|
$id = $model->getID(); |
499
|
|
|
} |
500
|
|
|
if ($id === false) { |
501
|
|
|
return false; |
|
|
|
|
502
|
|
|
} |
503
|
|
|
$model->id = $id; |
504
|
|
|
return $model->field('last_transition'); |
505
|
|
|
} |
506
|
|
|
|
507
|
|
|
/** |
508
|
|
|
* Returns the role that ran the last transition |
509
|
|
|
* |
510
|
|
|
* @param Model $model The model being acted on |
511
|
|
|
* @param int $id The id of the item to check |
512
|
|
|
* @return string The role that last ran a transition of the machine |
513
|
|
|
*/ |
514
|
|
View Code Duplication |
public function getLastRole(Model $model, $id = null) { |
|
|
|
|
515
|
|
|
if ($id === null) { |
516
|
|
|
$id = $model->getID(); |
517
|
|
|
} |
518
|
|
|
if ($id === false) { |
519
|
|
|
return false; |
|
|
|
|
520
|
|
|
} |
521
|
|
|
$model->id = $id; |
522
|
|
|
return $model->field('last_role'); |
523
|
|
|
} |
524
|
|
|
|
525
|
|
|
/** |
526
|
|
|
* Simple method to return contents for a GV file, that |
527
|
|
|
* can be made into graphics by: |
528
|
|
|
* {{{ |
529
|
|
|
* dot -Tpng -ofsm.png fsm.gv |
530
|
|
|
* }}} |
531
|
|
|
* Assuming that the contents are written to the file fsm.gv |
532
|
|
|
* |
533
|
|
|
* @param Model $model The model being acted on |
534
|
|
|
* @return string The contents of the graphviz file |
535
|
|
|
*/ |
536
|
|
|
public function toDot(Model $model) { |
537
|
|
|
$digraph = <<<EOT |
538
|
|
|
digraph finite_state_machine { |
539
|
|
|
rankdir=LR |
540
|
|
|
fontsize=12 |
541
|
|
|
node [shape = circle]; |
542
|
|
|
|
543
|
|
|
EOT; |
544
|
|
|
|
545
|
|
|
foreach ($model->transitions as $transition => $states) { |
546
|
|
|
foreach ($states as $stateFrom => $stateTo) { |
547
|
|
|
$digraph .= sprintf("\t%s -> %s [ label = \"%s\" ];\n", $stateFrom, $stateTo, $transition); |
548
|
|
|
} |
549
|
|
|
} |
550
|
|
|
|
551
|
|
|
return $digraph . "}"; |
552
|
|
|
} |
553
|
|
|
|
554
|
|
|
/** |
555
|
|
|
* This method prepares an array for each transition in the statemachine making it easier to iterate throug the machine for |
556
|
|
|
* output to various formats |
557
|
|
|
* |
558
|
|
|
* @param Model $model The model being acted on |
559
|
|
|
* @param array $roles The role(s) executing the transition change. with an options array. |
560
|
|
|
* 'role' => array('color' => color of the arrows) |
561
|
|
|
* In the future many more Graphviz options can be added |
562
|
|
|
* @return array returns an array of all transitions |
563
|
|
|
* @author Frode Marton Meling <[email protected]> |
564
|
|
|
*/ |
565
|
|
|
public function prepareForDotWithRoles(Model $model, $roles) { |
566
|
|
|
$preparedForDotArray = array(); |
567
|
|
|
foreach ($model->transitions as $transition => $states) { |
568
|
|
|
foreach ($roles as $role => $options) { |
569
|
|
|
foreach ($states as $stateFrom => $stateTo) { |
570
|
|
|
// if roles are not defined in transitionRules we add or if roles are defined, at least one needs to be present |
571
|
|
|
if (!isset($model->transitionRules[$transition]['role']) || (isset($model->transitionRules[$transition]['role']) && $this->_containsAnyRoles($model->transitionRules[$transition]['role'], $roles))) { |
572
|
|
|
$dataToPrepare = array( |
573
|
|
|
'stateFrom' => $stateFrom, |
574
|
|
|
'stateTo' => $stateTo, |
575
|
|
|
'transition' => $transition |
576
|
|
|
); |
577
|
|
|
if (isset($model->transitionRules[$transition]['role'])) { |
578
|
|
|
if (in_array($role, $model->transitionRules[$transition]['role'])) { |
579
|
|
|
$dataToPrepare['roles'] = array($role); |
580
|
|
|
} |
581
|
|
|
} |
582
|
|
|
if (isset($model->transitionRules[$transition]['depends'])) { |
583
|
|
|
$dataToPrepare['depends'] = $model->transitionRules[$transition]['depends']; |
584
|
|
|
} |
585
|
|
|
// we do not add if role is given as transitionRule, but part is not in it. |
586
|
|
|
$preparedForDotArray = $this->addToPrepareArray($model, $dataToPrepare, $preparedForDotArray); |
587
|
|
|
} |
588
|
|
|
} |
589
|
|
|
} |
590
|
|
|
} |
591
|
|
|
return $preparedForDotArray; |
592
|
|
|
} |
593
|
|
|
|
594
|
|
|
/** |
595
|
|
|
* Method to return contents for a GV file based on array of roles. That means you can send |
596
|
|
|
* an array of roles (with options) and this method will calculate the presentation that |
597
|
|
|
* can be made into graphics by: |
598
|
|
|
* {{{ |
599
|
|
|
* dot -Tpng -ofsm.png fsm.gv |
600
|
|
|
* }}} |
601
|
|
|
* Assuming that the contents are written to the file fsm.gv |
602
|
|
|
* |
603
|
|
|
* @param Model $model The model being acted on |
604
|
|
|
* @param array $roles The role(s) executing the transition change. with an options array. |
605
|
|
|
* 'role' => array('color' => color of the arrows) |
606
|
|
|
* In the future many more Graphviz options can be added |
607
|
|
|
* @param array $dotOptions Options for nodes |
608
|
|
|
* 'color' => 'color of all nodes' |
609
|
|
|
* 'activeColor' => 'the color you want the active node to have' |
610
|
|
|
* @return string The contents of the graphviz file |
611
|
|
|
* @author Frode Marton Meling <[email protected]> |
612
|
|
|
*/ |
613
|
|
|
public function createDotFileForRoles(Model $model, $roles, $dotOptions) { |
614
|
|
|
$transitionsArray = $this->prepareForDotWithRoles($model, $roles); |
615
|
|
|
$digraph = "digraph finite_state_machine {\n\tfontsize=12;\n\tnode [shape = oval, style=filled, color = \"%s\"];\n\tstyle=filled;\n\tlabel=\"%s\"\n%s\n%s}\n"; |
616
|
|
|
$activeState = "\t" . "\"" . Inflector::humanize($this->getCurrentState($model)) . "\"" . " [ color = " . $dotOptions['activeColor'] . " ];"; |
617
|
|
|
|
618
|
|
|
$node = "\t\"%s\" -> \"%s\" [ style = bold, fontsize = 9, arrowType = normal, label = \"%s %s%s\" %s];\n"; |
619
|
|
|
$dotNodes = ""; |
620
|
|
|
|
621
|
|
|
foreach ($transitionsArray as $transition) { |
|
|
|
|
622
|
|
|
$dotNodes .= sprintf($node, |
623
|
|
|
Inflector::humanize($transition['stateFrom']), |
624
|
|
|
Inflector::humanize($transition['stateTo']), |
625
|
|
|
Inflector::humanize($transition['transition']), |
626
|
|
|
(isset($transition['roles']) && (!$this->_containsAllRoles($transition['roles'], $roles) || (count($roles) == 1))) ? 'by (' . Inflector::humanize(implode(' or ', $transition['roles'])) . ')' : 'by All', |
627
|
|
|
(isset($transition['depends'])) ? "\nif " . Inflector::humanize($transition['depends']) : '', |
628
|
|
|
(isset($transition['roles']) && count($transition['roles']) == 1) ? "color = \"" . $roles[$transition['roles'][0]]['color'] . "\"" : ''//, |
629
|
|
|
); |
630
|
|
|
} |
631
|
|
|
$graph = sprintf($digraph, $dotOptions['color'], 'Statemachine for ' . Inflector::humanize($model->alias) . ' role(s) : ' . Inflector::humanize(implode(', ', $this->getAllRoles($model, $roles))), $activeState, $dotNodes); |
632
|
|
|
return $graph; |
633
|
|
|
} |
634
|
|
|
|
635
|
|
|
/** |
636
|
|
|
* This helperfunction fetches out all roles from an array of roles with options. Note that this is a ('role' => $options) array |
637
|
|
|
* I did not find a php method for this, so made it myself |
638
|
|
|
* |
639
|
|
|
* @param Model $model The model being acted on |
640
|
|
|
* @param Array $roles This is just an array of roles like array('role1', 'role2'...) |
641
|
|
|
* @return Array Returns an array of roles like array('role1', 'role2'...) |
642
|
|
|
* @author Frode Marton Meling <[email protected]> |
643
|
|
|
* @todo Add separate tests @codingStandardsIgnoreLine |
644
|
|
|
*/ |
645
|
|
|
public function getAllRoles(Model $model, $roles) { |
646
|
|
|
$arrayToReturn = array(); |
647
|
|
|
foreach ($roles as $role => $option) { |
648
|
|
|
$arrayToReturn[] = $role; |
649
|
|
|
} |
650
|
|
|
return $arrayToReturn; |
651
|
|
|
} |
652
|
|
|
|
653
|
|
|
/** |
654
|
|
|
* This function is used to add transitions to Array. This tests for conditions and makes sure duplicates are not added. |
655
|
|
|
* |
656
|
|
|
* @param Model $model The model being acted on |
657
|
|
|
* @param array $data An array of a transition to be added |
658
|
|
|
* @param array $prepareArray The current array to populate |
659
|
|
|
* @return mixed |
660
|
|
|
* @author Frode Marton Meling <[email protected]> |
661
|
|
|
* @todo Move this to protected, Needs a reimplementation of the functiun in test to make it public for testing @codingStandardsIgnoreLine |
662
|
|
|
*/ |
663
|
|
|
public function addToPrepareArray(Model $model, $data, $prepareArray) { |
664
|
|
|
if (!is_array($data)) { |
665
|
|
|
return false; |
666
|
|
|
} |
667
|
|
|
|
668
|
|
|
if (!$this->_stateAndTransitionExist($data)) { |
669
|
|
|
return false; |
670
|
|
|
} |
671
|
|
|
|
672
|
|
|
// Check if we are preparing an object with states, transitions and depends |
673
|
|
|
if ($this->_stateTransitionAndDependsExist($data)) { |
674
|
|
|
$existingDataKey = $this->_stateTransitionAndDependsInArray($data, $prepareArray); |
675
|
|
View Code Duplication |
if ($existingDataKey === false) { |
|
|
|
|
676
|
|
|
$prepareArray[] = $data; |
677
|
|
|
} elseif (isset($data['roles'])) { |
678
|
|
|
$this->_addRoles($data['roles'], $prepareArray[$existingDataKey]); |
679
|
|
|
} |
680
|
|
|
return $prepareArray; |
681
|
|
|
} |
682
|
|
|
$existingDataKey = $this->_stateAndTransitionInArray($data, $prepareArray); |
683
|
|
View Code Duplication |
if ($existingDataKey !== false) { |
|
|
|
|
684
|
|
|
if (isset($data['roles'])) { |
685
|
|
|
$this->_addRoles($data['roles'], $prepareArray[$existingDataKey]); |
686
|
|
|
} |
687
|
|
|
return $prepareArray; |
688
|
|
|
} |
689
|
|
|
$prepareArray[] = $data; |
690
|
|
|
|
691
|
|
|
return $prepareArray; |
692
|
|
|
} |
693
|
|
|
|
694
|
|
|
/** |
695
|
|
|
* This helperfunction checks if all roles in an array (roles) is present in $allArrays. Note that this is a ('role' => $options) array |
696
|
|
|
* I did not find a php method for this, so made it myself |
697
|
|
|
* |
698
|
|
|
* @param Array $roles This is just an array of roles like array('role1', 'role2'...) |
699
|
|
|
* @param Array $allRoles This is the array to test on. This is a multidimentional array like array('role1' => array('of' => 'options'), 'role2' => array('of' => 'options') ) |
700
|
|
|
* @return bool Returns true if all roles are present, otherwise false |
701
|
|
|
* @author Frode Marton Meling <[email protected]> |
702
|
|
|
* @todo Add separate tests @codingStandardsIgnoreLine |
703
|
|
|
*/ |
704
|
|
|
protected function _containsAllRoles($roles, $allRoles) { |
705
|
|
|
foreach ($allRoles as $role => $options) { |
706
|
|
|
if (!in_array($role, $roles)) { |
707
|
|
|
return false; |
708
|
|
|
} |
709
|
|
|
} |
710
|
|
|
return true; |
711
|
|
|
} |
712
|
|
|
|
713
|
|
|
/** |
714
|
|
|
* This helperfunction checks if any of the roles in an array (roles) is present in $allArrays. Note that this is a ('role' => $options) array |
715
|
|
|
* I did not find a php method for this, so made it myself |
716
|
|
|
* |
717
|
|
|
* @param Array $roles This is just an array of roles like array('role1', 'role2'...) |
718
|
|
|
* @param Array $allRoles This is the array to test on. This is a multidimentional array like array('role1' => array('of' => 'options'), 'role2' => array('of' => 'options') ) |
719
|
|
|
* @return bool Returns true if just one of the roles are present, otherwise false |
720
|
|
|
* @author Frode Marton Meling <[email protected]> |
721
|
|
|
* @todo Add separate tests @codingStandardsIgnoreLine |
722
|
|
|
*/ |
723
|
|
|
protected function _containsAnyRoles($roles, $allRoles) { |
724
|
|
|
$atleastOne = false; |
725
|
|
|
foreach ($allRoles as $role => $options) { |
726
|
|
|
if (in_array($role, $roles)) { |
727
|
|
|
$atleastOne = true; |
728
|
|
|
} |
729
|
|
|
} |
730
|
|
|
return $atleastOne; |
731
|
|
|
} |
732
|
|
|
|
733
|
|
|
/** |
734
|
|
|
* This helperfunction adds a role to an array. It checks for duplicates and only adds if it is not already in array |
735
|
|
|
* If also checks that the resultArray is valid and that there are roles there to begin with |
736
|
|
|
* |
737
|
|
|
* @param Array $roles This is just an array of roles like array('role1', 'role2'...) |
738
|
|
|
* @param Array &$resultArray This function writes to this parameter by reference |
739
|
|
|
* @return bool Returns true if added, otherwise false |
740
|
|
|
* @author Frode Marton Meling <[email protected]> |
741
|
|
|
* @todo Add separate tests @codingStandardsIgnoreLine |
742
|
|
|
*/ |
743
|
|
|
protected function _addRoles($roles, &$resultArray) { |
744
|
|
|
$addedAtleastOne = false; |
745
|
|
|
foreach ($roles as $role) { |
746
|
|
|
if (!isset($resultArray['roles']) || isset($resultArray['roles']) && !in_array($role, $resultArray['roles'])) { |
747
|
|
|
$resultArray['roles'][] = $role; |
748
|
|
|
$addedAtleastOne = true; |
749
|
|
|
} |
750
|
|
|
} |
751
|
|
|
return $addedAtleastOne; |
752
|
|
|
} |
753
|
|
|
|
754
|
|
|
/** |
755
|
|
|
* This helperfunction checks if state and transition is present in the array |
756
|
|
|
* |
757
|
|
|
* @param array $data The array to check |
758
|
|
|
* @return bool true if array is valid, otherwise false |
759
|
|
|
* @author Frode Marton Meling <[email protected]> |
760
|
|
|
* @todo Add separate tests @codingStandardsIgnoreLine |
761
|
|
|
*/ |
762
|
|
|
protected function _stateAndTransitionExist($data) { |
763
|
|
View Code Duplication |
if (isset($data['stateFrom']) && isset($data['stateTo']) && isset($data['transition'])) { |
|
|
|
|
764
|
|
|
return true; |
765
|
|
|
} |
766
|
|
|
return false; |
767
|
|
|
} |
768
|
|
|
|
769
|
|
|
/** |
770
|
|
|
* This helperfunction checks if state, transition and depends exist in array |
771
|
|
|
* |
772
|
|
|
* @param array $data The array to check |
773
|
|
|
* @return bool True if state, transition and depends exist in array, otherwise false |
774
|
|
|
* @author Frode Marton Meling <[email protected]> |
775
|
|
|
* @todo Add separate tests @codingStandardsIgnoreLine |
776
|
|
|
*/ |
777
|
|
|
protected function _stateTransitionAndDependsExist($data) { |
778
|
|
View Code Duplication |
if (isset($data['stateFrom']) && isset($data['stateTo']) && isset($data['transition']) && isset($data['depends'])) { |
|
|
|
|
779
|
|
|
return true; |
780
|
|
|
} |
781
|
|
|
return false; |
782
|
|
|
} |
783
|
|
|
|
784
|
|
|
/** |
785
|
|
|
* This helperfunction checks if state and transition is present in prepareArray. this is used to prevent adding duplicates |
786
|
|
|
* |
787
|
|
|
* @param array $data The array for testing |
788
|
|
|
* @param array $prepareArray The array to check against |
789
|
|
|
* @return bool index in array if state and transition is present in prepareArray, otherwise false |
790
|
|
|
* @author Frode Marton Meling <[email protected]> |
791
|
|
|
* @todo Add separate tests @codingStandardsIgnoreLine |
792
|
|
|
*/ |
793
|
|
|
protected function _stateAndTransitionInArray($data, $prepareArray) { |
794
|
|
|
foreach ($prepareArray as $key => $value) { |
795
|
|
|
if (($value['stateFrom'] == $data['stateFrom']) && ($value['stateTo'] == $data['stateTo']) && ($value['transition'] == $data['transition'])) { |
796
|
|
|
return $key; |
797
|
|
|
} |
798
|
|
|
} |
799
|
|
|
return false; |
800
|
|
|
} |
801
|
|
|
|
802
|
|
|
/** |
803
|
|
|
* This helperfunction checks if state, transition and depends is present in prepareArray. this is used to prevent adding duplicates |
804
|
|
|
* |
805
|
|
|
* @param array $data The array for testing |
806
|
|
|
* @param array $prepareArray The array to check against |
807
|
|
|
* @return bool the index in array if state, transition and depends is present in prepareArray, otherwise false |
808
|
|
|
* @author Frode Marton Meling <[email protected]> |
809
|
|
|
* @todo Add separate tests @codingStandardsIgnoreLine |
810
|
|
|
*/ |
811
|
|
|
protected function _stateTransitionAndDependsInArray($data, $prepareArray) { |
812
|
|
|
foreach ($prepareArray as $key => $value) { |
813
|
|
|
if (!isset($value['depends'])) { |
814
|
|
|
continue; |
815
|
|
|
} |
816
|
|
|
if (($value['stateFrom'] == $data['stateFrom']) && ($value['stateTo'] == $data['stateTo']) && ($value['transition'] == $data['transition']) && ($value['depends'] == $data['depends'])) { |
817
|
|
|
return $key; |
818
|
|
|
} |
819
|
|
|
} |
820
|
|
|
return false; |
821
|
|
|
} |
822
|
|
|
|
823
|
|
|
/** |
824
|
|
|
* Checks whether or not the given role may perform the transition change. |
825
|
|
|
* The callback in 'depends' must be a valid model method. |
826
|
|
|
* |
827
|
|
|
* @param Model $model The model being acted on |
828
|
|
|
* @param string $role The role executing the transition change |
829
|
|
|
* @param string $transition The transition |
830
|
|
|
* @throws InvalidArgumentException if the transition require it be executed by a rule, and none is given |
831
|
|
|
* @return bool Whether or not the role may perform the action |
832
|
|
|
*/ |
833
|
|
|
protected function _checkRoleAgainstRule(Model $model, $role, $transition) { |
834
|
|
|
if (!isset($model->transitionRules[$transition])) { |
835
|
|
|
return null; |
836
|
|
|
} |
837
|
|
|
|
838
|
|
|
if (!$role) { |
839
|
|
|
throw new InvalidArgumentException('The transition ' . $transition . ' requires a role'); |
840
|
|
|
} |
841
|
|
|
|
842
|
|
|
if (!in_array($role, $model->transitionRules[$transition]['role'])) { |
843
|
|
|
return false; |
844
|
|
|
} |
845
|
|
|
|
846
|
|
|
if (!isset($model->transitionRules[$transition]['depends'])) { |
847
|
|
|
return true; |
848
|
|
|
} |
849
|
|
|
|
850
|
|
|
$callback = Inflector::variable($model->transitionRules[$transition]['depends']); |
851
|
|
|
|
852
|
|
|
if ($this->_hasMethod($model, $callback)) { |
853
|
|
|
// Fix: if the method is supplied as an anonymous callback, we cannot call |
854
|
|
|
// it from the model directly |
855
|
|
|
$res = $this->settings[$model->alias]['methods'][$callback]($role); |
856
|
|
|
} else { |
857
|
|
|
$res = call_user_func(array($model, $callback), $role); |
858
|
|
|
} |
859
|
|
|
|
860
|
|
|
return $res; |
861
|
|
|
} |
862
|
|
|
|
863
|
|
|
/** |
864
|
|
|
* Calls transition listeners before or after a particular transition. |
865
|
|
|
* Special model methods are also called, if they exist: |
866
|
|
|
* - onBeforeTransition |
867
|
|
|
* - onAfterTransition |
868
|
|
|
* - onBefore<Transition> i.e. onBeforePark() |
869
|
|
|
* - onAfter<Transition> i.e. onAfterPark() |
870
|
|
|
* |
871
|
|
|
* @param Model $model The model being acted on |
872
|
|
|
* @param string $transition The transition name |
873
|
|
|
* @param string $triggerType Either before or after |
874
|
|
|
* @return void |
875
|
|
|
*/ |
876
|
|
|
protected function _callTransitionListeners(Model $model, $transition, $triggerType = 'after') { |
877
|
|
|
$transitionListeners = & $this->settings[$model->alias]['transition_listeners']; |
878
|
|
|
$listeners = $transitionListeners['transition'][$triggerType]; |
879
|
|
|
|
880
|
|
|
if (isset($transitionListeners[$transition][$triggerType])) { |
881
|
|
|
$listeners = array_merge($transitionListeners[$transition][$triggerType], $listeners); |
882
|
|
|
} |
883
|
|
|
|
884
|
|
|
foreach (array( |
885
|
|
|
'on' . Inflector::camelize($triggerType . 'Transition'), |
886
|
|
|
'on' . Inflector::camelize($triggerType . $transition) |
887
|
|
|
) as $method) { |
888
|
|
|
if (method_exists($model, $method)) { |
889
|
|
|
$listeners[] = array( |
890
|
|
|
'cb' => array($model, $method), |
891
|
|
|
'bubble' => true |
892
|
|
|
); |
893
|
|
|
} |
894
|
|
|
} |
895
|
|
|
|
896
|
|
|
$currentState = $this->getCurrentState($model); |
897
|
|
|
$previousState = $this->getPreviousState($model); |
898
|
|
|
|
899
|
|
|
foreach ($listeners as $cb) { |
900
|
|
|
call_user_func_array($cb['cb'], array($currentState, $previousState, $transition)); |
901
|
|
|
|
902
|
|
|
if (!$cb['bubble']) { |
903
|
|
|
break; |
904
|
|
|
} |
905
|
|
|
} |
906
|
|
|
} |
907
|
|
|
|
908
|
|
|
/** |
909
|
|
|
* Deformalizes a method name, removing 'can' and 'is' as well as underscoring |
910
|
|
|
* the remaining text. |
911
|
|
|
* |
912
|
|
|
* @param string $name The model name |
913
|
|
|
* @return string The deformalized method name |
914
|
|
|
*/ |
915
|
|
|
protected function _deFormalizeMethodName($name) { |
916
|
|
|
return Inflector::underscore(preg_replace('#^(can|is)#', '', $name)); |
917
|
|
|
} |
918
|
|
|
|
919
|
|
|
/** |
920
|
|
|
* Checks whether or not a user-defined method exists in the Behavior |
921
|
|
|
* |
922
|
|
|
* @param Model $model The model being acted on |
923
|
|
|
* @param string $method The method's name |
924
|
|
|
* @return bool True if the method exists, false otherwise |
925
|
|
|
*/ |
926
|
|
|
protected function _hasMethod(Model $model, $method) { |
927
|
|
|
return isset($this->settings[$model->alias]['methods'][$method]) || isset($this->mapMethods['/' . $method . '/']); |
928
|
|
|
} |
929
|
|
|
} |
930
|
|
|
|
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.