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

Collection::doDeferredInitialization()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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