Completed
Pull Request — master (#580)
by Greg
03:16
created

Collection::defer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 2
1
<?php
2
namespace Robo\Collection;
3
4
use Robo\Result;
5
use Robo\ResultData;
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
                $this->printTaskOutput($level, $text, $context);
0 ignored issues
show
Bug introduced by
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...
183
            }
184
        );
185
    }
186
187
    /**
188
     * @param \Robo\Contract\TaskInterface $rollbackTask
189
     *
190
     * @return $this
191
     */
192
    protected function wrapAndRegisterRollback(TaskInterface $rollbackTask)
193
    {
194
        $collection = $this;
195
        $rollbackRegistrationTask = new CallableTask(
196
            function () use ($collection, $rollbackTask) {
197
                $collection->registerRollback($rollbackTask);
198
            },
199
            $this
200
        );
201
        $this->addToTaskList(self::UNNAMEDTASK, $rollbackRegistrationTask);
202
        return $this;
203
    }
204
205
    /**
206
     * Add either a 'before' or 'after' function or task.
207
     *
208
     * @param string $method
209
     * @param string $name
210
     * @param callable|TaskInterface $task
211
     * @param string $nameOfTaskToAdd
212
     *
213
     * @return $this
214
     */
215
    protected function addBeforeOrAfter($method, $name, $task, $nameOfTaskToAdd)
216
    {
217
        if (is_callable($task)) {
218
            $task = new CallableTask($task, $this);
219
        }
220
        $existingTask = $this->namedTask($name);
221
        $fn = [$existingTask, $method];
222
        call_user_func($fn, $task, $nameOfTaskToAdd);
223
        return $this;
224
    }
225
226
    /**
227
     * Wrap the provided task in a wrapper that will ignore
228
     * any errors or exceptions that may be produced.  This
229
     * is useful, for example, in adding optional cleanup tasks
230
     * at the beginning of a task collection, to remove previous
231
     * results which may or may not exist.
232
     *
233
     * TODO: Provide some way to specify which sort of errors
234
     * are ignored, so that 'file not found' may be ignored,
235
     * but 'permission denied' reported?
236
     *
237
     * @param \Robo\Contract\TaskInterface $task
238
     *
239
     * @return \Robo\Collection\CallableTask
240
     */
241
    public function ignoreErrorsTaskWrapper(TaskInterface $task)
242
    {
243
        // If the task is a stack-based task, then tell it
244
        // to try to run all of its operations, even if some
245
        // of them fail.
246
        if ($task instanceof StackBasedTask) {
247
            $task->stopOnFail(false);
248
        }
249
        $ignoreErrorsInTask = function () use ($task) {
250
            $data = [];
251
            try {
252
                $result = $this->runSubtask($task);
253
                $message = $result->getMessage();
254
                $data = $result->getData();
255
                $data['exitcode'] = $result->getExitCode();
256
            } catch (\Exception $e) {
257
                $message = $e->getMessage();
258
            }
259
260
            return Result::success($task, $message, $data);
261
        };
262
        // Wrap our ignore errors callable in a task.
263
        return new CallableTask($ignoreErrorsInTask, $this);
264
    }
265
266
    /**
267
     * @param callable $task
268
     *
269
     * @return \Robo\Collection\CallableTask
270
     */
271
    public function ignoreErrorsCodeWrapper(callable $task)
272
    {
273
        return $this->ignoreErrorsTaskWrapper(new CallableTask($task, $this));
274
    }
275
276
    /**
277
     * Return the list of task names added to this collection.
278
     *
279
     * @return array
280
     */
281
    public function taskNames()
282
    {
283
        return array_keys($this->taskList);
284
    }
285
286
    /**
287
     * Test to see if a specified task name exists.
288
     * n.b. before() and after() require that the named
289
     * task exist; use this function to test first, if
290
     * unsure.
291
     *
292
     * @param string $name
293
     *
294
     * @return bool
295
     */
296
    public function hasTask($name)
297
    {
298
        return array_key_exists($name, $this->taskList);
299
    }
300
301
    /**
302
     * Find an existing named task.
303
     *
304
     * @param string $name
305
     *   The name of the task to insert before.  The named task MUST exist.
306
     *
307
     * @return Element
308
     *   The task group for the named task. Generally this is only
309
     *   used to call 'before()' and 'after()'.
310
     */
311
    protected function namedTask($name)
312
    {
313
        if (!$this->hasTask($name)) {
314
            throw new \RuntimeException("Could not find task named $name");
315
        }
316
        return $this->taskList[$name];
317
    }
318
319
    /**
320
     * Add a list of tasks to our task collection.
321
     *
322
     * @param TaskInterface[] $tasks
323
     *   An array of tasks to run with rollback protection
324
     *
325
     * @return $this
326
     */
327
    public function addTaskList(array $tasks)
