GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Failed Conditions
Push — develop ( 97d950...17c62d )
by Maxime
19:32
created

src/Rocketeer/Console/Commands/AbstractCommand.php (6 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
3
/*
4
 * This file is part of Rocketeer
5
 *
6
 * (c) Maxime Fabre <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 */
12
13
namespace Rocketeer\Console\Commands;
14
15
use League\Container\ContainerAwareInterface;
16
use Rocketeer\Console\Style\RocketeerStyle;
17
use Rocketeer\Interfaces\IdentifierInterface;
18
use Rocketeer\Tasks\AbstractTask;
19
use Rocketeer\Tasks\Closure;
20
use Rocketeer\Traits\ContainerAwareTrait;
21
use Rocketeer\Traits\Properties\HasEventsTrait;
22
use Symfony\Component\Console\Command\Command;
23
use Symfony\Component\Console\Input\InputInterface;
24
use Symfony\Component\Console\Input\InputOption;
25
use Symfony\Component\Console\Output\OutputInterface;
26
27
/**
28
 * An abstract command with various helpers for all
29
 * subcommands to inherit.
30
 *
31
 * @mixin RocketeerStyle
32
 */
33
abstract class AbstractCommand extends Command implements IdentifierInterface, ContainerAwareInterface
34
{
35
    use ContainerAwareTrait;
36
    use HasEventsTrait;
37
38
    /**
39
     * @var InputInterface
40
     */
41
    protected $input;
42
43
    /**
44
     * @var RocketeerStyle
45
     */
46
    protected $output;
47
48
    /**
49
     * The console command name.
50
     *
51
     * @var string
52
     */
53
    protected $name;
54
55
    /**
56
     * The console command description.
57
     *
58
     * @var string
59
     */
60
    protected $description;
61
62
    /**
63
     * Whether the command's task should be built
64
     * into a pipeline or run straight.
65
     *
66
     * @var bool
67
     */
68
    protected $straight = false;
69
70
    /**
71
     * the task to execute on fire.
72
     *
73
     * @var \Rocketeer\Tasks\AbstractTask
74
     */
75
    protected $task;
76
77
    /**
78
     * @param \Rocketeer\Tasks\AbstractTask|null $task
79
     */
80
    public function __construct(AbstractTask $task = null)
81
    {
82
        parent::__construct($this->name);
83
84
        $this->setDescription($this->description);
85
        $this->specifyParameters();
86
87
        // If we passed a Task, bind its properties
88
        // to the command
89
        if ($task) {
90
            $this->task = $task;
91
92
            if (!$this->description && $description = $task->getDescription()) {
93
                $this->setDescription($description);
94
            }
95
        }
96
    }
97
98
    /**
99
     * @return InputInterface
100
     */
101
    public function getInput()
102
    {
103
        return $this->input;
104
    }
105
106
    /**
107
     * @return RocketeerStyle
108
     */
109
    public function getOutput()
110
    {
111
        return $this->output;
112
    }
113
114
    //////////////////////////////////////////////////////////////////////
115
    ////////////////////////////// ARGUMENTS /////////////////////////////
116
    //////////////////////////////////////////////////////////////////////
117
118
    /**
119
     * Get the console command arguments.
120
     *
121
     * @return array
122
     */
123
    protected function getArguments()
124
    {
125
        return [];
126
    }
127
128
    /**
129
     * Get the console command options.
130
     *
131
     * @return array
132
     */
133
    protected function getOptions()
134
    {
135
        // General options
136
        $global = [
137
            ['parallel', 'P', InputOption::VALUE_NONE, 'Run the tasks asynchronously instead of sequentially'],
138
            [
139
                'pretend',
140
                'p',
141
                InputOption::VALUE_NONE,
142
                'Shows which command would execute without actually doing anything',
143
            ],
144
        ];
145
146
        // Options that override the predefined configuration
147
        $overrides = [
148
            ['on', 'C', InputOption::VALUE_REQUIRED, 'The connection(s) to execute the Task in'],
149
            ['stage', 'S', InputOption::VALUE_REQUIRED, 'The stage to execute the Task in'],
150
            ['server', null, InputOption::VALUE_REQUIRED, 'The server to execute the Task in'],
151
            ['branch', 'B', InputOption::VALUE_REQUIRED, 'The branch to deploy'],
152
            ['release', null, InputOption::VALUE_REQUIRED, 'What to tag the release as'],
153
        ];
154
155
        // Additional credentials passed to Rocketeer
156
        $credentials = [
157
            ['host', null, InputOption::VALUE_REQUIRED, 'The host to use if asked'],
158
            ['root', null, InputOption::VALUE_REQUIRED, 'The root directory to use if asked'],
159
            ['username', null, InputOption::VALUE_REQUIRED, 'The username to use if asked'],
160
            ['password', null, InputOption::VALUE_REQUIRED, 'The password to use if asked'],
161
            ['key', null, InputOption::VALUE_REQUIRED, 'The key to use if asked'],
162
            ['keyphrase', null, InputOption::VALUE_REQUIRED, 'The keyphrase to use if asked'],
163
            ['repository', null, InputOption::VALUE_REQUIRED, 'The repository to use if asked'],
164
        ];
165
166
        return array_merge(
167
            $global,
168
            $overrides,
169
            $credentials
170
        );
171
    }
172
173
    /**
174
     * Specify the arguments and options on the command.
175
     */
176
    protected function specifyParameters()
177
    {
178
        // We will loop through all of the arguments and options for the command and
179
        // set them all on the base command instance. This specifies what can get
180
        // passed into these commands as "parameters" to control the execution.
181
        foreach ($this->getArguments() as $arguments) {
182
            $this->addArgument(...$arguments);
183
        }
184
185
        foreach ($this->getOptions() as $options) {
186
            $this->addOption(...$options);
0 ignored issues
show
$options is of type array<integer,string|null>, but the function expects a string.

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...
187
        }
188
    }
189
190
    //////////////////////////////////////////////////////////////////////
191
    ////////////////////////////// CONTAINER /////////////////////////////
192
    //////////////////////////////////////////////////////////////////////
193
194
    /**
195
     * @param string $key
196
     *
197
     * @return mixed
198
     */
199
    public function __get($key)
200
    {
201
        $key = $this->getLocatorHandle($key);
202
        if ($key === 'command') {
203
            return $this;
204
        }
205
206
        return $this->container->get($key);
207
    }
208
209
    /**
210
     * @param string $name
211
     * @param array  $arguments
212
     *
213
     * @return mixed
214
     */
215
    public function __call($name, $arguments)
216
    {
217
        // Defer calls to output
218
        if (method_exists($this->output, $name)) {
219
            return $this->output->$name(...$arguments);
220
        }
221
    }
222
223
    /**
224
     * Get the task this command executes.
225
     *
226
     * @return AbstractTask
227
     */
228
    public function getTask()
229
    {
230
        return $this->task;
231
    }
232
233
    /**
234
     * Get a global identifier for this entity.
235
     *
236
     * @return string
237
     */
238
    public function getIdentifier()
239
    {
240
        return 'commands.'.str_replace(':', '.', $this->name);
241
    }
242
243
    //////////////////////////////////////////////////////////////////////
244
    ////////////////////////////// EXECUTION /////////////////////////////
245
    //////////////////////////////////////////////////////////////////////
246
247
    /**
248
     * Execute the console command.
249
     *
250
     * @param \Symfony\Component\Console\Input\InputInterface   $input
251
     * @param \Symfony\Component\Console\Output\OutputInterface $output
252
     *
253
     * @return mixed
254
     */
255
    protected function execute(InputInterface $input, OutputInterface $output)
256
    {
257
        $this->input = $input;
258
        $this->output = new RocketeerStyle($input, $output);
259
260
        return $this->container->call([$this, 'fire']);
261
    }
262
263
    /**
264
     * Fire a Tasks Queue.
265
     *
266
     * @param string|string[]|Closure|Closure[]|\Rocketeer\Tasks\AbstractTask|\Rocketeer\Tasks\AbstractTask[] $tasks
267
     *
268
     * @return int
269
     */
270
    protected function fireTasksQueue($tasks)
271
    {
272
        $this->prepareEnvironment();
273
274
        // Fire tasks and events arround them
275
        $status = $this->runWithBeforeAfterEvents(function () use ($tasks) {
276
            if ($this->straight) {
277
                return $this->builder->buildTask($tasks)->fire();
0 ignored issues
show
Documentation Bug introduced by Maxime Fabre
The method buildTask does not exist on object<Rocketeer\Services\Builders\Builder>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
278
            }
279
280
            $pipeline = $this->queue->run($tasks);
281
282
            return $pipeline->succeeded();
283
        });
284
285
        // Remove command instance
286
        $this->container->remove('command');
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface League\Container\ContainerInterface as the method remove() does only exist in the following implementations of said interface: Rocketeer\Services\Container\Container, Rocketeer\Services\Container\Container.

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...
287
288
        // Save history to logs
289
        $this->explainer->info('Saved logs to '.$this->logs->getLogsRealpath());
290
291
        return $status ? 0 : 1;
292
    }
293
294
    /**
295
     * Run the tasks.
296
     *
297
     * @return mixed
298
     */
299
    abstract public function fire();
300
301
    //////////////////////////////////////////////////////////////////////
302
    /////////////////////////////// INPUT ////////////////////////////////
303
    //////////////////////////////////////////////////////////////////////
304
305
    /**
306
     * Get the value of a command argument.
307
     *
308
     * @param string $key
0 ignored issues
show
Should the type for parameter $key not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
309
     *
310
     * @return string|array
311
     */
312
    public function argument($key = null)
313
    {
314
        if (is_null($key)) {
315
            return $this->input->getArguments();
316
        }
317
318
        return $this->input->getArgument($key);
319
    }
320
321
    /**
322
     * Get the value of a command option.
323
     *
324
     * @param string $key
0 ignored issues
show
Should the type for parameter $key not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
325
     *
326
     * @return string|array
327
     */
328
    public function option($key = null)
329
    {
330
        if (is_null($key)) {
331
            return $this->input->getOptions();
332
        }
333
334
        return $this->input->getOption($key);
335
    }
336
337
    //////////////////////////////////////////////////////////////////////
338
    ////////////////////////////// HELPERS ///////////////////////////////
339
    //////////////////////////////////////////////////////////////////////
340
341
    /**
342
     * Time an operation and display it afterwards.
343
     *
344
     * @param callable $callback
345
     *
346
     * @return bool
347
     */
348
    public function time(callable $callback)
349
    {
350
        $results = $this->timer->time($this, $callback);
351
        $time = $this->timer->getLatestTime($this);
352
353
        $this->explainer->line('Execution time: <comment>'.$time.'s</comment>');
354
355
        return $results;
356
    }
357
358
    /**
359
     * Prepare the environment.
360
     */
361
    protected function prepareEnvironment()
362
    {
363
        // Bind command to container
364
        $this->container->add('command', $this);
365
366
        // Set active connections from flag
367
        if ($connections = $this->command->option('on')) {
368
            $this->connections->setActiveConnections($connections);
0 ignored issues
show
It seems like $connections defined by $this->command->option('on') on line 367 can also be of type array; however, Rocketeer\Services\Conne...:setActiveConnections() does only seem to accept string|array<integer,string>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
369
        }
370
    }
371
}
372