This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Robo\Collection; |
||
4 | |||
5 | use Robo\Exception\AbortTasksException; |
||
6 | use Robo\Result; |
||
7 | use Robo\State\Data; |
||
8 | use Psr\Log\LogLevel; |
||
9 | use Robo\Contract\TaskInterface; |
||
10 | use Robo\Task\StackBasedTask; |
||
11 | use Robo\Task\BaseTask; |
||
12 | use Robo\TaskInfo; |
||
13 | use Robo\Contract\WrappedTaskInterface; |
||
14 | use Robo\Exception\TaskException; |
||
15 | use Robo\Exception\TaskExitException; |
||
16 | use Robo\Contract\CommandInterface; |
||
17 | use Robo\Contract\InflectionInterface; |
||
18 | use Robo\State\StateAwareInterface; |
||
19 | use Robo\State\StateAwareTrait; |
||
20 | |||
21 | /** |
||
22 | * Group tasks into a collection that run together. Supports |
||
23 | * rollback operations for handling error conditions. |
||
24 | * |
||
25 | * This is an internal class. Clients should use a CollectionBuilder |
||
26 | * rather than direct use of the Collection class. @see CollectionBuilder. |
||
27 | * |
||
28 | * Below, the example FilesystemStack task is added to a collection, |
||
29 | * and associated with a rollback task. If any of the operations in |
||
30 | * the FilesystemStack, or if any of the other tasks also added to |
||
31 | * the task collection should fail, then the rollback function is |
||
32 | * called. Here, taskDeleteDir is used to remove partial results |
||
33 | * of an unfinished task. |
||
34 | */ |
||
35 | class Collection extends BaseTask implements CollectionInterface, CommandInterface, StateAwareInterface |
||
36 | { |
||
37 | use StateAwareTrait; |
||
38 | |||
39 | /** |
||
40 | * @var \Robo\Collection\Element[] |
||
41 | */ |
||
42 | protected $taskList = []; |
||
43 | |||
44 | /** |
||
45 | * @var \Robo\Contract\TaskInterface[] |
||
46 | */ |
||
47 | protected $rollbackStack = []; |
||
48 | |||
49 | /** |
||
50 | * @var \Robo\Contract\TaskInterface[] |
||
51 | */ |
||
52 | protected $completionStack = []; |
||
53 | |||
54 | /** |
||
55 | * @var \Robo\Collection\CollectionInterface |
||
56 | */ |
||
57 | protected $parentCollection; |
||
58 | |||
59 | /** |
||
60 | * @var callable[] |
||
61 | */ |
||
62 | protected $deferredCallbacks = []; |
||
63 | |||
64 | /** |
||
65 | * @var string[] |
||
66 | */ |
||
67 | protected $messageStoreKeys = []; |
||
68 | |||
69 | /** |
||
70 | * Constructor. |
||
71 | */ |
||
72 | public function __construct() |
||
73 | { |
||
74 | $this->resetState(); |
||
75 | } |
||
76 | |||
77 | /** |
||
78 | * @param int $interval |
||
79 | */ |
||
80 | public function setProgressBarAutoDisplayInterval($interval) |
||
81 | { |
||
82 | if (!$this->progressIndicator) { |
||
83 | return; |
||
84 | } |
||
85 | return $this->progressIndicator->setProgressBarAutoDisplayInterval($interval); |
||
86 | } |
||
87 | |||
88 | /** |
||
89 | * {@inheritdoc} |
||
90 | */ |
||
91 | public function add(TaskInterface $task, $name = self::UNNAMEDTASK) |
||
92 | { |
||
93 | $task = new CompletionWrapper($this, $task); |
||
94 | $this->addToTaskList($name, $task); |
||
95 | return $this; |
||
96 | } |
||
97 | |||
98 | /** |
||
99 | * {@inheritdoc} |
||
100 | */ |
||
101 | public function addCode(callable $code, $name = self::UNNAMEDTASK) |
||
102 | { |
||
103 | return $this->add(new CallableTask($code, $this), $name); |
||
104 | } |
||
105 | |||
106 | /** |
||
107 | * {@inheritdoc} |
||
108 | */ |
||
109 | public function addIterable($iterable, callable $code) |
||
110 | { |
||
111 | $callbackTask = (new IterationTask($iterable, $code, $this))->inflect($this); |
||
112 | return $this->add($callbackTask); |
||
113 | } |
||
114 | |||
115 | /** |
||
116 | * {@inheritdoc} |
||
117 | */ |
||
118 | public function rollback(TaskInterface $rollbackTask) |
||
119 | { |
||
120 | // Rollback tasks always try as hard as they can, and never report failures. |
||
121 | $rollbackTask = $this->ignoreErrorsTaskWrapper($rollbackTask); |
||
122 | return $this->wrapAndRegisterRollback($rollbackTask); |
||
123 | } |
||
124 | |||
125 | /** |
||
126 | * {@inheritdoc} |
||
127 | */ |
||
128 | public function rollbackCode(callable $rollbackCode) |
||
129 | { |
||
130 | // Rollback tasks always try as hard as they can, and never report failures. |
||
131 | $rollbackTask = $this->ignoreErrorsCodeWrapper($rollbackCode); |
||
132 | return $this->wrapAndRegisterRollback($rollbackTask); |
||
133 | } |
||
134 | |||
135 | /** |
||
136 | * {@inheritdoc} |
||
137 | */ |
||
138 | public function completion(TaskInterface $completionTask) |
||
139 | { |
||
140 | $collection = $this; |
||
141 | $completionRegistrationTask = new CallableTask( |
||
142 | function () use ($collection, $completionTask) { |
||
143 | |||
144 | $collection->registerCompletion($completionTask); |
||
145 | }, |
||
146 | $this |
||
147 | ); |
||
148 | $this->addToTaskList(self::UNNAMEDTASK, $completionRegistrationTask); |
||
149 | return $this; |
||
150 | } |
||
151 | |||
152 | /** |
||
153 | * {@inheritdoc} |
||
154 | */ |
||
155 | public function completionCode(callable $completionTask) |
||
156 | { |
||
157 | $completionTask = new CallableTask($completionTask, $this); |
||
158 | return $this->completion($completionTask); |
||
159 | } |
||
160 | |||
161 | /** |
||
162 | * {@inheritdoc} |
||
163 | */ |
||
164 | public function before($name, $task, $nameOfTaskToAdd = self::UNNAMEDTASK) |
||
165 | { |
||
166 | return $this->addBeforeOrAfter(__FUNCTION__, $name, $task, $nameOfTaskToAdd); |
||
167 | } |
||
168 | |||
169 | /** |
||
170 | * {@inheritdoc} |
||
171 | */ |
||
172 | public function after($name, $task, $nameOfTaskToAdd = self::UNNAMEDTASK) |
||
173 | { |
||
174 | return $this->addBeforeOrAfter(__FUNCTION__, $name, $task, $nameOfTaskToAdd); |
||
175 | } |
||
176 | |||
177 | /** |
||
178 | * {@inheritdoc} |
||
179 | */ |
||
180 | public function progressMessage($text, $context = [], $level = LogLevel::NOTICE) |
||
181 | { |
||
182 | $context += ['name' => 'Progress']; |
||
183 | $context += TaskInfo::getTaskContext($this); |
||
184 | return $this->addCode( |
||
185 | function () use ($level, $text, $context) { |
||
186 | $context += $this->getState()->getData(); |
||
187 | $this->printTaskOutput($level, $text, $context); |
||
0 ignored issues
–
show
|
|||
188 | } |
||
189 | ); |
||
190 | } |
||
191 | |||
192 | /** |
||
193 | * @param \Robo\Contract\TaskInterface $rollbackTask |
||
194 | * |
||
195 | * @return $this |
||
196 | */ |
||
197 | protected function wrapAndRegisterRollback(TaskInterface $rollbackTask) |
||
198 | { |
||
199 | $collection = $this; |
||
200 | $rollbackRegistrationTask = new CallableTask( |
||
201 | function () use ($collection, $rollbackTask) { |
||
202 | $collection->registerRollback($rollbackTask); |
||
203 | }, |
||
204 | $this |
||
205 | ); |
||
206 | $this->addToTaskList(self::UNNAMEDTASK, $rollbackRegistrationTask); |
||
207 | return $this; |
||
208 | } |
||
209 | |||
210 | /** |
||
211 | * Add either a 'before' or 'after' function or task. |
||
212 | * |
||
213 | * @param string $method |
||
214 | * @param string $name |
||
215 | * @param callable|\Robo\Contract\TaskInterface $task |
||
216 | * @param string $nameOfTaskToAdd |
||
217 | * |
||
218 | * @return $this |
||
219 | */ |
||
220 | protected function addBeforeOrAfter($method, $name, $task, $nameOfTaskToAdd) |
||
221 | { |
||
222 | if (is_callable($task)) { |
||
223 | $task = new CallableTask($task, $this); |
||
224 | } |
||
225 | $existingTask = $this->namedTask($name); |
||
226 | $fn = [$existingTask, $method]; |
||
227 | call_user_func($fn, $task, $nameOfTaskToAdd); |
||
228 | return $this; |
||
229 | } |
||
230 | |||
231 | /** |
||
232 | * Wrap the provided task in a wrapper that will ignore |
||
233 | * any errors or exceptions that may be produced. This |
||
234 | * is useful, for example, in adding optional cleanup tasks |
||
235 | * at the beginning of a task collection, to remove previous |
||
236 | * results which may or may not exist. |
||
237 | * |
||
238 | * TODO: Provide some way to specify which sort of errors |
||
239 | * are ignored, so that 'file not found' may be ignored, |
||
240 | * but 'permission denied' reported? |
||
241 | * |
||
242 | * @param \Robo\Contract\TaskInterface $task |
||
243 | * |
||
244 | * @return \Robo\Collection\CallableTask |
||
245 | */ |
||
246 | public function ignoreErrorsTaskWrapper(TaskInterface $task) |
||
247 | { |
||
248 | // If the task is a stack-based task, then tell it |
||
249 | // to try to run all of its operations, even if some |
||
250 | // of them fail. |
||
251 | if ($task instanceof StackBasedTask) { |
||
252 | $task->stopOnFail(false); |
||
253 | } |
||
254 | $ignoreErrorsInTask = function () use ($task) { |
||
255 | $data = []; |
||
256 | try { |
||
257 | $result = $this->runSubtask($task); |
||
258 | $message = $result->getMessage(); |
||
259 | $data = $result->getData(); |
||
260 | $data['exitcode'] = $result->getExitCode(); |
||
261 | } catch (AbortTasksException $abortTasksException) { |
||
262 | throw $abortTasksException; |
||
263 | } catch (\Exception $e) { |
||
264 | $message = $e->getMessage(); |
||
265 | } |
||
266 | |||
267 | return Result::success($task, $message, $data); |
||
268 | }; |
||
269 | // Wrap our ignore errors callable in a task. |
||
270 | return new CallableTask($ignoreErrorsInTask, $this); |
||
271 | } |
||
272 | |||
273 | /** |
||
274 | * @param callable $task |
||
275 | * |
||
276 | * @return \Robo\Collection\CallableTask |
||
277 | */ |
||
278 | public function ignoreErrorsCodeWrapper(callable $task) |
||
279 | { |
||
280 | return $this->ignoreErrorsTaskWrapper(new CallableTask($task, $this)); |
||
281 | } |
||
282 | |||
283 | /** |
||
284 | * Return the list of task names added to this collection. |
||
285 | * |
||
286 | * @return string[] |
||
287 | */ |
||
288 | public function taskNames() |
||
289 | { |
||
290 | return array_keys($this->taskList); |
||
291 | } |
||
292 | |||
293 | /** |
||
294 | * Test to see if a specified task name exists. |
||
295 | * n.b. before() and after() require that the named |
||
296 | * task exist; use this function to test first, if |
||
297 | * unsure. |
||
298 | * |
||
299 | * @param string $name |
||
300 | * |
||
301 | * @return bool |
||
302 | */ |
||
303 | public function hasTask($name) |
||
304 | { |
||
305 | return array_key_exists($name, $this->taskList); |
||
306 | } |
||
307 | |||
308 | /** |
||
309 | * Find an existing named task. |
||
310 | * |
||
311 | * @param string $name |
||
312 | * The name of the task to insert before. The named task MUST exist. |
||
313 | * |
||
314 | * @return \Robo\Collection\Element |
||
315 | * The task group for the named task. Generally this is only |
||
316 | * used to call 'before()' and 'after()'. |
||
317 | */ |
||
318 | protected function namedTask($name) |
||
319 | { |
||
320 | if (!$this->hasTask($name)) { |
||
321 | throw new \RuntimeException("Could not find task named $name"); |
||
322 | } |
||
323 | return $this->taskList[$name]; |
||
324 | } |
||
325 | |||
326 | /** |
||
327 | * Add a list of tasks to our task collection. |
||
328 | * |
||
329 | * @param \Robo\Contract\TaskInterface[] $tasks |
||
330 | * An array of tasks to run with rollback protection |
||
331 | * |
||
332 | * @return $this |
||
333 | */ |
||
334 | public function addTaskList(array $tasks) |
||
335 | { |
||
336 | foreach ($tasks as $name => $task) { |
||
337 | $this->add($task, $name); |
||
338 | } |
||
339 | return $this; |
||
340 | } |
||
341 | |||
342 | /** |
||
343 | * Add the provided task to our task list. |
||
344 | * |
||
345 | * @param string $name |
||
346 | * @param \Robo\Contract\TaskInterface $task |
||
347 | * |
||
348 | * @return $this |
||
349 | */ |
||
350 | protected function addToTaskList($name, TaskInterface $task) |
||
351 | { |
||
352 | // All tasks are stored in a task group so that we have a place |
||
353 | // to hang 'before' and 'after' tasks. |
||
354 | $taskGroup = new Element($task); |
||
355 | return $this->addCollectionElementToTaskList($name, $taskGroup); |
||
356 | } |
||
357 | |||
358 | /** |
||
359 | * @param int|string $name |
||
360 | * @param \Robo\Collection\Element $taskGroup |
||
361 | * |
||
362 | * @return $this |
||
363 | */ |
||
364 | protected function addCollectionElementToTaskList($name, Element $taskGroup) |
||
365 | { |
||
366 | // If a task name is not provided, then we'll let php pick |
||
367 | // the array index. |
||
368 | if (Result::isUnnamed($name)) { |
||
369 | $this->taskList[] = $taskGroup; |
||
370 | return $this; |
||
371 | } |
||
372 | // If we are replacing an existing task with the |
||
373 | // same name, ensure that our new task is added to |
||
374 | // the end. |
||
375 | $this->taskList[$name] = $taskGroup; |
||
376 | return $this; |
||
377 | } |
||
378 | |||
379 | /** |
||
380 | * Set the parent collection. This is necessary so that nested |
||
381 | * collections' rollback and completion tasks can be added to the |
||
382 | * top-level collection, ensuring that the rollbacks for a collection |
||
383 | * will run if any later task fails. |
||
384 | * |
||
385 | * @param \Robo\Collection\NestedCollectionInterface $parentCollection |
||
386 | * |
||
387 | * @return $this |
||
388 | */ |
||
389 | public function setParentCollection(NestedCollectionInterface $parentCollection) |
||
390 | { |
||
391 | $this->parentCollection = $parentCollection; |
||
0 ignored issues
–
show
$parentCollection is of type object<Robo\Collection\NestedCollectionInterface> , but the property $parentCollection was declared to be of type object<Robo\Collection\CollectionInterface> . Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly. Either this assignment is in error or an instanceof check should be added for that assignment. class Alien {}
class Dalek extends Alien {}
class Plot
{
/** @var Dalek */
public $villain;
}
$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
$plot->villain = $alien;
}
Loading history...
|
|||
392 | return $this; |
||
393 | } |
||
394 | |||
395 | /** |
||
396 | * Get the appropriate parent collection to use |
||
397 | * |
||
398 | * @return \Robo\Collection\CollectionInterface|$this |
||
399 | */ |
||
400 | public function getParentCollection() |
||
401 | { |
||
402 | return $this->parentCollection ? $this->parentCollection : $this; |
||
403 | } |
||
404 | |||
405 | /** |
||
406 | * Register a rollback task to run if there is any failure. |
||
407 | * |
||
408 | * Clients are free to add tasks to the rollback stack as |
||
409 | * desired; however, usually it is preferable to call |
||
410 | * Collection::rollback() instead. With that function, |
||
411 | * the rollback function will only be called if all of the |
||
412 | * tasks added before it complete successfully, AND some later |
||
413 | * task fails. |
||
414 | * |
||
415 | * One example of a good use-case for registering a callback |
||
416 | * function directly is to add a task that sends notification |
||
417 | * when a task fails. |
||
418 | * |
||
419 | * @param \Robo\Contract\TaskInterface $rollbackTask |
||
420 | * The rollback task to run on failure. |
||
421 | * |
||
422 | * @return null |
||
423 | */ |
||
424 | public function registerRollback(TaskInterface $rollbackTask) |
||
425 | { |
||
426 | if ($this->parentCollection) { |
||
427 | return $this->parentCollection->registerRollback($rollbackTask); |
||
0 ignored issues
–
show
The method
registerRollback() does not exist on Robo\Collection\CollectionInterface . Did you maybe mean rollback() ?
This check marks calls to methods that do not seem to exist on an object. This is most likely the result of a method being renamed without all references to it being renamed likewise.
Loading history...
|
|||
428 | } |
||
429 | if ($rollbackTask) { |
||
430 | array_unshift($this->rollbackStack, $rollbackTask); |
||
431 | } |
||
432 | } |
||
433 | |||
434 | /** |
||
435 | * Register a completion task to run once all other tasks finish. |
||
436 | * Completion tasks run whether or not a rollback operation was |
||
437 | * triggered. They do not trigger rollbacks if they fail. |
||
438 | * |
||
439 | * The typical use-case for a completion function is to clean up |
||
440 | * temporary objects (e.g. temporary folders). The preferred |
||
441 | * way to do that, though, is to use Temporary::wrap(). |
||
442 | * |
||
443 | * On failures, completion tasks will run after all rollback tasks. |
||
444 | * If one task collection is nested inside another task collection, |
||
445 | * then the nested collection's completion tasks will run as soon as |
||
446 | * the nested task completes; they are not deferred to the end of |
||
447 | * the containing collection's execution. |
||
448 | * |
||
449 | * @param \Robo\Contract\TaskInterface $completionTask |
||
450 | * The completion task to run at the end of all other operations. |
||
451 | * |
||
452 | * @return null |
||
453 | */ |
||
454 | public function registerCompletion(TaskInterface $completionTask) |
||
455 | { |
||
456 | if ($this->parentCollection) { |
||
457 | return $this->parentCollection->registerCompletion($completionTask); |
||
0 ignored issues
–
show
The method
registerCompletion() does not exist on Robo\Collection\CollectionInterface . Did you maybe mean completion() ?
This check marks calls to methods that do not seem to exist on an object. This is most likely the result of a method being renamed without all references to it being renamed likewise.
Loading history...
|
|||
458 | } |
||
459 | if ($completionTask) { |
||
460 | // Completion tasks always try as hard as they can, and never report failures. |
||
461 | $completionTask = $this->ignoreErrorsTaskWrapper($completionTask); |
||
462 | $this->completionStack[] = $completionTask; |
||
463 | } |
||
464 | } |
||
465 | |||
466 | /** |
||
467 | * Return the count of steps in this collection |
||
468 | * |
||
469 | * @return int |
||
470 | */ |
||
471 | public function progressIndicatorSteps() |
||
472 | { |
||
473 | $steps = 0; |
||
474 | foreach ($this->taskList as $name => $taskGroup) { |
||
475 | $steps += $taskGroup->progressIndicatorSteps(); |
||
476 | } |
||
477 | return $steps; |
||
478 | } |
||
479 | |||
480 | /** |
||
481 | * A Collection of tasks can provide a command via `getCommand()` |
||
482 | * if it contains a single task, and that task implements CommandInterface. |
||
483 | * |
||
484 | * @return string |
||
485 | * |
||
486 | * @throws \Robo\Exception\TaskException |
||
487 | */ |
||
488 | public function getCommand() |
||
489 | { |
||
490 | if (empty($this->taskList)) { |
||
491 | return ''; |
||
492 | } |
||
493 | |||
494 | if (count($this->taskList) > 1) { |
||
495 | // TODO: We could potentially iterate over the items in the collection |
||
496 | // and concatenate the result of getCommand() from each one, and fail |
||
497 | // only if we encounter a command that is not a CommandInterface. |
||
498 | throw new TaskException($this, "getCommand() does not work on arbitrary collections of tasks."); |
||
499 | } |
||
500 | |||
501 | $taskElement = reset($this->taskList); |
||
502 | $task = $taskElement->getTask(); |
||
503 | $task = ($task instanceof WrappedTaskInterface) ? $task->original() : $task; |
||
504 | if ($task instanceof CommandInterface) { |
||
505 | return $task->getCommand(); |
||
506 | } |
||
507 | |||
508 | throw new TaskException($task, get_class($task) . " does not implement CommandInterface, so can't be used to provide a command"); |
||
509 | } |
||
510 | |||
511 | /** |
||
512 | * Run our tasks, and roll back if necessary. |
||
513 | * |
||
514 | * @return \Robo\Result |
||
515 | */ |
||
516 | public function run() |
||
517 | { |
||
518 | $result = $this->runWithoutCompletion(); |
||
519 | $this->complete(); |
||
520 | return $result; |
||
521 | } |
||
522 | |||
523 | /** |
||
524 | * @return \Robo\Result |
||
525 | */ |
||
526 | private function runWithoutCompletion() |
||
527 | { |
||
528 | $result = Result::success($this); |
||
529 | |||
530 | if (empty($this->taskList)) { |
||
531 | return $result; |
||
532 | } |
||
533 | |||
534 | $this->startProgressIndicator(); |
||
535 | if ($result->wasSuccessful()) { |
||
536 | foreach ($this->taskList as $name => $taskGroup) { |
||
537 | $taskList = $taskGroup->getTaskList(); |
||
538 | $result = $this->runTaskList($name, $taskList, $result); |
||
539 | if (!$result->wasSuccessful()) { |
||
540 | $this->fail(); |
||
541 | return $result; |
||
542 | } |
||
543 | } |
||
544 | $this->taskList = []; |
||
545 | } |
||
546 | $this->stopProgressIndicator(); |
||
547 | $result['time'] = $this->getExecutionTime(); |
||
548 | |||
549 | return $result; |
||
550 | } |
||
551 | |||
552 | /** |
||
553 | * Run every task in a list, but only up to the first failure. |
||
554 | * Return the failing result, or success if all tasks run. |
||
555 | * |
||
556 | * @param string $name |
||
557 | * @param \Robo\Contract\TaskInterface[] $taskList |
||
558 | * @param \Robo\Result $result |
||
559 | * |
||
560 | * @return \Robo\Result |
||
561 | * |
||
562 | * @throws \Robo\Exception\TaskExitException |
||
563 | */ |
||
564 | private function runTaskList($name, array $taskList, Result $result) |
||
565 | { |
||
566 | try { |
||
567 | foreach ($taskList as $taskName => $task) { |
||
568 | $taskResult = $this->runSubtask($task); |
||
569 | $this->advanceProgressIndicator(); |
||
570 | // If the current task returns an error code, then stop |
||
571 | // execution and signal a rollback. |
||
572 | if (!$taskResult->wasSuccessful()) { |
||
573 | return $taskResult; |
||
574 | } |
||
575 | // We accumulate our results into a field so that tasks that |
||
576 | // have a reference to the collection may examine and modify |
||
577 | // the incremental results, if they wish. |
||
578 | $key = Result::isUnnamed($taskName) ? $name : $taskName; |
||
579 | $result->accumulate($key, $taskResult); |
||
580 | // The result message will be the message of the last task executed. |
||
581 | $result->setMessage($taskResult->getMessage()); |
||
582 | } |
||
583 | } catch (TaskExitException $exitException) { |
||
584 | $this->fail(); |
||
585 | throw $exitException; |
||
586 | } catch (\Exception $e) { |
||
587 | // Tasks typically should not throw, but if one does, we will |
||
588 | // convert it into an error and roll back. |
||
589 | return Result::fromException($task, $e, $result->getData()); |
||
590 | } |
||
591 | return $result; |
||
592 | } |
||
593 | |||
594 | /** |
||
595 | * Force the rollback functions to run |
||
596 | * |
||
597 | * @return $this |
||
598 | */ |
||
599 | public function fail() |
||
600 | { |
||
601 | $this->disableProgressIndicator(); |
||
602 | $this->runRollbackTasks(); |
||
603 | $this->complete(); |
||
604 | return $this; |
||
605 | } |
||
606 | |||
607 | /** |
||
608 | * Force the completion functions to run |
||
609 | * |
||
610 | * @return $this |
||
611 | */ |
||
612 | public function complete() |
||
613 | { |
||
614 | $this->detatchProgressIndicator(); |
||
615 | $this->runTaskListIgnoringFailures($this->completionStack); |
||
616 | $this->reset(); |
||
617 | return $this; |
||
618 | } |
||
619 | |||
620 | /** |
||
621 | * Reset this collection, removing all tasks. |
||
622 | * |
||
623 | * @return $this |
||
624 | */ |
||
625 | public function reset() |
||
626 | { |
||
627 | $this->taskList = []; |
||
628 | $this->completionStack = []; |
||
629 | $this->rollbackStack = []; |
||
630 | return $this; |
||
631 | } |
||
632 | |||
633 | /** |
||
634 | * Run all of our rollback tasks. |
||
635 | * |
||
636 | * Note that Collection does not implement RollbackInterface, but |
||
637 | * it may still be used as a task inside another task collection |
||
638 | * (i.e. you can nest task collections, if desired). |
||
639 | */ |
||
640 | protected function runRollbackTasks() |
||
641 | { |
||
642 | $this->runTaskListIgnoringFailures($this->rollbackStack); |
||
643 | // Erase our rollback stack once we have finished rolling |
||
644 | // everything back. This will allow us to potentially use |
||
645 | // a command collection more than once (e.g. to retry a |
||
646 | // failed operation after doing some error recovery). |
||
647 | $this->rollbackStack = []; |
||
648 | } |
||
649 | |||
650 | /** |
||
651 | * @param \Robo\Contract\TaskInterface|\Robo\Collection\NestedCollectionInterface|\Robo\Contract\WrappedTaskInterface $task |
||
652 | * |
||
653 | * @return \Robo\Result |
||
654 | */ |
||
655 | protected function runSubtask($task) |
||
656 | { |
||
657 | $original = ($task instanceof WrappedTaskInterface) ? $task->original() : $task; |
||
658 | $this->setParentCollectionForTask($original, $this->getParentCollection()); |
||
659 | if ($original instanceof InflectionInterface) { |
||
660 | $original->inflect($this); |
||
661 | } |
||
662 | if ($original instanceof StateAwareInterface) { |
||
663 | $original->setState($this->getState()); |
||
664 | } |
||
665 | $this->doDeferredInitialization($original); |
||
0 ignored issues
–
show
It seems like
$original defined by $task instanceof \Robo\C...ask->original() : $task on line 657 can also be of type object<Robo\Collection\NestedCollectionInterface> ; however, Robo\Collection\Collecti...eferredInitialization() does only seem to accept object<Robo\Contract\TaskInterface> , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.
Loading history...
|
|||
666 | $taskResult = $task->run(); |
||
0 ignored issues
–
show
The method
run does only exist in Robo\Contract\TaskInterface , but not in Robo\Collection\NestedCollectionInterface .
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
Loading history...
|
|||
667 | $taskResult = Result::ensureResult($task, $taskResult); |
||
0 ignored issues
–
show
It seems like
$task defined by parameter $task on line 655 can also be of type object<Robo\Collection\NestedCollectionInterface> ; however, Robo\Result::ensureResult() does only seem to accept object<Robo\Contract\TaskInterface> , maybe add an additional type check?
This check looks at variables that have been passed in as parameters and are passed out again to other methods. If the outgoing method call has stricter type requirements than the method itself, an issue is raised. An additional type check may prevent trouble.
Loading history...
|
|||
668 | $this->doStateUpdates($original, $taskResult); |
||
0 ignored issues
–
show
It seems like
$original defined by $task instanceof \Robo\C...ask->original() : $task on line 657 can also be of type object<Robo\Collection\NestedCollectionInterface> ; however, Robo\Collection\Collection::doStateUpdates() does only seem to accept object<Robo\Contract\TaskInterface> , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.
Loading history...
|
|||
669 | return $taskResult; |
||
670 | } |
||
671 | |||
672 | /** |
||
673 | * @param \Robo\Contract\TaskInterface $task |
||
674 | * @param \Robo\State\Data $taskResult |
||
675 | */ |
||
676 | protected function doStateUpdates($task, Data $taskResult) |
||
677 | { |
||
678 | $this->updateState($taskResult); |
||
679 | $key = spl_object_hash($task); |
||
680 | if (array_key_exists($key, $this->messageStoreKeys)) { |
||
681 | $state = $this->getState(); |
||
682 | list($stateKey, $sourceKey) = $this->messageStoreKeys[$key]; |
||
683 | $value = empty($sourceKey) ? $taskResult->getMessage() : $taskResult[$sourceKey]; |
||
684 | $state[$stateKey] = $value; |
||
685 | } |
||
686 | } |
||
687 | |||
688 | /** |
||
689 | * @param \Robo\Contract\TaskInterface $task |
||
690 | * @param string $key |
||
691 | * @param string $source |
||
692 | * |
||
693 | * @return $this |
||
694 | */ |
||
695 | public function storeState($task, $key, $source = '') |
||
696 | { |
||
697 | $this->messageStoreKeys[spl_object_hash($task)] = [$key, $source]; |
||
698 | |||
699 | return $this; |
||
700 | } |
||
701 | |||
702 | /** |
||
703 | * @param \Robo\Contract\TaskInterface $task |
||
704 | * @param string $functionName |
||
705 | * @param string $stateKey |
||
706 | * |
||
707 | * @return $this |
||
708 | */ |
||
709 | public function deferTaskConfiguration($task, $functionName, $stateKey) |
||
710 | { |
||
711 | return $this->defer( |
||
712 | $task, |
||
713 | function ($task, $state) use ($functionName, $stateKey) { |
||
714 | $fn = [$task, $functionName]; |
||
715 | $value = $state[$stateKey]; |
||
716 | $fn($value); |
||
717 | } |
||
718 | ); |
||
719 | } |
||
720 | |||
721 | /** |
||
722 | * Defer execution of a callback function until just before a task |
||
723 | * runs. Use this time to provide more settings for the task, e.g. from |
||
724 | * the collection's shared state, which is populated with the results |
||
725 | * of previous test runs. |
||
726 | * |
||
727 | * @param \Robo\Contract\TaskInterface $task |
||
728 | * @param callable $callback |
||
729 | * |
||
730 | * @return $this |
||
731 | */ |
||
732 | public function defer($task, $callback) |
||
733 | { |
||
734 | $this->deferredCallbacks[spl_object_hash($task)][] = $callback; |
||
735 | |||
736 | return $this; |
||
737 | } |
||
738 | |||
739 | /** |
||
740 | * @param \Robo\Contract\TaskInterface $task |
||
741 | */ |
||
742 | protected function doDeferredInitialization($task) |
||
743 | { |
||
744 | // If the task is a state consumer, then call its receiveState method |
||
745 | if ($task instanceof \Robo\State\Consumer) { |
||
746 | $task->receiveState($this->getState()); |
||
747 | } |
||
748 | |||
749 | // Check and see if there are any deferred callbacks for this task. |
||
750 | $key = spl_object_hash($task); |
||
751 | if (!array_key_exists($key, $this->deferredCallbacks)) { |
||
752 | return; |
||
753 | } |
||
754 | |||
755 | // Call all of the deferred callbacks |
||
756 | foreach ($this->deferredCallbacks[$key] as $fn) { |
||
0 ignored issues
–
show
|
|||
757 | $fn($task, $this->getState()); |
||
758 | } |
||
759 | } |
||
760 | |||
761 | /** |
||
762 | * @param TaskInterface|NestedCollectionInterface|WrappedTaskInterface $task |
||
763 | * @param \Robo\Collection\CollectionInterface $parentCollection |
||
764 | */ |
||
765 | protected function setParentCollectionForTask($task, $parentCollection) |
||
766 | { |
||
767 | if ($task instanceof NestedCollectionInterface) { |
||
768 | $task->setParentCollection($parentCollection); |
||
769 | } |
||
770 | } |
||
771 | |||
772 | /** |
||
773 | * Run all of the tasks in a provided list, ignoring failures. |
||
774 | * |
||
775 | * You may force a failure by throwing a ForcedException in your rollback or |
||
776 | * completion task or callback. |
||
777 | * |
||
778 | * This is used to roll back or complete. |
||
779 | * |
||
780 | * @param \Robo\Contract\TaskInterface[] $taskList |
||
781 | */ |
||
782 | protected function runTaskListIgnoringFailures(array $taskList) |
||
783 | { |
||
784 | foreach ($taskList as $task) { |
||
785 | try { |
||
786 | $this->runSubtask($task); |
||
787 | } catch (AbortTasksException $abortTasksException) { |
||
788 | // If there's a forced exception, end the loop of tasks. |
||
789 | if ($message = $abortTasksException->getMessage()) { |
||
790 | $this->logger()->notice($message); |
||
791 | } |
||
792 | break; |
||
793 | } catch (\Exception $e) { |
||
794 | // Ignore rollback failures. |
||
795 | } |
||
796 | } |
||
797 | } |
||
798 | |||
799 | /** |
||
800 | * Give all of our tasks to the provided collection builder. |
||
801 | * |
||
802 | * @param \Robo\Collection\CollectionBuilder $builder |
||
803 | */ |
||
804 | public function transferTasks($builder) |
||
805 | { |
||
806 | foreach ($this->taskList as $name => $taskGroup) { |
||
807 | // TODO: We are abandoning all of our before and after tasks here. |
||
808 | // At the moment, transferTasks is only called under conditions where |
||
809 | // there will be none of these, but care should be taken if that changes. |
||
810 | $task = $taskGroup->getTask(); |
||
811 | $builder->addTaskToCollection($task); |
||
812 | } |
||
813 | $this->reset(); |
||
814 | } |
||
815 | } |
||
816 |
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.