Completed
Push — master ( d3a073...5737c8 )
by Greg
02:21
created

src/Collection/Collection.php (1 issue)

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