328
    {
329
        foreach ($tasks as $name => $task) {
330
            $this->add($task, $name);
331
        }
332
        return $this;
333
    }
334
335
    /**
336
     * Add the provided task to our task list.
337
     *
338
     * @param string $name
339
     * @param \Robo\Contract\TaskInterface $task
340
     *
341
     * @return \Robo\Collection\Collection
342
     */
343
    protected function addToTaskList($name, TaskInterface $task)
344
    {
345
        // All tasks are stored in a task group so that we have a place
346
        // to hang 'before' and 'after' tasks.
347
        $taskGroup = new Element($task);
348
        return $this->addCollectionElementToTaskList($name, $taskGroup);
349
    }
350
351
    /**
352
     * @param int|string $name
353
     * @param \Robo\Collection\Element $taskGroup
354
     *
355
     * @return $this
356
     */
357
    protected function addCollectionElementToTaskList($name, Element $taskGroup)
358
    {
359
        // If a task name is not provided, then we'll let php pick
360
        // the array index.
361
        if (Result::isUnnamed($name)) {
362
            $this->taskList[] = $taskGroup;
363
            return $this;
364
        }
365
        // If we are replacing an existing task with the
366
        // same name, ensure that our new task is added to
367
        // the end.
368
        $this->taskList[$name] = $taskGroup;
369
        return $this;
370
    }
371
372
    /**
373
     * Set the parent collection. This is necessary so that nested
374
     * collections' rollback and completion tasks can be added to the
375
     * top-level collection, ensuring that the rollbacks for a collection
376
     * will run if any later task fails.
377
     *
378
     * @param \Robo\Collection\NestedCollectionInterface $parentCollection
379
     *
380
     * @return $this
381
     */
382
    public function setParentCollection(NestedCollectionInterface $parentCollection)
383
    {
384
        $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...
385
        return $this;
386
    }
387
388
    /**
389
     * Get the appropriate parent collection to use
390
     *
391
     * @return CollectionInterface
392
     */
393
    public function getParentCollection()
394
    {
395
        return $this->parentCollection ? $this->parentCollection : $this;
396
    }
397
398
    /**
399
     * Register a rollback task to run if there is any failure.
400
     *
401
     * Clients are free to add tasks to the rollback stack as
402
     * desired; however, usually it is preferable to call
403
     * Collection::rollback() instead.  With that function,
404
     * the rollback function will only be called if all of the
405
     * tasks added before it complete successfully, AND some later
406
     * task fails.
407
     *
408
     * One example of a good use-case for registering a callback
409
     * function directly is to add a task that sends notification
410
     * when a task fails.
411
     *
412
     * @param TaskInterface $rollbackTask
413
     *   The rollback task to run on failure.
414
     */
415
    public function registerRollback(TaskInterface $rollbackTask)
416
    {
417
        if ($this->parentCollection) {
418
            return $this->parentCollection->registerRollback($rollbackTask);
0 ignored issues
show
Bug introduced by
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...
419
        }
420
        if ($rollbackTask) {
421
            $this->rollbackStack[] = $rollbackTask;
422
        }
423
    }
424
425
    /**
426
     * Register a completion task to run once all other tasks finish.
427
     * Completion tasks run whether or not a rollback operation was
428
     * triggered. They do not trigger rollbacks if they fail.
429
     *
430
     * The typical use-case for a completion function is to clean up
431
     * temporary objects (e.g. temporary folders).  The preferred
432
     * way to do that, though, is to use Temporary::wrap().
433
     *
434
     * On failures, completion tasks will run after all rollback tasks.
435
     * If one task collection is nested inside another task collection,
436
     * then the nested collection's completion tasks will run as soon as
437
     * the nested task completes; they are not deferred to the end of
438
     * the containing collection's execution.
439
     *
440
     * @param TaskInterface $completionTask
441
     *   The completion task to run at the end of all other operations.
442
     */
443
    public function registerCompletion(TaskInterface $completionTask)
444
    {
445
        if ($this->parentCollection) {
446
            return $this->parentCollection->registerCompletion($completionTask);
0 ignored issues
show
Bug introduced by
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...
447
        }
448
        if ($completionTask) {
449
            // Completion tasks always try as hard as they can, and never report failures.
450
            $completionTask = $this->ignoreErrorsTaskWrapper($completionTask);
451
            $this->completionStack[] = $completionTask;
452
        }
453
    }
454
455
    /**
456
     * Return the count of steps in this collection
457
     *
458
     * @return int
459
     */
460
    public function progressIndicatorSteps()
461
    {
462
        $steps = 0;
463
        foreach ($this->taskList as $name => $taskGroup) {
464
            $steps += $taskGroup->progressIndicatorSteps();
465
        }
466
        return $steps;
467
    }
