Completed
Push — master ( 8b8afe...5f2bbe )
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\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);
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);
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);
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();
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) {
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