Completed
Pull Request — master (#525)
by
unknown
03:29
created

CollectionBuilder::setLogLevel()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 1
1
<?php
2
namespace Robo\Collection;
3
4
use Robo\Config;
5
use Psr\Log\LogLevel;
6
use Robo\Contract\InflectionInterface;
7
use Robo\Contract\TaskInterface;
8
use Robo\Contract\CompletionInterface;
9
use Robo\Contract\WrappedTaskInterface;
10
use Robo\Task\Simulator;
11
use ReflectionClass;
12
use Robo\Task\BaseTask;
13
use Robo\Contract\BuilderAwareInterface;
14
use Robo\Contract\CommandInterface;
15
16
/**
17
 * Creates a collection, and adds tasks to it.  The collection builder
18
 * offers a streamlined chained-initialization mechanism for easily
19
 * creating task groups.  Facilities for creating working and temporary
20
 * directories are also provided.
21
 *
22
 * ``` php
23
 * <?php
24
 * $result = $this->collectionBuilder()
25
 *   ->taskFilesystemStack()
26
 *     ->mkdir('g')
27
 *     ->touch('g/g.txt')
28
 *   ->rollback(
29
 *     $this->taskDeleteDir('g')
30
 *   )
31
 *   ->taskFilesystemStack()
32
 *     ->mkdir('g/h')
33
 *     ->touch('g/h/h.txt')
34
 *   ->taskFilesystemStack()
35
 *     ->mkdir('g/h/i/c')
36
 *     ->touch('g/h/i/i.txt')
37
 *   ->run()
38
 * ?>
39
 *
40
 * In the example above, the `taskDeleteDir` will be called if
41
 * ```
42
 */
43
class CollectionBuilder extends BaseTask implements NestedCollectionInterface, WrappedTaskInterface, CommandInterface
44
{
45
    /**
46
     * @var \Robo\Tasks
47
     */
48
    protected $commandFile;
49
50
    /**
51
     * @var CollectionInterface
52
     */
53
    protected $collection;
54
55
    /**
56
     * @var TaskInterface
57
     */
58
    protected $currentTask;
59
60
    /**
61
     * @var bool
62
     */
63
    protected $simulated;
64
65
    /**
66
     * @param \Robo\Tasks $commandFile
67
     */
68
    public function __construct($commandFile)
69
    {
70
        $this->commandFile = $commandFile;
71
    }
72
73
    /**
74
     * @param int $logLevel
75
     *
76
     * @return $this
77
     */
78
    public function setLogLevel($logLevel)
79
    {
80
        $this->logLevel = $logLevel;
81
        if (method_exists($this->currentTask, 'setLogLevel')) {
82
            $this->currentTask->setLogLevel($logLevel);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Robo\Contract\TaskInterface as the method setLogLevel() does only exist in the following implementations of said interface: 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\CommandStack, Robo\Task\Composer\Base, Robo\Task\Composer\DumpAutoload, Robo\Task\Composer\Install, Robo\Task\Composer\Remove, Robo\Task\Composer\Update, Robo\Task\Composer\Validate, 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\Vcs\GitStack, Robo\Task\Vcs\HgStack, Robo\Task\Vcs\SvnStack, TestedRoboTask, unit\CollectionTestTask, unit\ConfigurationTestTaskA, unit\ConfigurationTestTaskB, unit\CountingTask.

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...
83
        }
84
85
        return $this;
86
    }
87
88
    /**
89
     * @param bool $simulated
90
     *
91
     * @return $this
92
     */
93
    public function simulated($simulated = true)
94
    {
95
        $this->simulated = $simulated;
96
        return $this;
97
    }
98
99
    /**
100
     * @return bool
101
     */
102
    public function isSimulated()
103
    {
104
        if (!isset($this->simulated)) {
105
            $this->simulated = $this->getConfig()->get(Config::SIMULATE);
106
        }
107
        return $this->simulated;
108
    }
109
110
    /**
111
     * Create a temporary directory to work in. When the collection
112
     * completes or rolls back, the temporary directory will be deleted.
113
     * Returns the path to the location where the directory will be
114
     * created.
115
     *
116
     * @param string $prefix
117
     * @param string $base
118
     * @param bool $includeRandomPart
119
     *
120
     * @return string
121
     */
122
    public function tmpDir($prefix = 'tmp', $base = '', $includeRandomPart = true)
123
    {
124
        // n.b. Any task that the builder is asked to create is
125
        // automatically added to the builder's collection, and
126
        // wrapped in the builder object. Therefore, the result
127
        // of any call to `taskFoo()` from within the builder will
128
        // always be `$this`.
129
        return $this->taskTmpDir($prefix, $base, $includeRandomPart)->getPath();
0 ignored issues
show
Bug introduced by
The method taskTmpDir() does not exist on Robo\Collection\CollectionBuilder. Did you maybe mean tmpDir()?

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...
130
    }
131
132
    /**
133
     * Create a working directory to hold results. A temporary directory
134
     * is first created to hold the intermediate results.  After the
135
     * builder finishes, the work directory is moved into its final location;
136
     * any results already in place will be moved out of the way and
137
     * then deleted.
138
     *
139
     * @param string $finalDestination The path where the working directory
140
     *   will be moved once the task collection completes.
141
     *
142
     * @return string
143
     */
144
    public function workDir($finalDestination)
145
    {
146
        // Creating the work dir task in this context adds it to our task collection.
147
        return $this->taskWorkDir($finalDestination)->getPath();
0 ignored issues
show
Bug introduced by
The method taskWorkDir() does not exist on Robo\Collection\CollectionBuilder. Did you maybe mean workDir()?

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...
148
    }
149
150
    public function addTask(TaskInterface $task)
151
    {
152
        $this->getCollection()->add($task);
153
        return $this;
154
    }
155
156
    public function addCode(callable $code)
157
    {
158
        $this->getCollection()->addCode($code);
159
        return $this;
160
    }
161
162
    /**
163
     * Add a list of tasks to our task collection.
164
     *
165
     * @param TaskInterface[] $tasks
166
     *   An array of tasks to run with rollback protection
167
     *
168
     * @return $this
169
     */
170
    public function addTaskList(array $tasks)
171
    {
172
        $this->getCollection()->addTaskList($tasks);
173
        return $this;
174
    }
175
176
    public function rollback(TaskInterface $task)
177
    {
178
        // Ensure that we have a collection if we are going to add
179
        // a rollback function.
180
        $this->getCollection()->rollback($task);
181
        return $this;
182
    }
183
184
    public function rollbackCode(callable $rollbackCode)
185
    {
186
        $this->getCollection()->rollbackCode($rollbackCode);
187
        return $this;
188
    }
189
190
    public function completion(TaskInterface $task)
191
    {
192
        $this->getCollection()->completion($task);
193
        return $this;
194
    }
195
196
    public function completionCode(callable $completionCode)
197
    {
198
        $this->getCollection()->completionCode($completionCode);
199
        return $this;
200
    }
201
202
    /**
203
     * @param string $text
204
     * @param array $context
205
     * @param string $level
206
     *
207
     * @return $this
208
     */
209
    public function progressMessage($text, $context = [], $level = LogLevel::NOTICE)
210
    {
211
        $this->getCollection()->progressMessage($text, $context, $level);
212
        return $this;
213
    }
214
215
    /**
216
     * @param \Robo\Collection\NestedCollectionInterface $parentCollection
217
     *
218
     * @return $this
219
     */
220
    public function setParentCollection(NestedCollectionInterface $parentCollection)
221
    {
222
        $this->getCollection()->setParentCollection($parentCollection);
223
        return $this;
224
    }
225
226
    /**
227
     * Called by the factory method of each task; adds the current
228
     * task to the task builder.
229
     *
230
     * TODO: protected
231
     *
232
     * @param TaskInterface $task
233
     *
234
     * @return $this
235
     */
236
    public function addTaskToCollection($task)
237
    {
238
        // Postpone creation of the collection until the second time
239
        // we are called. At that time, $this->currentTask will already
240
        // be populated.  We call 'getCollection()' so that it will
241
        // create the collection and add the current task to it.
242
        // Note, however, that if our only tasks implements NestedCollectionInterface,
243
        // then we should force this builder to use a collection.
244
        if (!$this->collection && (isset($this->currentTask) || ($task instanceof NestedCollectionInterface))) {
245
            $this->getCollection();
246
        }
247
        $this->currentTask = $task;
248
        if ($this->collection) {
249
            $this->collection->add($task);
250
        }
251
        return $this;
252
    }
253
254
    /**
255
     * Return the current task for this collection builder.
256
     * TODO: Not needed?
257
     *
258
     * @return \Robo\Contract\TaskInterface
259
     */
260
    public function getCollectionBuilderCurrentTask()
261
    {
262
        return $this->currentTask;
263
    }
264
265
    /**
266
     * Create a new builder with its own task collection
267
     *
268
     * @return CollectionBuilder
269
     */
270
    public function newBuilder()
271
    {
272
        $collectionBuilder = new self($this->commandFile);
273
        $collectionBuilder->inflect($this);
274
        $collectionBuilder->simulated($this->isSimulated());
275
        return $collectionBuilder;
276
    }
277
278
    /**
279
     * Calling the task builder with methods of the current
280
     * task calls through to that method of the task.
281
     *
282
     * There is extra complexity in this function that could be
283
     * simplified if we attached the 'LoadAllTasks' and custom tasks
284
     * to the collection builder instead of the RoboFile.  While that
285
     * change would be a better design overall, it would require that
286
     * the user do a lot more work to set up and use custom tasks.
287
     * We therefore take on some additional complexity here in order
288
     * to allow users to maintain their tasks in their RoboFile, which
289
     * is much more convenient.
290
     *
291
     * Calls to $this->collectionBuilder()->taskFoo() cannot be made
292
     * directly because all of the task methods are protected.  These
293
     * calls will therefore end up here.  If the method name begins
294
     * with 'task', then it is eligible to be used with the builder.
295
     *
296
     * When we call getBuiltTask, below, it will use the builder attached
297
     * to the commandfile to build the task. However, this is not what we
298
     * want:  the task needs to be built from THIS collection builder, so that
299
     * it will be affected by whatever state is active in this builder.
300
     * To do this, we have two choices: 1) save and restore the builder
301
     * in the commandfile, or 2) clone the commandfile and set this builder
302
     * on the copy. 1) is vulnerable to failure in multithreaded environments
303
     * (currently not supported), while 2) might cause confusion if there
304
     * is shared state maintained in the commandfile, which is in the
305
     * domain of the user.
306
     *
307
     * Note that even though we are setting up the commandFile to
308
     * use this builder, getBuiltTask always creates a new builder
309
     * (which is constructed using all of the settings from the
310
     * commandFile's builder), and the new task is added to that.
311
     * We therefore need to transfer the newly built task into this
312
     * builder. The temporary builder is discarded.
313
     *
314
     * @param string $fn
315
     * @param array $args
316
     *
317
     * @return $this|mixed
318
     */
319
    public function __call($fn, $args)
320
    {
321
        if (preg_match('#^task[A-Z]#', $fn) && (method_exists($this->commandFile, 'getBuiltTask'))) {
322
            $saveBuilder = $this->commandFile->getBuilder();
323
            $this->commandFile->setBuilder($this);
324
            $temporaryBuilder = $this->commandFile->getBuiltTask($fn, $args);
325
            $this->commandFile->setBuilder($saveBuilder);
326
            if (!$temporaryBuilder) {
327
                throw new \BadMethodCallException("No such method $fn: task does not exist in " . get_class($this->commandFile));
328
            }
329
            $temporaryBuilder->getCollection()->transferTasks($this);
330
            return $this;
331
        }
332
        if (!isset($this->currentTask)) {
333
            throw new \BadMethodCallException("No such method $fn: current task undefined in collection builder.");
334
        }
335
        // If the method called is a method of the current task,
336
        // then call through to the current task's setter method.
337
        $result = call_user_func_array([$this->currentTask, $fn], $args);
338
339
        // If something other than a setter method is called, then return its result.
340
        $currentTask = ($this->currentTask instanceof WrappedTaskInterface) ? $this->currentTask->original() : $this->currentTask;
341
        if (isset($result) && ($result !== $currentTask)) {
342
            return $result;
343
        }
344
345
        return $this;
346
    }
347
348
    /**
349
     * Construct the desired task and add it to this builder.
350
     *
351
     * @param string|object $name
352
     * @param array $args
353
     *
354
     * @return \Robo\Collection\CollectionBuilder
355
     */
356
    public function build($name, $args)
357
    {
358
        $reflection = new ReflectionClass($name);
359
        $task = $reflection->newInstanceArgs($args);
360
        if (method_exists($this->currentTask, 'setLogLevel')) {
361
            $this->currentTask->setLogLevel($this->logLevel);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Robo\Contract\TaskInterface as the method setLogLevel() does only exist in the following implementations of said interface: 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\CommandStack, Robo\Task\Composer\Base, Robo\Task\Composer\DumpAutoload, Robo\Task\Composer\Install, Robo\Task\Composer\Remove, Robo\Task\Composer\Update, Robo\Task\Composer\Validate, 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\Vcs\GitStack, Robo\Task\Vcs\HgStack, Robo\Task\Vcs\SvnStack, TestedRoboTask, unit\CollectionTestTask, unit\ConfigurationTestTaskA, unit\ConfigurationTestTaskB, unit\CountingTask.

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...
362
        }
363
        if (!$task) {
364
            throw new RuntimeException("Can not construct task $name");
365
        }
366
        $task = $this->fixTask($task, $args);
367
        return $this->addTaskToCollection($task);
368
    }
369
370
    /**
371
     * @param InflectionInterface $task
372
     * @param array $args
373
     *
374
     * @return \Robo\Collection\CompletionWrapper|\Robo\Task\Simulator
375
     */
376
    protected function fixTask($task, $args)
377
    {
378
        $task->inflect($this);
379
        if ($task instanceof BuilderAwareInterface) {
380
            $task->setBuilder($this);
381
        }
382
383
        // Do not wrap our wrappers.
384
        if ($task instanceof CompletionWrapper || $task instanceof Simulator) {
385
            return $task;
386
        }
387
388
        // Remember whether or not this is a task before
389
        // it gets wrapped in any decorator.
390
        $isTask = $task instanceof TaskInterface;
391
        $isCollection = $task instanceof NestedCollectionInterface;
392
393
        // If the task implements CompletionInterface, ensure
394
        // that its 'complete' method is called when the application
395
        // terminates -- but only if its 'run' method is called
396
        // first.  If the task is added to a collection, then the
397
        // task will be unwrapped via its `original` method, and
398
        // it will be re-wrapped with a new completion wrapper for
399
        // its new collection.
400
        if ($task instanceof CompletionInterface) {
401
            $task = new CompletionWrapper(Temporary::getCollection(), $task);
402
        }
403
404
        // If we are in simulated mode, then wrap any task in
405
        // a TaskSimulator.
406
        if ($isTask && !$isCollection && ($this->isSimulated())) {
407
            $task = new \Robo\Task\Simulator($task, $args);
0 ignored issues
show
Documentation introduced by
$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...
408
            $task->inflect($this);
409
        }
410
411
        return $task;
412
    }
413
414
    /**
415
     * When we run the collection builder, run everything in the collection.
416
     *
417
     * @return \Robo\Result
418
     */
419
    public function run()
420
    {
421
        $this->startTimer();
422
        $result = $this->runTasks();
423
        $this->stopTimer();
424
        $result['time'] = $this->getExecutionTime();
425
        return $result;
426
    }
427
428
    /**
429
     * If there is a single task, run it; if there is a collection, run
430
     * all of its tasks.
431
     *
432
     * @return \Robo\Result
433
     */
434
    protected function runTasks()
435
    {
436
        if (!$this->collection && $this->currentTask) {
437
            return $this->currentTask->run();
438
        }
439
        return $this->getCollection()->run();
440
    }
441
442
    /**
443
     * @return string
444
     */
445
    public function getCommand()
446
    {
447
        if (!$this->collection && $this->currentTask) {
448
            $task = $this->currentTask;
449
            $task = ($task instanceof WrappedTaskInterface) ? $task->original() : $task;
450
            if ($task instanceof CommandInterface) {
451
                return $task->getCommand();
452
            }
453
        }
454
455
        return $this->getCollection()->getCommand();
456
    }
457
458
    /**
459
     * @return \Robo\Collection\Collection
460
     */
461
    public function original()
462
    {
463
        return $this->getCollection();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->getCollection(); (Robo\Collection\CollectionInterface) is incompatible with the return type declared by the interface Robo\Contract\WrappedTaskInterface::original of type Robo\Contract\TaskInterface.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
464
    }
465
466
    /**
467
     * Return the collection of tasks associated with this builder.
468
     *
469
     * @return CollectionInterface
470
     */
471
    public function getCollection()
472
    {
473
        if (!isset($this->collection)) {
474
            $this->collection = new Collection();
475
            $this->collection->inflect($this);
476
            $this->collection->setProgressBarAutoDisplayInterval($this->getConfig()->get(Config::PROGRESS_BAR_AUTO_DISPLAY_INTERVAL));
477
478
            if (isset($this->currentTask)) {
479
                $this->collection->add($this->currentTask);
480
            }
481
        }
482
        return $this->collection;
483
    }
484
}
485