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

src/Collection/CollectionBuilder.php (7 issues)

Upgrade to new PHP Analysis Engine

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

1
<?php
2
namespace Robo\Collection;
3
4
use Consolidation\Config\Inject\ConfigForSetters;
5
use Robo\Config\Config;
6
use Psr\Log\LogLevel;
7
use Robo\Contract\InflectionInterface;
8
use Robo\Contract\TaskInterface;
9
use Robo\Contract\CompletionInterface;
10
use Robo\Contract\WrappedTaskInterface;
11
use Robo\Task\Simulator;
12
use ReflectionClass;
13
use Robo\Task\BaseTask;
14
use Robo\Contract\BuilderAwareInterface;
15
use Robo\Contract\CommandInterface;
16
use Robo\Contract\VerbosityThresholdInterface;
17
use Robo\State\StateAwareInterface;
18
use Robo\State\StateAwareTrait;
19
use Robo\Result;
20
21
/**
22
 * Creates a collection, and adds tasks to it.  The collection builder
23
 * offers a streamlined chained-initialization mechanism for easily
24
 * creating task groups.  Facilities for creating working and temporary
25
 * directories are also provided.
26
 *
27
 * ``` php
28
 * <?php
29
 * $result = $this->collectionBuilder()
30
 *   ->taskFilesystemStack()
31
 *     ->mkdir('g')
32
 *     ->touch('g/g.txt')
33
 *   ->rollback(
34
 *     $this->taskDeleteDir('g')
35
 *   )
36
 *   ->taskFilesystemStack()
37
 *     ->mkdir('g/h')
38
 *     ->touch('g/h/h.txt')
39
 *   ->taskFilesystemStack()
40
 *     ->mkdir('g/h/i/c')
41
 *     ->touch('g/h/i/i.txt')
42
 *   ->run()
43
 * ?>
44
 *
45
 * In the example above, the `taskDeleteDir` will be called if
46
 * ```
47
 */
