Completed
Push — master ( a20e35...2a3e96 )
by Greg
03:21
created

src/Collection/Collection.php (6 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
namespace Robo\Collection;
3
4
use Robo\Result;
5
use Robo\State\Data;
6
use Psr\Log\LogLevel;
7
use Robo\Contract\TaskInterface;
8
use Robo\Task\StackBasedTask;
9
use Robo\Task\BaseTask;
10
use Robo\TaskInfo;
11
use Robo\Contract\WrappedTaskInterface;
12
use Robo\Exception\TaskException;
13
use Robo\Exception\TaskExitException;
14
use Robo\Contract\CommandInterface;
15
16
use Robo\Contract\InflectionInterface;
17
use Robo\State\StateAwareInterface;
18
use Robo\State\StateAwareTrait;
19
20
/**
21
 * Group tasks into a collection that run together. Supports
22
 * rollback operations for handling error conditions.
23
 *
24
 * This is an internal class. Clients should use a CollectionBuilder
25
 * rather than direct use of the Collection class.  @see CollectionBuilder.
26
 *
27
 * Below, the example FilesystemStack task is added to a collection,
28
 * and associated with a rollback task.  If any of the operations in
29
 * the FilesystemStack, or if any of the other tasks also added to
30
 * the task collection should fail, then the rollback function is
31
 * called. Here, taskDeleteDir is used to remove partial results
32
 * of an unfinished task.
33
 */
34
class Collection extends BaseTask implements CollectionInterface, CommandInterface, StateAwareInterface
35
{
36
    use StateAwareTrait;
37
38
    /**
39
     * @var \Robo\Collection\Element[]
40
     */
41
    protected $taskList = [];
42
43
    /**
44
     * @var TaskInterface[]
45
     */
46
    protected $rollbackStack = [];
47
48
    /**
49
     * @var TaskInterface[]
50
     */
51
    protected $completionStack = [];
52
53
    /**
54
     * @var CollectionInterface
55
     */
56
    protected $parentCollection;
57
58
    /**
59
     * @var callable[]
60
     */
61
    protected $deferredCallbacks = [];
62
63
    /**
64
     * @var string[]
65
     */
66
    protected $messageStoreKeys = [];
67
68
    /**
69
     * Constructor.
70
     */
71
    public function __construct()
72
    {
73
        $this->resetState();
74
    }
75
76
    public function setProgressBarAutoDisplayInterval($interval)
77
    {
78
        if (!$this->progressIndicator) {
79
            return;
80
        }
81
        return $this->progressIndicator->setProgressBarAutoDisplayInterval($interval);
82
    }
83
84
    /**
85
     * {@inheritdoc}
86
     */
87
    public function add(TaskInterface $task, $name = self::UNNAMEDTASK)
88
    {
89
        $task = new CompletionWrapper($this, $task);
90
        $this->addToTaskList($name, $task);
91
        return $this;
92
    }
93
94
    /**
95
     * {@inheritdoc}
96
     */
97
    public function addCode(callable $code, $name = self::UNNAMEDTASK)
98
    {
99
        return $this->add(new CallableTask($code, $this), $name);
100
    }
101
102
    /**
103
     * {@inheritdoc}
104
     */
105
    public function addIterable($iterable, callable $code)
106
    {
107
        $callbackTask = (new IterationTask($iterable, $code, $this))->inflect($this);
108
        return $this->add($callbackTask);
109
    }
110
111
    /**
112
     * {@inheritdoc}
113
     */
114
    public function rollback(TaskInterface $rollbackTask)
115
    {
116
        // Rollback tasks always try as hard as they can, and never report failures.
117
        $rollbackTask = $this->ignoreErrorsTaskWrapper($rollbackTask);
118
        return $this->wrapAndRegisterRollback($rollbackTask);
119
    }
120
121
    /**
122
     * {@inheritdoc}
123
     */
124
    public function rollbackCode(callable $rollbackCode)
125
    {
126
        // Rollback tasks always try as hard as they can, and never report failures.
127
        $rollbackTask = $this->ignoreErrorsCodeWrapper($rollbackCode);
128
        return $this->wrapAndRegisterRollback($rollbackTask);
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134
    public function completion(TaskInterface $completionTask)
135
    {
136
        $collection = $this;
137
        $completionRegistrationTask = new CallableTask(
138
            function () use ($collection, $completionTask) {
139
140
                $collection->registerCompletion($completionTask);
141
            },
142
            $this
143
        );
144
        $this->addToTaskList(self::UNNAMEDTASK, $completionRegistrationTask);
145
        return $this;
146
    }
147
148
    /**
149
     * {@inheritdoc}
150
     */
151
    public function completionCode(callable $completionTask)
152
    {
153
        $completionTask = new CallableTask($completionTask, $this);
154
        return $this->completion($completionTask);
155
    }
156
157
    /**
158
     * {@inheritdoc}
159
     */
160
    public function before($name, $task, $nameOfTaskToAdd = self::UNNAMEDTASK)
161
    {
162
        return $this->addBeforeOrAfter(__FUNCTION__, $name, $task, $nameOfTaskToAdd);
163
    }
164
165
    /**
166
     * {@inheritdoc}
167
     */
168
    public function after($name, $task, $nameOfTaskToAdd = self::UNNAMEDTASK)
169
    {
170
        return $this->addBeforeOrAfter(__FUNCTION__, $name, $task, $nameOfTaskToAdd);
171
    }
172
173
    /**
174
     * {@inheritdoc}
175
     */
176
    public function progressMessage($text, $context = [], $level = LogLevel::NOTICE)
177
    {
178
        $context += ['name' => 'Progress'];
179
        $context += TaskInfo::getTaskContext($this);
180
        return $this->addCode(
181
            function () use ($level, $text, $context) {
182
                $context += $this->getState()->getData();
183
                $this->printTaskOutput($level, $text, $context);
0 ignored issues
show
It seems like $level defined by parameter $level on line 176 can also be of type object<Psr\Log\LogLevel>; however, Robo\Common\TaskIO::printTaskOutput() does only seem to accept string, 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...
184
            }
185
        );
186
    }
187
188
    /**
189
     * @param \Robo\Contract\TaskInterface $rollbackTask
190
     *
191
     * @return $this
192
     */
193
    protected function wrapAndRegisterRollback(TaskInterface $rollbackTask)
194
    {
195
        $collection = $this;
196
        $rollbackRegistrationTask = new CallableTask(
197
            function () use ($collection, $rollbackTask) {
198
                $collection->registerRollback($rollbackTask);
199
            },
200
            $this
201
        );
202
        $this->addToTaskList(self::UNNAMEDTASK, $rollbackRegistrationTask);
203
        return $this;
204
    }
205
206
    /**
207
     * Add either a 'before' or 'after' function or task.
208
     *
209
     * @param string $method
210
     * @param string $name
211
     * @param callable|TaskInterface $task
212
     * @param string $nameOfTaskToAdd
213
     *
214
     * @return $this
215
     */
216
    protected function addBeforeOrAfter($method, $name, $task, $nameOfTaskToAdd)
217
    {
218
        if (is_callable($task)) {
219
            $task = new CallableTask($task, $this);
220
        }
221
        $existingTask = $this->namedTask($name);
222
        $fn = [$existingTask, $method];
223
        call_user_func($fn, $task, $nameOfTaskToAdd);
224
        return $this;
225
    }
226
227
    /**
228
     * Wrap the provided task in a wrapper that will ignore
229
     * any errors or exceptions that may be produced.  This
230
     * is useful, for example, in adding optional cleanup tasks
231
     * at the beginning of a task collection, to remove previous
232
     * results which may or may not exist.
233
     *
234
     * TODO: Provide some way to specify which sort of errors
235
     * are ignored, so that 'file not found' may be ignored,
236
     * but 'permission denied' reported?
237
     *
238
     * @param \Robo\Contract\TaskInterface $task
239
     *
240
     * @return \Robo\Collection\CallableTask
241
     */
242
    public function ignoreErrorsTaskWrapper(TaskInterface $task)
243
    {
244
        // If the task is a stack-based task, then tell it
245
        // to try to run all of its operations, even if some
246
        // of them fail.
247
        if ($task instanceof StackBasedTask) {
248
            $task->stopOnFail(false);
249
        }
250
        $ignoreErrorsInTask = function () use ($task) {
251
            $data = [];
252
            try {
253
                $result = $this->runSubtask($task);
254
                $message = $result->getMessage();
255
                $data = $result->getData();
256
                $data['exitcode'] = $result->getExitCode();
257
            } catch (\Exception $e) {
258
                $message = $e->getMessage();
259
            }
260
261
            return Result::success($task, $message, $data);
262
        };
263
        // Wrap our ignore errors callable in a task.
264
        return new CallableTask($ignoreErrorsInTask, $this);
265
    }
266
267
    /**
268
     * @param callable $task
269
     *
270
     * @return \Robo\Collection\CallableTask
271
     */
272
    public function ignoreErrorsCodeWrapper(callable $task)
273
    {
274
        return $this->ignoreErrorsTaskWrapper(new CallableTask($task, $this));
275
    }
276
277
    /**
278
     * Return the list of task names added to this collection.
279
     *
280
     * @return array
281
     */
282
    public function taskNames()
283
    {
284
        return array_keys($this->taskList);
285
    }
286
287
    /**
288
     * Test to see if a specified task name exists.
289
     * n.b. before() and after() require that the named
290
     * task exist; use this function to test first, if
291
     * unsure.
292
     *
293
     * @param string $name
294
     *
295
     * @return bool
296
     */
297
    public function hasTask($name)
298
    {
299
        return array_key_exists($name, $this->taskList);
300
    }
301
302
    /**
303
     * Find an existing named task.
304
     *
305
     * @param string $name
306
     *   The name of the task to insert before.  The named task MUST exist.
307
     *
308
     * @return Element
309
     *   The task group for the named task. Generally this is only
310
     *   used to call 'before()' and 'after()'.
311
     */
312
    protected function namedTask($name)
313
    {
314
        if (!$this->hasTask($name)) {
315
            throw new \RuntimeException("Could not find task named $name");
316
        }
317
        return $this->taskList[$name];
318
    }
319
320
    /**
321
     * Add a list of tasks to our task collection.
322
     *
323
     * @param TaskInterface[] $tasks
324
     *   An array of tasks to run with rollback protection
325
     *
326
     * @return $this
327
     */
328
    public function addTaskList(array $tasks)
329
    {
330
        foreach ($tasks as $name => $task) {
331
            $this->add($task, $name);
332
        }
333
        return $this;
334
    }
335
336
    /**
337
     * Add the provided task to our task list.
338
     *
339
     * @param string $name
340
     * @param \Robo\Contract\TaskInterface $task
341
     *
342
     * @return \Robo\Collection\Collection
343
     */
344
    protected function addToTaskList($name, TaskInterface $task)
345
    {
346
        // All tasks are stored in a task group so that we have a place
347
        // to hang 'before' and 'after' tasks.
348
        $taskGroup = new Element($task);
349
        return $this->addCollectionElementToTaskList($name, $taskGroup);
350
    }
351
352
    /**
353
     * @param int|string $name
354
     * @param \Robo\Collection\Element $taskGroup
355
     *
356
     * @return $this
357
     */
358
    protected function addCollectionElementToTaskList($name, Element $taskGroup)
359
    {
360
        // If a task name is not provided, then we'll let php pick
361
        // the array index.
362
        if (Result::isUnnamed($name)) {
363
            $this->taskList[] = $taskGroup;
364
            return $this;
365
        }
366
        // If we are replacing an existing task with the
367
        // same name, ensure that our new task is added to
368
        // the end.
369
        $this->taskList[$name] = $taskGroup;
370
        return $this;
371
    }
372
373
    /**
374
     * Set the parent collection. This is necessary so that nested
375
     * collections' rollback and completion tasks can be added to the
376
     * top-level collection, ensuring that the rollbacks for a collection
377
     * will run if any later task fails.
378
     *
379
     * @param \Robo\Collection\NestedCollectionInterface $parentCollection
380
     *
381
     * @return $this
382
     */
383
    public function setParentCollection(NestedCollectionInterface $parentCollection)
384
    {
385
        $this->parentCollection = $parentCollection;
0 ignored issues
show
Documentation Bug introduced by
$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...
386
        return $this;
387
    }
388
389
    /**
390
     * Get the appropriate parent collection to use
391
     *
392
     * @return CollectionInterface
393
     */
394
    public function getParentCollection()
395
    {
396
        return $this->parentCollection ? $this->parentCollection : $this;
397
    }
398
399
    /**
400
     * Register a rollback task to run if there is any failure.
401
     *
402
     * Clients are free to add tasks to the rollback stack as
403
     * desired; however, usually it is preferable to call
404
     * Collection::rollback() instead.  With that function,
405
     * the rollback function will only be called if all of the
406
     * tasks added before it complete successfully, AND some later
407
     * task fails.
408
     *
409
     * One example of a good use-case for registering a callback
410
     * function directly is to add a task that sends notification
411
     * when a task fails.
412
     *
413
     * @param TaskInterface $rollbackTask
414
     *   The rollback task to run on failure.
415
     */
416
    public function registerRollback(TaskInterface $rollbackTask)
417
    {
418
        if ($this->parentCollection) {
419
            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...
420
        }
421
        if ($rollbackTask) {
422
            array_unshift($this->rollbackStack, $rollbackTask);
423
        }
424
    }
425
426
    /**
427
     * Register a completion task to run once all other tasks finish.
428
     * Completion tasks run whether or not a rollback operation was
429
     * triggered. They do not trigger rollbacks if they fail.
430
     *
431
     * The typical use-case for a completion function is to clean up
432
     * temporary objects (e.g. temporary folders).  The preferred
433
     * way to do that, though, is to use Temporary::wrap().
434
     *
435
     * On failures, completion tasks will run after all rollback tasks.
436
     * If one task collection is nested inside another task collection,
437
     * then the nested collection's completion tasks will run as soon as
438
     * the nested task completes; they are not deferred to the end of
439
     * the containing collection's execution.
440
     *
441
     * @param TaskInterface $completionTask
442
     *   The completion task to run at the end of all other operations.
443
     */
444
    public function registerCompletion(TaskInterface $completionTask)
445
    {
446
        if ($this->parentCollection) {
447
            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...
448
        }
449
        if ($completionTask) {
450
            // Completion tasks always try as hard as they can, and never report failures.
451
            $completionTask = $this->ignoreErrorsTaskWrapper($completionTask);
452
            $this->completionStack[] = $completionTask;
453
        }
454
    }
455
456
    /**
457
     * Return the count of steps in this collection
458
     *
459
     * @return int
460
     */
461
    public function progressIndicatorSteps()
462
    {
463
        $steps = 0;
464
        foreach ($this->taskList as $name => $taskGroup) {
465
            $steps += $taskGroup->progressIndicatorSteps();
466
        }
467
        return $steps;
468
    }
469
470
    /**
471
     * A Collection of tasks can provide a command via `getCommand()`
472
     * if it contains a single task, and that task implements CommandInterface.
473
     *
474
     * @return string
475
     *
476
     * @throws \Robo\Exception\TaskException
477
     */
478
    public function getCommand()
479
    {
480
        if (empty($this->taskList)) {
481
            return '';
482
        }
483
484
        if (count($this->taskList) > 1) {
485
            // TODO: We could potentially iterate over the items in the collection
486
            // and concatenate the result of getCommand() from each one, and fail
487
            // only if we encounter a command that is not a CommandInterface.
488
            throw new TaskException($this, "getCommand() does not work on arbitrary collections of tasks.");
489
        }
490
491
        $taskElement = reset($this->taskList);
492
        $task = $taskElement->getTask();
493
        $task = ($task instanceof WrappedTaskInterface) ? $task->original() : $task;
494
        if ($task instanceof CommandInterface) {
495
            return $task->getCommand();
496
        }
497
498
        throw new TaskException($task, get_class($task) . " does not implement CommandInterface, so can't be used to provide a command");
499
    }
500
501
    /**
502
     * Run our tasks, and roll back if necessary.
503
     *
504
     * @return \Robo\Result
505
     */
506
    public function run()
507
    {
508
        $result = $this->runWithoutCompletion();
509
        $this->complete();
510
        return $result;
511
    }
512
513
    /**
514
     * @return \Robo\Result
515
     */
516
    private function runWithoutCompletion()
517
    {
518
        $result = Result::success($this);
519
520
        if (empty($this->taskList)) {
521
            return $result;
522
        }
523
524
        $this->startProgressIndicator();
525
        if ($result->wasSuccessful()) {
526
            foreach ($this->taskList as $name => $taskGroup) {
527
                $taskList = $taskGroup->getTaskList();
528
                $result = $this->runTaskList($name, $taskList, $result);
529
                if (!$result->wasSuccessful()) {
530
                    $this->fail();
531
                    return $result;
532
                }
533
            }
534
            $this->taskList = [];
535
        }
536
        $this->stopProgressIndicator();
537
        $result['time'] = $this->getExecutionTime();
538
539
        return $result;
540
    }
541
542
    /**
543
     * Run every task in a list, but only up to the first failure.
544
     * Return the failing result, or success if all tasks run.
545
     *
546
     * @param string $name
547
     * @param TaskInterface[] $taskList
548
     * @param \Robo\Result $result
549
     *
550
     * @return \Robo\Result
551
     *
552
     * @throws \Robo\Exception\TaskExitException
553
     */
554
    private function runTaskList($name, array $taskList, Result $result)
555
    {
556
        try {
557
            foreach ($taskList as $taskName => $task) {
558
                $taskResult = $this->runSubtask($task);
559
                $this->advanceProgressIndicator();
560
                // If the current task returns an error code, then stop
561
                // execution and signal a rollback.
562
                if (!$taskResult->wasSuccessful()) {
563
                    return $taskResult;
564
                }
565
                // We accumulate our results into a field so that tasks that
566
                // have a reference to the collection may examine and modify
567
                // the incremental results, if they wish.
568
                $key = Result::isUnnamed($taskName) ? $name : $taskName;
569
                $result->accumulate($key, $taskResult);
570
                // The result message will be the message of the last task executed.
571
                $result->setMessage($taskResult->getMessage());
572
            }
573
        } catch (TaskExitException $exitException) {
574
            $this->fail();
575
            throw $exitException;
576
        } catch (\Exception $e) {
577
            // Tasks typically should not throw, but if one does, we will
578
            // convert it into an error and roll back.
579
            return Result::fromException($task, $e, $result->getData());
580
        }
581
        return $result;
582
    }
583
584
    /**
585
     * Force the rollback functions to run
586
     *
587
     * @return $this
588
     */
589
    public function fail()
590
    {
591
        $this->disableProgressIndicator();
592
        $this->runRollbackTasks();
593
        $this->complete();
594
        return $this;
595
    }
596
597
    /**
598
     * Force the completion functions to run
599
     *
600
     * @return $this
601
     */
602
    public function complete()
603
    {
604
        $this->detatchProgressIndicator();
605
        $this->runTaskListIgnoringFailures($this->completionStack);
606
        $this->reset();
607
        return $this;
608
    }
609
610
    /**
611
     * Reset this collection, removing all tasks.
612
     *
613
     * @return $this
614
     */
615
    public function reset()
616
    {
617
        $this->taskList = [];
618
        $this->completionStack = [];
619
        $this->rollbackStack = [];
620
        return $this;
621
    }
622
623
    /**
624
     * Run all of our rollback tasks.
625
     *
626
     * Note that Collection does not implement RollbackInterface, but
627
     * it may still be used as a task inside another task collection
628
     * (i.e. you can nest task collections, if desired).
629
     */
630
    protected function runRollbackTasks()
631
    {
632
        $this->runTaskListIgnoringFailures($this->rollbackStack);
633
        // Erase our rollback stack once we have finished rolling
634
        // everything back.  This will allow us to potentially use
635
        // a command collection more than once (e.g. to retry a
636
        // failed operation after doing some error recovery).
637
        $this->rollbackStack = [];
638
    }
639
640
    /**
641
     * @param TaskInterface|NestedCollectionInterface|WrappedTaskInterface $task
642
     *
643
     * @return \Robo\Result
644
     */
645
    protected function runSubtask($task)
646
    {
647
        $original = ($task instanceof WrappedTaskInterface) ? $task->original() : $task;
648
        $this->setParentCollectionForTask($original, $this->getParentCollection());
649
        if ($original instanceof InflectionInterface) {
650
            $original->inflect($this);
651
        }
652
        if ($original instanceof StateAwareInterface) {
653
            $original->setState($this->getState());
654
        }
655
        $this->doDeferredInitialization($original);
656
        $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

  1. Add an additional type-check:

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

    function someFunction(B $x) { /** ... */ }
    
Loading history...
657
        $taskResult = Result::ensureResult($task, $taskResult);
658
        $this->doStateUpdates($original, $taskResult);
659
        return $taskResult;
660
    }
661
662
    protected function doStateUpdates($task, Data $taskResult)
663
    {
664
        $this->updateState($taskResult);
665
        $key = spl_object_hash($task);
666
        if (array_key_exists($key, $this->messageStoreKeys)) {
667
            $state = $this->getState();
668
            list($stateKey, $sourceKey) = $this->messageStoreKeys[$key];
669
            $value = empty($sourceKey) ? $taskResult->getMessage() : $taskResult[$sourceKey];
670
            $state[$stateKey] = $value;
671
        }
672
    }
673
674
    public function storeState($task, $key, $source = '')
675
    {
676
        $this->messageStoreKeys[spl_object_hash($task)] = [$key, $source];
677
678
        return $this;
679
    }
680
681
    public function deferTaskConfiguration($task, $functionName, $stateKey)
682
    {
683
        return $this->defer(
684
            $task,
685
            function ($task, $state) use ($functionName, $stateKey) {
686
                $fn = [$task, $functionName];
687
                $value = $state[$stateKey];
688
                $fn($value);
689
            }
690
        );
691
    }
692
693
    /**
694
     * Defer execution of a callback function until just before a task
695
     * runs. Use this time to provide more settings for the task, e.g. from
696
     * the collection's shared state, which is populated with the results
697
     * of previous test runs.
698
     */
699
    public function defer($task, $callback)
700
    {
701
        $this->deferredCallbacks[spl_object_hash($task)][] = $callback;
702
703
        return $this;
704
    }
705
706
    protected function doDeferredInitialization($task)
707
    {
708
        // If the task is a state consumer, then call its receiveState method
709
        if ($task instanceof \Robo\State\Consumer) {
710
            $task->receiveState($this->getState());
711
        }
712
713
        // Check and see if there are any deferred callbacks for this task.
714
        $key = spl_object_hash($task);
715
        if (!array_key_exists($key, $this->deferredCallbacks)) {
716
            return;
717
        }
718
719
        // Call all of the deferred callbacks
720
        foreach ($this->deferredCallbacks[$key] as $fn) {
0 ignored issues
show
The expression $this->deferredCallbacks[$key] of type callable is not traversable.
Loading history...
721
            $fn($task, $this->getState());
722
        }
723
    }
724
725
    /**
726
     * @param TaskInterface|NestedCollectionInterface|WrappedTaskInterface $task
727
     * @param $parentCollection
728
     */
729
    protected function setParentCollectionForTask($task, $parentCollection)
730
    {
731
        if ($task instanceof NestedCollectionInterface) {
732
            $task->setParentCollection($parentCollection);
733
        }
734
    }
735
736
    /**
737
     * Run all of the tasks in a provided list, ignoring failures.
738
     * This is used to roll back or complete.
739
     *
740
     * @param TaskInterface[] $taskList
741
     */
742
    protected function runTaskListIgnoringFailures(array $taskList)
743
    {
744
        foreach ($taskList as $task) {
745
            try {
746
                $this->runSubtask($task);
747
            } catch (\Exception $e) {
748
                // Ignore rollback failures.
749
            }
750
        }
751
    }
752
753
    /**
754
     * Give all of our tasks to the provided collection builder.
755
     *
756
     * @param CollectionBuilder $builder
757
     */
758
    public function transferTasks($builder)
759
    {
760
        foreach ($this->taskList as $name => $taskGroup) {
761
            // TODO: We are abandoning all of our before and after tasks here.
762
            // At the moment, transferTasks is only called under conditions where
763
            // there will be none of these, but care should be taken if that changes.
764
            $task = $taskGroup->getTask();
765
            $builder->addTaskToCollection($task);
766
        }
767
        $this->reset();
768
    }
769
}
770