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

CompletionWrapper::setLogLevel()   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
3
namespace Robo\Collection;
4
5
use Robo\Task\BaseTask;
6
use Robo\Contract\TaskInterface;
7
use Robo\Contract\RollbackInterface;
8
use Robo\Contract\CompletionInterface;
9
use Robo\Contract\WrappedTaskInterface;
10
11
/**
12
 * Creates a task wrapper that will manage rollback and collection
13
 * management to a task when it runs.  Tasks are automatically
14
 * wrapped in a CompletionWrapper when added to a task collection.
15
 *
16
 * Clients may need to wrap their task in a CompletionWrapper if it
17
 * creates temporary objects.
18
 *
19
 * @see \Robo\Task\Filesystem\loadTasks::taskTmpDir
20
 */
21
class CompletionWrapper extends BaseTask implements WrappedTaskInterface
22
{
23
    /**
24
     * @var \Robo\Collection\Collection
25
     */
26
    private $collection;
27
28
    /**
29
     * @var \Robo\Contract\TaskInterface
30
     */
31
    private $task;
32
33
    /**
34
     * @var NULL|\Robo\Contract\TaskInterface
35
     */
36
    private $rollbackTask;
37
38
    /**
39
     * Create a CompletionWrapper.
40
     *
41
     * Temporary tasks are always wrapped in a CompletionWrapper, as are
42
     * any tasks that are added to a collection.  If a temporary task
43
     * is added to a collection, then it is first unwrapped from its
44
     * CompletionWrapper (via its original() method), and then added to a
45
     * new CompletionWrapper for the collection it is added to.
46
     *
47
     * In this way, when the CompletionWrapper is finally executed, the
48
     * task's rollback and completion handlers will be registered on
49
     * whichever collection it was registered on.
50
     *
51
     * @todo Why not CollectionInterface the type of the $collection argument?
52
     *
53
     * @param \Robo\Collection\Collection $collection
54
     * @param \Robo\Contract\TaskInterface $task
55
     * @param \Robo\Contract\TaskInterface|NULL $rollbackTask
56
     */
57
    public function __construct(Collection $collection, TaskInterface $task, TaskInterface $rollbackTask = null)
58
    {
59
        $this->collection = $collection;
60
        $this->task = ($task instanceof WrappedTaskInterface) ? $task->original() : $task;
61
        $this->rollbackTask = $rollbackTask;
62
    }
63
64
    /**
65
     * @param int $logLevel
66
     */
67
    public function setLogLevel($logLevel)
68
    {
69
        $this->logLevel = $logLevel;
70
        $this->collection->setLogLevel($logLevel);
71
        if (method_exists($this->task, 'setLogLevel')) {
72
            $this->task->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...
73
        }
74
    }
75
76
    /**
77
     * {@inheritdoc}
78
     */
79
    public function original()
80
    {
81
        return $this->task;
82
    }
83
84
    /**
85
     * Before running this task, register its rollback and completion
86
     * handlers on its collection. The reason this class exists is to
87
     * defer registration of rollback and completion tasks until 'run()' time.
88
     *
89
     * @return \Robo\Result
90
     */
91
    public function run()
92
    {
93
        if ($this->rollbackTask) {
94
            $this->collection->registerRollback($this->rollbackTask);
95
        }
96
        if ($this->task instanceof RollbackInterface) {
97
            $this->collection->registerRollback(new CallableTask([$this->task, 'rollback'], $this->task));
98
        }
99
        if ($this->task instanceof CompletionInterface) {
100
            $this->collection->registerCompletion(new CallableTask([$this->task, 'complete'], $this->task));
101
        }
102
103
        return $this->task->run();
104
    }
105
106
    /**
107
     * Make this wrapper object act like the class it wraps.
108
     *
109
     * @param string $function
110
     * @param array $args
111
     *
112
     * @return mixed
113
     */
114
    public function __call($function, $args)
115
    {
116
        return call_user_func_array(array($this->task, $function), $args);
117
    }
118
}
119