Completed
Pull Request — master (#596)
by Greg
05:04 queued 01:12
created

CollectionBuilder::classNameWithoutNamespace()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
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\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
use Robo\Contract\VerbosityThresholdInterface;
16
use Robo\State\StateAwareInterface;
17
use Robo\State\StateAwareTrait;
18
use Robo\Result;
19
20
/**
21
 * Creates a collection, and adds tasks to it.  The collection builder
22
 * offers a streamlined chained-initialization mechanism for easily
23
 * creating task groups.  Facilities for creating working and temporary
24
 * directories are also provided.
25
 *
26
 * ``` php
27
 * <?php
28
 * $result = $this->collectionBuilder()
29
 *   ->taskFilesystemStack()
30
 *     ->mkdir('g')
31
 *     ->touch('g/g.txt')
32
 *   ->rollback(
33
 *     $this->taskDeleteDir('g')
34
 *   )
35
 *   ->taskFilesystemStack()
36
 *     ->mkdir('g/h')
37
 *     ->touch('g/h/h.txt')
38
 *   ->taskFilesystemStack()
39
 *     ->mkdir('g/h/i/c')
40
 *     ->touch('g/h/i/i.txt')
41
 *   ->run()
42
 * ?>
43
 *
44
 * In the example above, the `taskDeleteDir` will be called if
45
 * ```
46
 */
47
class CollectionBuilder extends BaseTask implements NestedCollectionInterface, WrappedTaskInterface, CommandInterface, StateAwareInterface
48
{
49
    use StateAwareTrait;
50
51
    /**
52
     * @var \Robo\Tasks
53
     */
54
    protected $commandFile;
55
56
    /**
57
     * @var CollectionInterface
58
     */
59
    protected $collection;
60
61
    /**
62
     * @var TaskInterface
63
     */
64
    protected $currentTask;
65
66
    /**
67
     * @var bool
68
     */
69
    protected $simulated;
70
71
    /**
72
     * @param \Robo\Tasks $commandFile
73
     */
74
    public function __construct($commandFile)
75
    {
76
        $this->commandFile = $commandFile;
77
        $this->resetState();
78
    }
79
80 View Code Duplication
    public static function create($container, $commandFile)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
81
    {
82
        $builder = new self($commandFile);
83
84
        $builder->setLogger($container->get('logger'));
85
        $builder->setProgressIndicator($container->get('progressIndicator'));
86
        $builder->setConfig($container->get('config'));
87
        $builder->setOutputAdapter($container->get('outputAdapter'));
88
89
        return $builder;
90
    }
91
92
    /**
93
     * @param bool $simulated
94
     *
95
     * @return $this
96
     */
97
    public function simulated($simulated = true)
98
    {
99
        $this->simulated = $simulated;
100
        return $this;
101
    }
102
103
    /**
104
     * @return bool
105
     */
106
    public function isSimulated()
107
    {
108
        if (!isset($this->simulated)) {
109
            $this->simulated = $this->getConfig()->get(Config::SIMULATE);
110
        }
111
        return $this->simulated;
112
    }
113
114
    /**
115
     * Create a temporary directory to work in. When the collection
116
     * completes or rolls back, the temporary directory will be deleted.
117
     * Returns the path to the location where the directory will be
118
     * created.
119
     *
120
     * @param string $prefix
121
     * @param string $base
122
     * @param bool $includeRandomPart
123
     *
124
     * @return string
125
     */
126
    public function tmpDir($prefix = 'tmp', $base = '', $includeRandomPart = true)
127
    {
128
        // n.b. Any task that the builder is asked to create is
129
        // automatically added to the builder's collection, and
130
        // wrapped in the builder object. Therefore, the result
131
        // of any call to `taskFoo()` from within the builder will
132
        // always be `$this`.
133
        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...
134
    }
135
136
    /**
137
     * Create a working directory to hold results. A temporary directory
138
     * is first created to hold the intermediate results.  After the
139
     * builder finishes, the work directory is moved into its final location;
140
     * any results already in place will be moved out of the way and
141
     * then deleted.
142
     *
143
     * @param string $finalDestination The path where the working directory
144
     *   will be moved once the task collection completes.
145
     *
146
     * @return string
147
     */
148
    public function workDir($finalDestination)
149
    {
150
        // Creating the work dir task in this context adds it to our task collection.
151
        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...
152
    }
153
154
    public function addTask(TaskInterface $task)
155
    {
156
        $this->getCollection()->add($task);
157
        return $this;
158
    }
159
160
  /**
161
   * Add arbitrary code to execute as a task.
162
   *
163
   * @see \Robo\Collection\CollectionInterface::addCode
164
   *
165
   * @param callable $code
166
   * @param int|string $name
167
   * @return $this
168
   */
169
    public function addCode(callable $code, $name = \Robo\Collection\CollectionInterface::UNNAMEDTASK)
170
    {
171
        $this->getCollection()->addCode($code, $name);
172
        return $this;
173
    }
174
175
    /**
176
     * Add a list of tasks to our task collection.
177
     *
178
     * @param TaskInterface[] $tasks
179
     *   An array of tasks to run with rollback protection
180
     *
181
     * @return $this
182
     */
183
    public function addTaskList(array $tasks)
184
    {
185
        $this->getCollection()->addTaskList($tasks);
186
        return $this;
187
    }
188
189
    public function rollback(TaskInterface $task)
190
    {
191
        // Ensure that we have a collection if we are going to add
192
        // a rollback function.
193
        $this->getCollection()->rollback($task);
194
        return $this;
195
    }
196
197
    public function rollbackCode(callable $rollbackCode)
198
    {
199
        $this->getCollection()->rollbackCode($rollbackCode);
200
        return $this;
201
    }
202
203
    public function completion(TaskInterface $task)
204
    {
205
        $this->getCollection()->completion($task);
206
        return $this;
207
    }
208
209
    public function completionCode(callable $completionCode)
210
    {
211
        $this->getCollection()->completionCode($completionCode);
212
        return $this;
213
    }
214
215
    /**
216
     * @param string $text
217
     * @param array $context
218
     * @param string $level
219
     *
220
     * @return $this
221
     */
222
    public function progressMessage($text, $context = [], $level = LogLevel::NOTICE)
223
    {
224
        $this->getCollection()->progressMessage($text, $context, $level);
225
        return $this;
226
    }
227
228
    /**
229
     * @param \Robo\Collection\NestedCollectionInterface $parentCollection
230
     *
231
     * @return $this
232
     */
233
    public function setParentCollection(NestedCollectionInterface $parentCollection)
234
    {
235
        $this->getCollection()->setParentCollection($parentCollection);
236
        return $this;
237
    }
238
239
    /**
240
     * Called by the factory method of each task; adds the current
241
     * task to the task builder.
242
     *
243
     * TODO: protected
244
     *
245
     * @param TaskInterface $task
246
     *
247
     * @return $this
248
     */
249
    public function addTaskToCollection($task)
250
    {
251
        // Postpone creation of the collection until the second time
252
        // we are called. At that time, $this->currentTask will already
253
        // be populated.  We call 'getCollection()' so that it will
254
        // create the collection and add the current task to it.
255
        // Note, however, that if our only tasks implements NestedCollectionInterface,
256
        // then we should force this builder to use a collection.
257
        if (!$this->collection && (isset($this->currentTask) || ($task instanceof NestedCollectionInterface))) {
258
            $this->getCollection();
259
        }
260
        $this->currentTask = $task;
261
        if ($this->collection) {
262
            $this->collection->add($task);
263
        }
264
        return $this;
265
    }
266
267
    public function getState()
268
    {
269
        $collection = $this->getCollection();
270
        return $collection->getState();
271
    }
272
273
    public function storeState($key, $source = '')
0 ignored issues
show
Unused Code introduced by
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...
Unused Code introduced by
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...
274
    {
275
        return $this->callCollectionStateFuntion(__FUNCTION__, func_get_args());
276
    }
277
278
    public function deferTaskConfiguration($functionName, $stateKey)
0 ignored issues
show
Unused Code introduced by
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...
Unused Code introduced by
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...
279
    {
280
        return $this->callCollectionStateFuntion(__FUNCTION__, func_get_args());
281
    }
282
283
    public function defer($callback)
0 ignored issues
show
Unused Code introduced by
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...
284
    {
285
        return $this->callCollectionStateFuntion(__FUNCTION__, func_get_args());
286
    }
287
288
    protected function callCollectionStateFuntion($functionName, $args)
289
    {
290
        $currentTask = ($this->currentTask instanceof WrappedTaskInterface) ? $this->currentTask->original() : $this->currentTask;
291
292
        array_unshift($args, $currentTask);
293
        $collection = $this->getCollection();
294
        $fn = [$collection, $functionName];
295
296
        call_user_func_array($fn, $args);
297
        return $this;
298
    }
299
300
    public function setVerbosityThreshold($verbosityThreshold)
301
    {
302
        $currentTask = ($this->currentTask instanceof WrappedTaskInterface) ? $this->currentTask->original() : $this->currentTask;
303
        if ($currentTask) {
304
            $currentTask->setVerbosityThreshold($verbosityThreshold);
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 setVerbosityThreshold() 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\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...
305
            return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (Robo\Collection\CollectionBuilder) is incompatible with the return type of the parent method Robo\Task\BaseTask::setVerbosityThreshold of type Robo\Common\VerbosityThresholdTrait.

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...
306
        }
307
        parent::setVerbosityThreshold($verbosityThreshold);
308
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (Robo\Collection\CollectionBuilder) is incompatible with the return type of the parent method Robo\Task\BaseTask::setVerbosityThreshold of type Robo\Common\VerbosityThresholdTrait.

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...
309
    }
310
311
312
    /**
313
     * Return the current task for this collection builder.
314
     * TODO: Not needed?
315
     *
316
     * @return \Robo\Contract\TaskInterface
317
     */
318
    public function getCollectionBuilderCurrentTask()
319
    {
320
        return $this->currentTask;
321
    }
322
323
    /**
324
     * Create a new builder with its own task collection
325
     *
326
     * @return CollectionBuilder
327
     */
328
    public function newBuilder()
329
    {
330
        $collectionBuilder = new self($this->commandFile);
331
        $collectionBuilder->inflect($this);
332
        $collectionBuilder->simulated($this->isSimulated());
333
        $collectionBuilder->setVerbosityThreshold($this->verbosityThreshold());
334
        $collectionBuilder->setState($this->getState());
335
336
        return $collectionBuilder;
337
    }
338
339
    /**
340
     * Calling the task builder with methods of the current
341
     * task calls through to that method of the task.
342
     *
343
     * There is extra complexity in this function that could be
344
     * simplified if we attached the 'LoadAllTasks' and custom tasks
345
     * to the collection builder instead of the RoboFile.  While that
346
     * change would be a better design overall, it would require that
347
     * the user do a lot more work to set up and use custom tasks.
348
     * We therefore take on some additional complexity here in order
349
     * to allow users to maintain their tasks in their RoboFile, which
350
     * is much more convenient.
351
     *
352
     * Calls to $this->collectionBuilder()->taskFoo() cannot be made
353
     * directly because all of the task methods are protected.  These
354
     * calls will therefore end up here.  If the method name begins
355
     * with 'task', then it is eligible to be used with the builder.
356
     *
357
     * When we call getBuiltTask, below, it will use the builder attached
358
     * to the commandfile to build the task. However, this is not what we
359
     * want:  the task needs to be built from THIS collection builder, so that
360
     * it will be affected by whatever state is active in this builder.
361
     * To do this, we have two choices: 1) save and restore the builder
362
     * in the commandfile, or 2) clone the commandfile and set this builder
363
     * on the copy. 1) is vulnerable to failure in multithreaded environments
364
     * (currently not supported), while 2) might cause confusion if there
365
     * is shared state maintained in the commandfile, which is in the
366
     * domain of the user.
367
     *
368
     * Note that even though we are setting up the commandFile to
369
     * use this builder, getBuiltTask always creates a new builder
370
     * (which is constructed using all of the settings from the
371
     * commandFile's builder), and the new task is added to that.
372
     * We therefore need to transfer the newly built task into this
373
     * builder. The temporary builder is discarded.
374
     *
375
     * @param string $fn
376
     * @param array $args
377
     *
378
     * @return $this|mixed
379
     */
380
    public function __call($fn, $args)
381
    {
382
        if (preg_match('#^task[A-Z]#', $fn) && (method_exists($this->commandFile, 'getBuiltTask'))) {
383
            $saveBuilder = $this->commandFile->getBuilder();
384
            $this->commandFile->setBuilder($this);
385
            $temporaryBuilder = $this->commandFile->getBuiltTask($fn, $args);
386
            $this->commandFile->setBuilder($saveBuilder);
387
            if (!$temporaryBuilder) {
388
                throw new \BadMethodCallException("No such method $fn: task does not exist in " . get_class($this->commandFile));
389
            }
390
            $temporaryBuilder->getCollection()->transferTasks($this);
391
            return $this;
392
        }
393
        if (!isset($this->currentTask)) {
394
            throw new \BadMethodCallException("No such method $fn: current task undefined in collection builder.");
395
        }
396
        // If the method called is a method of the current task,
397
        // then call through to the current task's setter method.
398
        $result = call_user_func_array([$this->currentTask, $fn], $args);
399
400
        // If something other than a setter method is called, then return its result.
401
        $currentTask = ($this->currentTask instanceof WrappedTaskInterface) ? $this->currentTask->original() : $this->currentTask;
402
        if (isset($result) && ($result !== $currentTask)) {
403
            return $result;
404
        }
405
406
        return $this;
407
    }
408
409
    /**
410
     * Construct the desired task and add it to this builder.
411
     *
412
     * @param string|object $name
413
     * @param array $args
414
     *
415
     * @return \Robo\Collection\CollectionBuilder
416
     */
417
    public function build($name, $args)
418
    {
419
        $reflection = new ReflectionClass($name);
420
        $task = $reflection->newInstanceArgs($args);
421
        if (!$task) {
422
            throw new RuntimeException("Can not construct task $name");
423
        }
424
        $task = $this->fixTask($task, $args);
425
        $this->configureTask($name, $task);
426
        return $this->addTaskToCollection($task);
427
    }
428
429
    /**
430
     * @param InflectionInterface $task
431
     * @param array $args
432
     *
433
     * @return \Robo\Collection\CompletionWrapper|\Robo\Task\Simulator
434
     */
435
    protected function fixTask($task, $args)
436
    {
437
        if ($task instanceof InflectionInterface) {
438
            $task->inflect($this);
439
        }
440
        if ($task instanceof BuilderAwareInterface) {
441
            $task->setBuilder($this);
442
        }
443
        if ($task instanceof VerbosityThresholdInterface) {
444
            $task->setVerbosityThreshold($this->verbosityThreshold());
445
        }
446
447
        // Do not wrap our wrappers.
448
        if ($task instanceof CompletionWrapper || $task instanceof Simulator) {
449
            return $task;
450
        }
451
452
        // Remember whether or not this is a task before
453
        // it gets wrapped in any decorator.
454
        $isTask = $task instanceof TaskInterface;
455
        $isCollection = $task instanceof NestedCollectionInterface;
456
457
        // If the task implements CompletionInterface, ensure
458
        // that its 'complete' method is called when the application
459
        // terminates -- but only if its 'run' method is called
460
        // first.  If the task is added to a collection, then the
461
        // task will be unwrapped via its `original` method, and
462
        // it will be re-wrapped with a new completion wrapper for
463
        // its new collection.
464
        if ($task instanceof CompletionInterface) {
465
            $task = new CompletionWrapper(Temporary::getCollection(), $task);
466
        }
467
468
        // If we are in simulated mode, then wrap any task in
469
        // a TaskSimulator.
470
        if ($isTask && !$isCollection && ($this->isSimulated())) {
471
            $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...
472
            $task->inflect($this);
473
        }
474
475
        return $task;
476
    }
477
478
    /**
479
     * Check to see if there are any setter methods defined in configuration
480
     * for this task.
481
     */
482
    protected function configureTask($taskClass, $task)
483
    {
484
        $taskClass = static::configClassIdentifier($taskClass);
485
        $configurationKey = "task.{$taskClass}.settings";
486
        $this->getConfig()->applyConfiguration($task, $configurationKey);
487
488
        // TODO: If we counted each instance of $taskClass that was called from
489
        // this builder, then we could also apply configuration from
490
        // "task.{$taskClass}[$N].settings"
491
492
        // TODO: If the builder knew what the current command name was,
493
        // then we could also search for task configuration under
494
        // command-specific keys such as "command.{$commandname}.task.{$taskClass}.settings".
495
    }
496
497
    /**
498
     * When we run the collection builder, run everything in the collection.
499
     *
500
     * @return \Robo\Result
501
     */
502
    public function run()
503
    {
504
        $this->startTimer();
505
        $result = $this->runTasks();
506
        $this->stopTimer();
507
        $result['time'] = $this->getExecutionTime();
508
        $result->mergeData($this->getState()->getData());
509
        return $result;
510
    }
511
512
    /**
513
     * If there is a single task, run it; if there is a collection, run
514
     * all of its tasks.
515
     *
516
     * @return \Robo\Result
517
     */
518
    protected function runTasks()
519
    {
520
        if (!$this->collection && $this->currentTask) {
521
            $result = $this->currentTask->run();
522
            return Result::ensureResult($this->currentTask, $result);
523
        }
524
        return $this->getCollection()->run();
525
    }
526
527
    /**
528
     * @return string
529
     */
530
    public function getCommand()
531
    {
532
        if (!$this->collection && $this->currentTask) {
533
            $task = $this->currentTask;
534
            $task = ($task instanceof WrappedTaskInterface) ? $task->original() : $task;
535
            if ($task instanceof CommandInterface) {
536
                return $task->getCommand();
537
            }
538
        }
539
540
        return $this->getCollection()->getCommand();
541
    }
542
543
    /**
544
     * @return \Robo\Collection\Collection
545
     */
546
    public function original()
547
    {
548
        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...
549
    }
550
551
    /**
552
     * Return the collection of tasks associated with this builder.
553
     *
554
     * @return CollectionInterface
555
     */
556
    public function getCollection()
557
    {
558
        if (!isset($this->collection)) {
559
            $this->collection = new Collection();
560
            $this->collection->inflect($this);
561
            $this->collection->setState($this->getState());
562
            $this->collection->setProgressBarAutoDisplayInterval($this->getConfig()->get(Config::PROGRESS_BAR_AUTO_DISPLAY_INTERVAL));
563
564
            if (isset($this->currentTask)) {
565
                $this->collection->add($this->currentTask);
566
            }
567
        }
568
        return $this->collection;
569
    }
570
}
571