468
469
    /**
470
     * A Collection of tasks can provide a command via `getCommand()`
471
     * if it contains a single task, and that task implements CommandInterface.
472
     *
473
     * @return string
474
     *
475
     * @throws \Robo\Exception\TaskException
476
     */
477
    public function getCommand()
478
    {
479
        if (empty($this->taskList)) {
480
            return '';
481
        }
482
483
        if (count($this->taskList) > 1) {
484
            // TODO: We could potentially iterate over the items in the collection
485
            // and concatenate the result of getCommand() from each one, and fail
486
            // only if we encounter a command that is not a CommandInterface.
487
            throw new TaskException($this, "getCommand() does not work on arbitrary collections of tasks.");
488
        }
489
490
        $taskElement = reset($this->taskList);
491
        $task = $taskElement->getTask();
492
        $task = ($task instanceof WrappedTaskInterface) ? $task->original() : $task;
493
        if ($task instanceof CommandInterface) {
494
            return $task->getCommand();
495
        }
496
497
        throw new TaskException($task, get_class($task) . " does not implement CommandInterface, so can't be used to provide a command");
498
    }
499
500
    /**
501
     * Run our tasks, and roll back if necessary.
502
     *
503
     * @return \Robo\Result
504
     */
505
    public function run()
506
    {
507
        $result = $this->runWithoutCompletion();
508
        $this->complete();
509
        return $result;
510
    }
511
512
    /**
513
     * @return \Robo\Result
514
     */
515
    private function runWithoutCompletion()
516
    {
517
        $result = Result::success($this);
518
519
        if (empty($this->taskList)) {
520
            return $result;
521
        }
522
523
        $this->startProgressIndicator();
524
        if ($result->wasSuccessful()) {
525
            foreach ($this->taskList as $name => $taskGroup) {
526
                $taskList = $taskGroup->getTaskList();
527
                $result = $this->runTaskList($name, $taskList, $result);
528
                if (!$result->wasSuccessful()) {
529
                    $this->fail();
530
                    return $result;
531
                }
532
            }
533
            $this->taskList = [];
534
        }
535
        $this->stopProgressIndicator();
536
        $result['time'] = $this->getExecutionTime();
537
538
        return $result;
539
    }
540
541
    /**
542
     * Run every task in a list, but only up to the first failure.
543
     * Return the failing result, or success if all tasks run.
544
     *
545
     * @param string $name
546
     * @param TaskInterface[] $taskList
547
     * @param \Robo\Result $result
548
     *
549
     * @return \Robo\Result
550
     *
551
     * @throws \Robo\Exception\TaskExitException
552
     */
553
    private function runTaskList($name, array $taskList, Result $result)
554
    {
555
        try {
556
            foreach ($taskList as $taskName => $task) {
557
                $taskResult = $this->runSubtask($task);
558
                $this->advanceProgressIndicator();
559
                // If the current task returns an error code, then stop
560
                // execution and signal a rollback.
561
                if (!$taskResult->wasSuccessful()) {
562
                    return $taskResult;
563
                }
564
                // We accumulate our results into a field so that tasks that
565
                // have a reference to the collection may examine and modify
566
                // the incremental results, if they wish.
567
                $key = Result::isUnnamed($taskName) ? $name : $taskName;
568
                $result->accumulate($key, $taskResult);
569
            }
570
        } catch (TaskExitException $exitException) {
571
            $this->fail();
572
            throw $exitException;
573
        } catch (\Exception $e) {
574
            // Tasks typically should not throw, but if one does, we will
575
            // convert it into an error and roll back.
576
            return Result::fromException($task, $e, $result->getData());
577
        }
578
        return $result;
579
    }
580
581
    /**
582
     * Force the rollback functions to run
583
     *
584
     * @return $this
585
     */
586
    public function fail()
587
    {
588
        $this->disableProgressIndicator();
589
        $this->runRollbackTasks();
590
        $this->complete();
591
        return $this;
592
    }
593
594
    /**
595
     * Force the completion functions to run
596
     *
597
     * @return $this
598
     */
599
    public function complete()
600
    {
601
        $this->detatchProgressIndicator();
602
        $this->runTaskListIgnoringFailures($this->completionStack);
603
        $this->reset();
604
        return $this;
605
    }
606
607
    /**
608
     * Reset this collection, removing all tasks.
609
     *
610
     * @return $this
611
     */
612
    public function reset()
613
    {
614
        $this->taskList = [];
615
        $this->completionStack = [];
616
        $this->rollbackStack = [];
617
        return $this;
618
    }
619
620
    /**
621
     * Run all of our rollback tasks.
622
     *
623
     * Note that Collection does not implement RollbackInterface, but
624
     * it may still be used as a task inside another task collection
625
     * (i.e. you can nest task collections, if desired).
626
     */
627
    protected function runRollbackTasks()