48
class CollectionBuilder extends BaseTask implements NestedCollectionInterface, WrappedTaskInterface, CommandInterface, StateAwareInterface
49
{
50
    use StateAwareTrait;
51
52
    /**
53
     * @var \Robo\Tasks
54
     */
55
    protected $commandFile;
56
57
    /**
58
     * @var CollectionInterface
59
     */
60
    protected $collection;
61
62
    /**
63
     * @var TaskInterface
64
     */
65
    protected $currentTask;
66
67
    /**
68
     * @var bool
69
     */
70
    protected $simulated;
71
72
    /**
73
     * @param \Robo\Tasks $commandFile
74
     */
75
    public function __construct($commandFile)
76
    {
77
        $this->commandFile = $commandFile;
78
        $this->resetState();
79
    }
80
81 View Code Duplication
    public static function create($container, $commandFile)
82
    {
83
        $builder = new self($commandFile);
84
85
        $builder->setLogger($container->get('logger'));
86
        $builder->setProgressIndicator($container->get('progressIndicator'));
87
        $builder->setConfig($container->get('config'));
88
        $builder->setOutputAdapter($container->get('outputAdapter'));
89
90
        return $builder;
91
    }
92
93
    /**
94
     * @param bool $simulated
95
     *
96
     * @return $this
97
     */
98
    public function simulated($simulated = true)
99
    {
100
        $this->simulated = $simulated;
101
        return $this;
102
    }
103
104
    /**
105
     * @return bool
106
     */
107
    public function isSimulated()
108
    {
109
        if (!isset($this->simulated)) {
110
            $this->simulated = $this->getConfig()->get(Config::SIMULATE);
111
        }
112
        return $this->simulated;
113
    }
114
115
    /**
116
     * Create a temporary directory to work in. When the collection
117
     * completes or rolls back, the temporary directory will be deleted.
118
     * Returns the path to the location where the directory will be
119
     * created.
120
     *
121
     * @param string $prefix
122
     * @param string $base
123
     * @param bool $includeRandomPart
124
     *
125
     * @return string
126
     */
127
    public function tmpDir($prefix = 'tmp', $base = '', $includeRandomPart = true)
128
    {
129
        // n.b. Any task that the builder is asked to create is
130
        // automatically added to the builder's collection, and
131
        // wrapped in the builder object. Therefore, the result
132
        // of any call to `taskFoo()` from within the builder will
133
        // always be `$this`.
134
        return $this->taskTmpDir($prefix, $base, $includeRandomPart)->getPath();
135
    }
136
137
    /**
138
     * Create a working directory to hold results. A temporary directory
139
     * is first created to hold the intermediate results.  After the
140
     * builder finishes, the work directory is moved into its final location;
141
     * any results already in place will be moved out of the way and
142
     * then deleted.
143
     *
144
     * @param string $finalDestination The path where the working directory
145
     *   will be moved once the task collection completes.
146
     *
147
     * @return string
148
     */
149
    public function workDir($finalDestination)
150
    {
151
        // Creating the work dir task in this context adds it to our task collection.
152
        return $this->taskWorkDir($finalDestination)->getPath();
153
    }
154
155
    public function addTask(TaskInterface $task)
156
    {
157
        $this->getCollection()->add($task);
158
        return $this;
159
    }
160
161
  /**
162
   * Add arbitrary code to execute as a task.
163
   *
164
   * @see \Robo\Collection\CollectionInterface::addCode
165
   *
166
   * @param callable $code
167
   * @param int|string $name
168
   * @return $this
169
   */
170
    public function addCode(callable $code, $name = \Robo\Collection\CollectionInterface::UNNAMEDTASK)
171
    {
172
        $this->getCollection()->addCode($code, $name);
173
        return $this;
174
    }
175
176
    /**
177
     * Add a list of tasks to our task collection.
178
     *
179
     * @param TaskInterface[] $tasks
180
     *   An array of tasks to run with rollback protection
181
     *
182
     * @return $this
183
     */
184
    public function addTaskList(array $tasks)
185
    {
186
        $this->getCollection()->addTaskList($tasks);
187
        return $this;
188
    }
189
190
    public function rollback(TaskInterface $task)
191
    {
192
        // Ensure that we have a collection if we are going to add
193
        // a rollback function.
194
        $this->getCollection()->rollback($task);
195
        return $this;
196
    }
197
198
    public function rollbackCode(callable $rollbackCode)
199
    {
200
        $this->getCollection()->rollbackCode($rollbackCode);
201
        return $this;
202
    }
203
204
    public function completion(TaskInterface $task)
205
    {
206
        $this->getCollection()->completion($task);
207
        return $this;
208
    }
209
210
    public function completionCode(callable $completionCode)
211
    {
212
        $this->getCollection()->completionCode($completionCode);
213
        return $this;
214
    }
215
216
    /**
217
     * @param string $text
218
     * @param array $context
219
     * @param string $level
220
     *
221
     * @return $this
222
     */
223
    public function progressMessage($text, $context = [], $level = LogLevel::NOTICE)
224
    {
225
        $this->getCollection()->progressMessage($text, $context, $level);
226
        return $this;
227
    }
228
229
    /**
230
     * @param \Robo\Collection\NestedCollectionInterface $parentCollection
231
     *
232
     * @return $this
233
     */
234
    public function setParentCollection(NestedCollectionInterface $parentCollection)
235
    {
236
        $this->getCollection()->setParentCollection($parentCollection);
237
        return $this;
238
    }
239
240
    /**
241
     * Called by the factory method of each task; adds the current
242
     * task to the task builder.
243
     *
244
     * TODO: protected
245
     *
246
     * @param TaskInterface $task
247
     *
248
     * @return $this
249
     */
250
    public function addTaskToCollection($task)
251
    {
252
        // Postpone creation of the collection until the second time
253
        // we are called. At that time, $this->currentTask will already
254
        // be populated.  We call 'getCollection()' so that it will
255
        // create the collection and add the current task to it.
256
        // Note, however, that if our only tasks implements NestedCollectionInterface,
257
        // then we should force this builder to use a collection.
258
        if (!$this->collection && (isset($this->currentTask) || ($task instanceof NestedCollectionInterface))) {
259
            $this->getCollection();
260
        }
261
        $this->currentTask = $task;
262
        if ($this->collection) {
263
            $this->collection->add($task);
264
        }
265
        return $this;
266
    }
267
268
    public function getState()
269
    {
270
        $collection = $this->getCollection();
271
        return $collection->getState();
272
    }
273
274
    public function storeState($key, $source = '')
0 ignored issues
show
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $source is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
275
    {
276
        return $this->callCollectionStateFuntion(__FUNCTION__, func_get_args());
277
    }
278
279
    public function deferTaskConfiguration($functionName, $stateKey)
0 ignored issues
show
The parameter $functionName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $stateKey is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
280
    {
281
        return $this->callCollectionStateFuntion(__FUNCTION__, func_get_args());
282
    }
283
284
    public function defer($callback)
0 ignored issues
show
The parameter $callback is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
285
    {
286
        return $this->callCollectionStateFuntion(__FUNCTION__, func_get_args());
287
    }
288
289
    protected function callCollectionStateFuntion($functionName, $args)
290
    {
291
        $currentTask = ($this->currentTask instanceof WrappedTaskInterface) ? $this->currentTask->original() : $this->currentTask;
292
293
        array_unshift($args, $currentTask);
294
        $collection = $this->getCollection();
295
        $fn = [$collection, $functionName];
296
297
        call_user_func_array($fn, $args);
298
        return $this;
299
    }
300
301
    public function setVerbosityThreshold($verbosityThreshold)
302
    {
303
        $currentTask = ($this->currentTask instanceof WrappedTaskInterface) ? $this->currentTask->original() : $this->currentTask;
304
        if ($currentTask) {
305
            $currentTask->setVerbosityThreshold($verbosityThreshold);
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface Robo\Contract\TaskInterface as the method setVerbosityThreshold() does only exist in the following implementations of said interface: RoboExample\Robo\Plugin\Commands\ExceptionTask, Robo\Collection\Collection, Robo\Collection\CollectionBuilder, Robo\Collection\CompletionWrapper, Robo\Collection\TaskForEach, Robo\Task\ApiGen\ApiGen, Robo\Task\Archive\Extract, Robo\Task\Archive\Pack, Robo\Task\Assets\CssPreprocessor, Robo\Task\Assets\ImageMinify, Robo\Task\Assets\Less, Robo\Task\Assets\Minify, Robo\Task\Assets\Scss, Robo\Task\BaseTask, Robo\Task\Base\Exec, Robo\Task\Base\ExecStack, Robo\Task\Base\ParallelExec, Robo\Task\Base\SymfonyCommand, Robo\Task\Base\Watch, Robo\Task\Bower\Base, Robo\Task\Bower\Install, Robo\Task\Bower\Update, Robo\Task\CollectionTestTask, Robo\Task\CommandStack, Robo\Task\Composer\Base, Robo\Task\Composer\Config, Robo\Task\Composer\CreateProject, Robo\Task\Composer\DumpAutoload, Robo\Task\Composer\Init, Robo\Task\Composer\Install, Robo\Task\Composer\Remove, Robo\Task\Composer\RequireDependency, Robo\Task\Composer\Update, Robo\Task\Composer\Validate, Robo\Task\CountingTask, Robo\Task\Development\Changelog, Robo\Task\Development\GenerateMarkdownDoc, Robo\Task\Development\GenerateTask, Robo\Task\Development\GitHub, Robo\Task\Development\GitHubRelease, Robo\Task\Development\OpenBrowser, Robo\Task\Development\PackPhar, Robo\Task\Development\PhpServer, Robo\Task\Docker\Base, Robo\Task\Docker\Build, Robo\Task\Docker\Commit, Robo\Task\Docker\Exec, Robo\Task\Docker\Pull, Robo\Task\Docker\Remove, Robo\Task\Docker\Run, Robo\Task\Docker\Start, Robo\Task\Docker\Stop, Robo\Task\File\Concat, Robo\Task\File\Replace, Robo\Task\File\TmpFile, Robo\Task\File\Write, Robo\Task\Filesystem\BaseDir, Robo\Task\Filesystem\CleanDir, Robo\Task\Filesystem\CopyDir, Robo\Task\Filesystem\DeleteDir, Robo\Task\Filesystem\FilesystemStack, Robo\Task\Filesystem\FlattenDir, Robo\Task\Filesystem\MirrorDir, Robo\Task\Filesystem\TmpDir, Robo\Task\Filesystem\WorkDir, Robo\Task\Gulp\Base, Robo\Task\Gulp\Run, Robo\Task\Npm\Base, Robo\Task\Npm\Install, Robo\Task\Npm\Update, Robo\Task\Remote\Rsync, Robo\Task\Remote\Ssh, Robo\Task\Simulator, Robo\Task\StackBasedTask, Robo\Task\Testing\Atoum, Robo\Task\Testing\Behat, Robo\Task\Testing\Codecept, Robo\Task\Testing\PHPUnit, Robo\Task\Testing\Phpspec, Robo\Task\ValueProviderTask, Robo\Task\Vcs\GitStack, Robo\Task\Vcs\HgStack, Robo\Task\Vcs\SvnStack, TestedRoboTask, unit\ConfigurationTestTaskA, unit\ConfigurationTestTaskB.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
306
            return $this;
307
        }
308
        parent::setVerbosityThreshold($verbosityThreshold);
309
        return $this;
310
    }
311
312
313
    /**
314
     * Return the current task for this collection builder.
315
     * TODO: Not needed?
316
     *
317
     * @return \Robo\Contract\TaskInterface
318
     */
319
    public function getCollectionBuilderCurrentTask()
320
    {
321
        return $this->currentTask;
322
    }
323
324
    /**
325
     * Create a new builder with its own task collection
326
     *
327
     * @return CollectionBuilder
328
     */
329
    public function newBuilder()
330
    {
331
        $collectionBuilder = new self($this->commandFile);
332
        $collectionBuilder->inflect($this);
333
        $collectionBuilder->simulated($this->isSimulated());
334
        $collectionBuilder->setVerbosityThreshold($this->verbosityThreshold());
335
        $collectionBuilder->setState($this->getState());
336
337
        return $collectionBuilder;
338
    }
339
340
    /**
341
     * Calling the task builder with methods of the current
342
     * task calls through to that method of the task.
343
     *
344
     * There is extra complexity in this function that could be
345
     * simplified if we attached the 'LoadAllTasks' and custom tasks
346
     * to the collection builder instead of the RoboFile.  While that
347
     * change would be a better design overall, it would require that
348
     * the user do a lot more work to set up and use custom tasks.
349
     * We therefore take on some additional complexity here in order
350
     * to allow users to maintain their tasks in their RoboFile, which
351
     * is much more convenient.
352
     *
353
     * Calls to $this->collectionBuilder()->taskFoo() cannot be made
354
     * directly because all of the task methods are protected.  These
355
     * calls will therefore end up here.  If the method name begins
356
     * with 'task', then it is eligible to be used with the builder.
357
     *
358
     * When we call getBuiltTask, below, it will use the builder attached
359
     * to the commandfile to build the task. However, this is not what we
360
     * want:  the task needs to be built from THIS collection builder, so that
361
     * it will be affected by whatever state is active in this builder.
362
     * To do this, we have two choices: 1) save and restore the builder
363
     * in the commandfile, or 2) clone the commandfile and set this builder
364
     * on the copy. 1) is vulnerable to failure in multithreaded environments
365
     * (currently not supported), while 2) might cause confusion if there
366
     * is shared state maintained in the commandfile, which is in the
367
     * domain of the user.
368
     *
369
     * Note that even though we are setting up the commandFile to
370
     * use this builder, getBuiltTask always creates a new builder
371
     * (which is constructed using all of the settings from the
372
     * commandFile's builder), and the new task is added to that.
373
     * We therefore need to transfer the newly built task into this
374
     * builder. The temporary builder is discarded.
375
     *
376
     * @param string $fn
377
     * @param array $args
378
     *
379
     * @return $this|mixed
380
     */
381
    public function __call($fn, $args)
382
    {
383
        if (preg_match('#^task[A-Z]#', $fn) && (method_exists($this->commandFile, 'getBuiltTask'))) {
384
            $saveBuilder = $this->commandFile->getBuilder();
385
            $this->commandFile->setBuilder($this);
386
            $temporaryBuilder = $this->commandFile->getBuiltTask($fn, $args);
387
            $this->commandFile->setBuilder($saveBuilder);
388
            if (!$temporaryBuilder) {
389
                throw new \BadMethodCallException("No such method $fn: task does not exist in " . get_class($this->commandFile));
390
            }
391
            $temporaryBuilder->getCollection()->transferTasks($this);
392
            return $this;
393
        }
394
        if (!isset($this->currentTask)) {
395
            throw new \BadMethodCallException("No such method $fn: current task undefined in collection builder.");
396
        }
397
        // If the method called is a method of the current task,
398
        // then call through to the current task's setter method.
399
        $result = call_user_func_array([$this->currentTask, $fn], $args);
400
401
        // If something other than a setter method is called, then return its result.
402
        $currentTask = ($this->currentTask instanceof WrappedTaskInterface) ? $this->currentTask->original() : $this->currentTask;
403
        if (isset($result) && ($result !== $currentTask)) {
404
            return $result;
405
        }
406
407
        return $this;
408
    }
409
410
    /**
411
     * Construct the desired task and add it to this builder.
412
     *
413
     * @param string|object $name
414
     * @param array $args
415
     *
416
     * @return \Robo\Collection\CollectionBuilder
417
     */
418
    public function build($name, $args)
419
    {
420
        $reflection = new ReflectionClass($name);
421
        $task = $reflection->newInstanceArgs($args);
422
        if (!$task) {
423
            throw new RuntimeException("Can not construct task $name");
424
        }
425
        $task = $this->fixTask($task, $args);
426
        $this->configureTask($name, $task);
427
        return $this->addTaskToCollection($task);
428
    }
429
430
    /**
431
     * @param InflectionInterface $task
432
     * @param array $args
433
     *
434
     * @return \Robo\Collection\CompletionWrapper|\Robo\Task\Simulator
435
     */
436
    protected function fixTask($task, $args)
437
    {
438
        if ($task instanceof InflectionInterface) {
439
            $task->inflect($this);
440
        }
441
        if ($task instanceof BuilderAwareInterface) {
442
            $task->setBuilder($this);
443
        }
444
        if ($task instanceof VerbosityThresholdInterface) {
445
            $task->setVerbosityThreshold($this->verbosityThreshold());
446
        }
447
448
        // Do not wrap our wrappers.
449
        if ($task instanceof CompletionWrapper || $task instanceof Simulator) {
450
            return $task;
451
        }
452
453
        // Remember whether or not this is a task before
454
        // it gets wrapped in any decorator.
455
        $isTask = $task instanceof TaskInterface;
456
        $isCollection = $task instanceof NestedCollectionInterface;
457
458
        // If the task implements CompletionInterface, ensure
459
        // that its 'complete' method is called when the application
460
        // terminates -- but only if its 'run' method is called
461
        // first.  If the task is added to a collection, then the
462
        // task will be unwrapped via its `original` method, and
463
        // it will be re-wrapped with a new completion wrapper for
464
        // its new collection.
465
        if ($task instanceof CompletionInterface) {
466
            $task = new CompletionWrapper(Temporary::getCollection(), $task);
467
        }
468
469
        // If we are in simulated mode, then wrap any task in
470
        // a TaskSimulator.
471
        if ($isTask && !$isCollection && ($this->isSimulated())) {
472
            $task = new \Robo\Task\Simulator($task, $args);
0 ignored issues
show
$task is of type object<Robo\Contract\InflectionInterface>, but the function expects a object<Robo\Contract\TaskInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
473
            $task->inflect($this);
474
        }
475
476
        return $task;
477
    }
478
479
    /**
480
     * Check to see if there are any setter methods defined in configuration
481
     * for this task.
482
     */
483
    protected function configureTask($taskClass, $task)
484
    {
485
        $taskClass = static::configClassIdentifier($taskClass);
486
        $configurationApplier = new ConfigForSetters($this->getConfig(), $taskClass, 'task.');
487
        $configurationApplier->apply($task, 'settings');
488
489
        // TODO: If we counted each instance of $taskClass that was called from
490
        // this builder, then we could also apply configuration from
491
        // "task.{$taskClass}[$N].settings"
492
493
        // TODO: If the builder knew what the current command name was,
494
        // then we could also search for task configuration under
495
        // command-specific keys such as "command.{$commandname}.task.{$taskClass}.settings".
496
    }
497
498
    /**
499
     * When we run the collection builder, run everything in the collection.
500
     *
501
     * @return \Robo\Result
502
     */
503
    public function run()
504
    {
505
        $this->startTimer();
506
        $result = $this->runTasks();
507
        $this->stopTimer();
508
        $result['time'] = $this->getExecutionTime();
509
        $result->mergeData($this->getState()->getData());
510
        return $result;
511
    }
512
513
    /**
514
     * If there is a single task, run it; if there is a collection, run
515
     * all of its tasks.
516
     *
517
     * @return \Robo\Result
518
     */
519
    protected function runTasks()
520
    {
521
        if (!$this->collection && $this->currentTask) {
522
            $result = $this->currentTask->run();
523
            return Result::ensureResult($this->currentTask, $result);
524
        }
525
        return $this->getCollection()->run();
526
    }
527
528
    /**
529
     * @return string
530
     */
531
    public function getCommand()
532
    {
533
        if (!$this->collection && $this->currentTask) {
534
            $task = $this->currentTask;
535
            $task = ($task instanceof WrappedTaskInterface) ? $task->original() : $task;
536
            if ($task instanceof CommandInterface) {
537
                return $task->getCommand();
538
            }
539
        }
540
541
        return $this->getCollection()->getCommand();
542
    }
543
544
    /**
545
     * @return \Robo\Collection\Collection
546
     */
547
    public function original()
548
    {
549
        return $this->getCollection();
550
    }
551
552
    /**
553
     * Return the collection of tasks associated with this builder.
554
     *
555
     * @return CollectionInterface
556
     */
557
    public function getCollection()
558
    {
559
        if (!isset($this->collection)) {
560
            $this->collection = new Collection();
561
            $this->collection->inflect($this);
562
            $this->collection->setState($this->getState());
563
            $this->collection->setProgressBarAutoDisplayInterval($this->getConfig()->get(Config::PROGRESS_BAR_AUTO_DISPLAY_INTERVAL));
564
565
            if (isset($this->currentTask)) {
566
                $this->collection->add($this->currentTask);
567
            }
568
        }
569
        return $this->collection;
570
    }
571
}
572