628
    {
629
        $this->runTaskListIgnoringFailures($this->rollbackStack);
630
        // Erase our rollback stack once we have finished rolling
631
        // everything back.  This will allow us to potentially use
632
        // a command collection more than once (e.g. to retry a
633
        // failed operation after doing some error recovery).
634
        $this->rollbackStack = [];
635
    }
636
637
    /**
638
     * @param TaskInterface|NestedCollectionInterface|WrappedTaskInterface $task
639
     *
640
     * @return \Robo\Result
641
     */
642
    protected function runSubtask($task)
643
    {
644
        $original = ($task instanceof WrappedTaskInterface) ? $task->original() : $task;
645
        $this->setParentCollectionForTask($original, $this->getParentCollection());
646
        if ($original instanceof InflectionInterface) {
647
            $original->inflect($this);
648
        }
649
        if ($original instanceof StateAwareInterface) {
650
            $original->setState($this->getState());
651
        }
652
        $this->doDeferredInitialization($original);
653
        $taskResult = $task->run();
0 ignored issues
show
Bug introduced by
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...
654
        $this->doStateUpdates($original, $taskResult);
655
        return $taskResult;
656
    }
657
658
    protected function doStateUpdates($task, $taskResult)
659
    {
660
        $this->updateState($taskResult);
661
        $key = spl_object_hash($task);
662
        if (array_key_exists($key, $this->messageStoreKeys)) {
663
            $state = $this->getState();
664
            list($stateKey, $sourceKey) = $this->messageStoreKeys[$key];
665
            $value = empty($sourceKey) ? $taskResult->getMessage() : $taskResult[$sourceKey];
666
            $state[$stateKey] = $value;
667
        }
668
    }
669
670
    public function storeState($task, $key, $source = '')
671
    {
672
        $this->messageStoreKeys[spl_object_hash($task)] = [$key, $source];
673
674
        return $this;
675
    }
676
677
    public function chainState($task, $functionName, $stateKey)
678
    {
679
        return $this->defer(
680
            $task,
681
            function ($task, $state) use ($functionName, $stateKey) {
682
                $fn = [$task, $functionName];
683
                $value = $state[$stateKey];
684
                $fn($value);
685
            }
686
        );
687
    }
688
689
    /**
690
     * Defer execution of a callback function until just before a task
691
     * runs. Use this time to provide more settings for the task, e.g. from
692
     * the collection's shared state, which is populated with the results
693
     * of previous test runs.
694
     */
695
    public function defer($task, $callback)
696
    {
697
        $this->deferredCallbacks[spl_object_hash($task)][] = $callback;
698
699
        return $this;
700
    }
701
702
    protected function doDeferredInitialization($task)
703
    {
704
        // If the task is a state consumer, then call its receiveState method
705
        if ($task instanceof \Robo\State\Consumer) {
706
            $task->receiveState($this->getState());
707
        }
708
709
        // Check and see if there are any deferred callbacks for this task.
710
        $key = spl_object_hash($task);
711
        if (!array_key_exists($key, $this->deferredCallbacks)) {
712
            return;
713
        }
714
715
        // Call all of the deferred callbacks
716
        foreach ($this->deferredCallbacks[$key] as $fn) {
0 ignored issues
show
Bug introduced by
The expression $this->deferredCallbacks[$key] of type callable is not traversable.
Loading history...
717
            $fn($task, $this->getState());
718
        }
719
    }
720
721
    /**
722
     * @param TaskInterface|NestedCollectionInterface|WrappedTaskInterface $task
723
     * @param $parentCollection
724
     */
725
    protected function setParentCollectionForTask($task, $parentCollection)
726
    {
727
        if ($task instanceof NestedCollectionInterface) {
728
            $task->setParentCollection($parentCollection);
729
        }
730
    }
731
732
    /**
733
     * Run all of the tasks in a provided list, ignoring failures.
734
     * This is used to roll back or complete.
735
     *
736
     * @param TaskInterface[] $taskList
737
     */
738
    protected function runTaskListIgnoringFailures(array $taskList)
739
    {
740
        foreach ($taskList as $task) {
741
            try {
742
                $this->runSubtask($task);
743
            } catch (\Exception $e) {
744
                // Ignore rollback failures.
745
            }
746
        }
747
    }
748
749
    /**
750
     * Give all of our tasks to the provided collection builder.
751
     *
752
     * @param CollectionBuilder $builder
753
     */
754
    public function transferTasks($builder)
755
    {
756
        foreach ($this->taskList as $name => $taskGroup) {
757
            // TODO: We are abandoning all of our before and after tasks here.
758
            // At the moment, transferTasks is only called under conditions where
759
            // there will be none of these, but care should be taken if that changes.
760
            $task = $taskGroup->getTask();
761
            $builder->addTaskToCollection($task);
762
        }
763
        $this->reset();
764
    }
765
}
766