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.
Completed
Push — master ( 560332...66e815 )
by Robert
11:59
created

Controller::getActionArgsHelp()   B

Complexity

Conditions 6
Paths 18

Size

Total Lines 37
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 6.0131

Importance

Changes 0
Metric Value
dl 0
loc 37
ccs 26
cts 28
cp 0.9286
rs 8.439
c 0
b 0
f 0
cc 6
eloc 27
nc 18
nop 1
crap 6.0131
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\console;
9
10
use Yii;
11
use yii\base\Action;
12
use yii\base\InlineAction;
13
use yii\base\InvalidRouteException;
14
use yii\helpers\Console;
15
16
/**
17
 * Controller is the base class of console command classes.
18
 *
19
 * A console controller consists of one or several actions known as sub-commands.
20
 * Users call a console command by specifying the corresponding route which identifies a controller action.
21
 * The `yii` program is used when calling a console command, like the following:
22
 *
23
 * ```
24
 * yii <route> [--param1=value1 --param2 ...]
25
 * ```
26
 *
27
 * where `<route>` is a route to a controller action and the params will be populated as properties of a command.
28
 * See [[options()]] for details.
29
 *
30
 * @property string $help This property is read-only.
31
 * @property string $helpSummary This property is read-only.
32
 * @property array $passedOptionValues The properties corresponding to the passed options. This property is
33
 * read-only.
34
 * @property array $passedOptions The names of the options passed during execution. This property is
35
 * read-only.
36
 *
37
 * @author Qiang Xue <[email protected]>
38
 * @since 2.0
39
 */
40
class Controller extends \yii\base\Controller
41
{
42
    const EXIT_CODE_NORMAL = 0;
43
    const EXIT_CODE_ERROR = 1;
44
45
    /**
46
     * @var bool whether to run the command interactively.
47
     */
48
    public $interactive = true;
49
    /**
50
     * @var bool whether to enable ANSI color in the output.
51
     * If not set, ANSI color will only be enabled for terminals that support it.
52
     */
53
    public $color;
54
    /**
55
     * @var bool whether to display help information about current command.
56
     * @since 2.0.10
57
     */
58
    public $help;
59
60
    /**
61
     * @var array the options passed during execution.
62
     */
63
    private $_passedOptions = [];
64
65
66
    /**
67
     * Returns a value indicating whether ANSI color is enabled.
68
     *
69
     * ANSI color is enabled only if [[color]] is set true or is not set
70
     * and the terminal supports ANSI color.
71
     *
72
     * @param resource $stream the stream to check.
73
     * @return bool Whether to enable ANSI style in output.
74
     */
75 4
    public function isColorEnabled($stream = \STDOUT)
76
    {
77 4
        return $this->color === null ? Console::streamSupportsAnsiColors($stream) : $this->color;
78
    }
79
80
    /**
81
     * Runs an action with the specified action ID and parameters.
82
     * If the action ID is empty, the method will use [[defaultAction]].
83
     * @param string $id the ID of the action to be executed.
84
     * @param array $params the parameters (name-value pairs) to be passed to the action.
85
     * @return int the status of the action execution. 0 means normal, other values mean abnormal.
86
     * @throws InvalidRouteException if the requested action ID cannot be resolved into an action successfully.
87
     * @throws Exception if there are unknown options or missing arguments
88
     * @see createAction
89
     */
90 57
    public function runAction($id, $params = [])
91
    {
92 57
        if (!empty($params)) {
93
            // populate options here so that they are available in beforeAction().
94 48
            $options = $this->options($id === '' ? $this->defaultAction : $id);
95 48
            if (isset($params['_aliases'])) {
96 1
                $optionAliases = $this->optionAliases();
97 1
                foreach ($params['_aliases'] as $name => $value) {
98 1
                    if (array_key_exists($name, $optionAliases)) {
99 1
                        $params[$optionAliases[$name]] = $value;
100 1
                    } else {
101
                        throw new Exception(Yii::t('yii', 'Unknown alias: -{name}', ['name' => $name]));
102
                    }
103 1
                }
104 1
                unset($params['_aliases']);
105 1
            }
106 48
            foreach ($params as $name => $value) {
107 48
                if (in_array($name, $options, true)) {
108 5
                    $default = $this->$name;
109 5
                    if (is_array($default)) {
110 5
                        $this->$name = preg_split('/\s*,\s*(?![^()]*\))/', $value);
111 5
                    } elseif ($default !== null) {
112 2
                        settype($value, gettype($default));
113 2
                        $this->$name = $value;
114 2
                    } else {
115 1
                        $this->$name = $value;
116
                    }
117 5
                    $this->_passedOptions[] = $name;
118 5
                    unset($params[$name]);
119 48
                } elseif (!is_int($name)) {
120
                    throw new Exception(Yii::t('yii', 'Unknown option: --{name}', ['name' => $name]));
121
                }
122 48
            }
123 48
        }
124 57
        if ($this->help) {
125 2
            $route = $this->getUniqueId() . '/' . $id;
126 2
            return Yii::$app->runAction('help', [$route]);
127
        }
128 57
        return parent::runAction($id, $params);
129
    }
130
131
    /**
132
     * Binds the parameters to the action.
133
     * This method is invoked by [[Action]] when it begins to run with the given parameters.
134
     * This method will first bind the parameters with the [[options()|options]]
135
     * available to the action. It then validates the given arguments.
136
     * @param Action $action the action to be bound with parameters
137
     * @param array $params the parameters to be bound to the action
138
     * @return array the valid parameters that the action can run with.
139
     * @throws Exception if there are unknown options or missing arguments
140
     */
141 63
    public function bindActionParams($action, $params)
142
    {
143 63
        if ($action instanceof InlineAction) {
144 63
            $method = new \ReflectionMethod($this, $action->actionMethod);
145 63
        } else {
146
            $method = new \ReflectionMethod($action, 'run');
147
        }
148
149 63
        $args = array_values($params);
150
151 63
        $missing = [];
152 63
        foreach ($method->getParameters() as $i => $param) {
153 61
            if ($param->isArray() && isset($args[$i])) {
154 1
                $args[$i] = preg_split('/\s*,\s*/', $args[$i]);
155 1
            }
156 61
            if (!isset($args[$i])) {
157 14
                if ($param->isDefaultValueAvailable()) {
158 14
                    $args[$i] = $param->getDefaultValue();
159 14
                } else {
160 1
                    $missing[] = $param->getName();
161
                }
162 14
            }
163 63
        }
164
165 63
        if (!empty($missing)) {
166 1
            throw new Exception(Yii::t('yii', 'Missing required arguments: {params}', ['params' => implode(', ', $missing)]));
167
        }
168
169 63
        return $args;
170
    }
171
172
    /**
173
     * Formats a string with ANSI codes
174
     *
175
     * You may pass additional parameters using the constants defined in [[\yii\helpers\Console]].
176
     *
177
     * Example:
178
     *
179
     * ```
180
     * echo $this->ansiFormat('This will be red and underlined.', Console::FG_RED, Console::UNDERLINE);
181
     * ```
182
     *
183
     * @param string $string the string to be formatted
184
     * @return string
185
     */
186 4
    public function ansiFormat($string)
187
    {
188 4
        if ($this->isColorEnabled()) {
189 4
            $args = func_get_args();
190 4
            array_shift($args);
191 4
            $string = Console::ansiFormat($string, $args);
192 4
        }
193 4
        return $string;
194
    }
195
196
    /**
197
     * Prints a string to STDOUT
198
     *
199
     * You may optionally format the string with ANSI codes by
200
     * passing additional parameters using the constants defined in [[\yii\helpers\Console]].
201
     *
202
     * Example:
203
     *
204
     * ```
205
     * $this->stdout('This will be red and underlined.', Console::FG_RED, Console::UNDERLINE);
206
     * ```
207
     *
208
     * @param string $string the string to print
209
     * @return int|bool Number of bytes printed or false on error
210
     */
211
    public function stdout($string)
212
    {
213
        if ($this->isColorEnabled()) {
214
            $args = func_get_args();
215
            array_shift($args);
216
            $string = Console::ansiFormat($string, $args);
217
        }
218
        return Console::stdout($string);
219
    }
220
221
    /**
222
     * Prints a string to STDERR
223
     *
224
     * You may optionally format the string with ANSI codes by
225
     * passing additional parameters using the constants defined in [[\yii\helpers\Console]].
226
     *
227
     * Example:
228
     *
229
     * ```
230
     * $this->stderr('This will be red and underlined.', Console::FG_RED, Console::UNDERLINE);
231
     * ```
232
     *
233
     * @param string $string the string to print
234
     * @return int|bool Number of bytes printed or false on error
235
     */
236
    public function stderr($string)
237
    {
238
        if ($this->isColorEnabled(\STDERR)) {
239
            $args = func_get_args();
240
            array_shift($args);
241
            $string = Console::ansiFormat($string, $args);
242
        }
243
        return fwrite(\STDERR, $string);
244
    }
245
246
    /**
247
     * Prompts the user for input and validates it
248
     *
249
     * @param string $text prompt string
250
     * @param array $options the options to validate the input:
251
     *
252
     *  - required: whether it is required or not
253
     *  - default: default value if no input is inserted by the user
254
     *  - pattern: regular expression pattern to validate user input
255
     *  - validator: a callable function to validate input. The function must accept two parameters:
256
     *      - $input: the user input to validate
257
     *      - $error: the error value passed by reference if validation failed.
258
     *
259
     * An example of how to use the prompt method with a validator function.
260
     *
261
     * ```php
262
     * $code = $this->prompt('Enter 4-Chars-Pin', ['required' => true, 'validator' => function($input, &$error) {
263
     *     if (strlen($input) !== 4) {
264
     *         $error = 'The Pin must be exactly 4 chars!';
265
     *         return false;
266
     *     }
267
     *     return true;
268
     * });
269
     * ```
270
     *
271
     * @return string the user input
272
     */
273
    public function prompt($text, $options = [])
274
    {
275
        if ($this->interactive) {
276
            return Console::prompt($text, $options);
277
        }
278
279
        return isset($options['default']) ? $options['default'] : '';
280
    }
281
282
    /**
283
     * Asks user to confirm by typing y or n.
284
     *
285
     * @param string $message to echo out before waiting for user input
286
     * @param bool $default this value is returned if no selection is made.
287
     * @return bool whether user confirmed.
288
     * Will return true if [[interactive]] is false.
289
     */
290 34
    public function confirm($message, $default = false)
291
    {
292 34
        if ($this->interactive) {
293
            return Console::confirm($message, $default);
294
        }
295
296 34
        return true;
297
    }
298
299
    /**
300
     * Gives the user an option to choose from. Giving '?' as an input will show
301
     * a list of options to choose from and their explanations.
302
     *
303
     * @param string $prompt the prompt message
304
     * @param array $options Key-value array of options to choose from
305
     *
306
     * @return string An option character the user chose
307
     */
308
    public function select($prompt, $options = [])
309
    {
310
        return Console::select($prompt, $options);
311
    }
312
313
    /**
314
     * Returns the names of valid options for the action (id)
315
     * An option requires the existence of a public member variable whose
316
     * name is the option name.
317
     * Child classes may override this method to specify possible options.
318
     *
319
     * Note that the values setting via options are not available
320
     * until [[beforeAction()]] is being called.
321
     *
322
     * @param string $actionID the action id of the current request
323
     * @return string[] the names of the options valid for the action
324
     */
325 51
    public function options($actionID)
326
    {
327
        // $actionId might be used in subclasses to provide options specific to action id
328 51
        return ['color', 'interactive', 'help'];
329
    }
330
331
    /**
332
     * Returns option alias names.
333
     * Child classes may override this method to specify alias options.
334
     *
335
     * @return array the options alias names valid for the action
336
     * where the keys is alias name for option and value is option name.
337
     *
338
     * @since 2.0.8
339
     * @see options()
340
     */
341 2
    public function optionAliases()
342
    {
343
        return [
344
            'h' => 'help'
345 2
        ];
346
    }
347
348
    /**
349
     * Returns properties corresponding to the options for the action id
350
     * Child classes may override this method to specify possible properties.
351
     *
352
     * @param string $actionID the action id of the current request
353
     * @return array properties corresponding to the options for the action
354
     */
355 20
    public function getOptionValues($actionID)
0 ignored issues
show
Unused Code introduced by
The parameter $actionID 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...
356
    {
357
        // $actionId might be used in subclasses to provide properties specific to action id
358 20
        $properties = [];
359 20
        foreach ($this->options($this->action->id) as $property) {
360 20
            $properties[$property] = $this->$property;
361 20
        }
362 20
        return $properties;
363
    }
364
365
    /**
366
     * Returns the names of valid options passed during execution.
367
     *
368
     * @return array the names of the options passed during execution
369
     */
370
    public function getPassedOptions()
371
    {
372
        return $this->_passedOptions;
373
    }
374
375
    /**
376
     * Returns the properties corresponding to the passed options
377
     *
378
     * @return array the properties corresponding to the passed options
379
     */
380 18
    public function getPassedOptionValues()
381
    {
382 18
        $properties = [];
383 18
        foreach ($this->_passedOptions as $property) {
384
            $properties[$property] = $this->$property;
385 18
        }
386 18
        return $properties;
387
    }
388
389
    /**
390
     * Returns one-line short summary describing this controller.
391
     *
392
     * You may override this method to return customized summary.
393
     * The default implementation returns first line from the PHPDoc comment.
394
     *
395
     * @return string
396
     */
397 2
    public function getHelpSummary()
398
    {
399 2
        return $this->parseDocCommentSummary(new \ReflectionClass($this));
400
    }
401
402
    /**
403
     * Returns help information for this controller.
404
     *
405
     * You may override this method to return customized help.
406
     * The default implementation returns help information retrieved from the PHPDoc comment.
407
     * @return string
408
     */
409
    public function getHelp()
410
    {
411
        return $this->parseDocCommentDetail(new \ReflectionClass($this));
412
    }
413
414
    /**
415
     * Returns a one-line short summary describing the specified action.
416
     * @param Action $action action to get summary for
417
     * @return string a one-line short summary describing the specified action.
418
     */
419 1
    public function getActionHelpSummary($action)
420
    {
421 1
        return $this->parseDocCommentSummary($this->getActionMethodReflection($action));
422
    }
423
424
    /**
425
     * Returns the detailed help information for the specified action.
426
     * @param Action $action action to get help for
427
     * @return string the detailed help information for the specified action.
428
     */
429 2
    public function getActionHelp($action)
430
    {
431 2
        return $this->parseDocCommentDetail($this->getActionMethodReflection($action));
432
    }
433
434
    /**
435
     * Returns the help information for the anonymous arguments for the action.
436
     * The returned value should be an array. The keys are the argument names, and the values are
437
     * the corresponding help information. Each value must be an array of the following structure:
438
     *
439
     * - required: boolean, whether this argument is required.
440
     * - type: string, the PHP type of this argument.
441
     * - default: string, the default value of this argument
442
     * - comment: string, the comment of this argument
443
     *
444
     * The default implementation will return the help information extracted from the doc-comment of
445
     * the parameters corresponding to the action method.
446
     *
447
     * @param Action $action
448
     * @return array the help information of the action arguments
449
     */
450 4
    public function getActionArgsHelp($action)
451
    {
452 4
        $method = $this->getActionMethodReflection($action);
453 4
        $tags = $this->parseDocCommentTags($method);
454 4
        $params = isset($tags['param']) ? (array) $tags['param'] : [];
455
456 4
        $args = [];
457
458
        /** @var \ReflectionParameter $reflection */
459 4
        foreach ($method->getParameters() as $i => $reflection) {
460 4
            $name = $reflection->getName();
461 4
            $tag = isset($params[$i]) ? $params[$i] : '';
462 4
            if (preg_match('/^(\S+)\s+(\$\w+\s+)?(.*)/s', $tag, $matches)) {
463 4
                $type = $matches[1];
464 4
                $comment = $matches[3];
465 4
            } else {
466
                $type = null;
467
                $comment = $tag;
468
            }
469 4
            if ($reflection->isDefaultValueAvailable()) {
470 2
                $args[$name] = [
471 2
                    'required' => false,
472 2
                    'type' => $type,
473 2
                    'default' => $reflection->getDefaultValue(),
474 2
                    'comment' => $comment,
475
                ];
476 2
            } else {
477 2
                $args[$name] = [
478 2
                    'required' => true,
479 2
                    'type' => $type,
480 2
                    'default' => null,
481 2
                    'comment' => $comment,
482
                ];
483
            }
484 4
        }
485 4
        return $args;
486
    }
487
488
    /**
489
     * Returns the help information for the options for the action.
490
     * The returned value should be an array. The keys are the option names, and the values are
491
     * the corresponding help information. Each value must be an array of the following structure:
492
     *
493
     * - type: string, the PHP type of this argument.
494
     * - default: string, the default value of this argument
495
     * - comment: string, the comment of this argument
496
     *
497
     * The default implementation will return the help information extracted from the doc-comment of
498
     * the properties corresponding to the action options.
499
     *
500
     * @param Action $action
501
     * @return array the help information of the action options
502
     */
503 3
    public function getActionOptionsHelp($action)
504
    {
505 3
        $optionNames = $this->options($action->id);
506 3
        if (empty($optionNames)) {
507
            return [];
508
        }
509
510 3
        $class = new \ReflectionClass($this);
511 3
        $options = [];
512 3
        foreach ($class->getProperties() as $property) {
513 3
            $name = $property->getName();
514 3
            if (!in_array($name, $optionNames, true)) {
515 3
                continue;
516
            }
517 3
            $defaultValue = $property->getValue($this);
518 3
            $tags = $this->parseDocCommentTags($property);
519 3
            if (isset($tags['var']) || isset($tags['property'])) {
520 3
                $doc = isset($tags['var']) ? $tags['var'] : $tags['property'];
521 3
                if (is_array($doc)) {
522
                    $doc = reset($doc);
523
                }
524 3
                if (preg_match('/^(\S+)(.*)/s', $doc, $matches)) {
525 3
                    $type = $matches[1];
526 3
                    $comment = $matches[2];
527 3
                } else {
528
                    $type = null;
529
                    $comment = $doc;
530
                }
531 3
                $options[$name] = [
532 3
                    'type' => $type,
533 3
                    'default' => $defaultValue,
534 3
                    'comment' => $comment,
535
                ];
536 3
            } else {
537
                $options[$name] = [
538
                    'type' => null,
539
                    'default' => $defaultValue,
540
                    'comment' => '',
541
                ];
542
            }
543 3
        }
544 3
        return $options;
545
    }
546
547
    private $_reflections = [];
548
549
    /**
550
     * @param Action $action
551
     * @return \ReflectionMethod
552
     */
553 5
    protected function getActionMethodReflection($action)
554
    {
555 5
        if (!isset($this->_reflections[$action->id])) {
556 5
            if ($action instanceof InlineAction) {
557 5
                $this->_reflections[$action->id] = new \ReflectionMethod($this, $action->actionMethod);
558 5
            } else {
559
                $this->_reflections[$action->id] = new \ReflectionMethod($action, 'run');
560
            }
561 5
        }
562 5
        return $this->_reflections[$action->id];
563
    }
564
565
    /**
566
     * Parses the comment block into tags.
567
     * @param \Reflector $reflection the comment block
568
     * @return array the parsed tags
569
     */
570 4
    protected function parseDocCommentTags($reflection)
571
    {
572 4
        $comment = $reflection->getDocComment();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Reflector as the method getDocComment() does only exist in the following implementations of said interface: ReflectionClass, ReflectionFunction, ReflectionFunctionAbstract, ReflectionMethod, ReflectionObject, ReflectionProperty.

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...
573 4
        $comment = "@description \n" . strtr(trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($comment, '/'))), "\r", '');
574 4
        $parts = preg_split('/^\s*@/m', $comment, -1, PREG_SPLIT_NO_EMPTY);
575 4
        $tags = [];
576 4
        foreach ($parts as $part) {
577 4
            if (preg_match('/^(\w+)(.*)/ms', trim($part), $matches)) {
578 4
                $name = $matches[1];
579 4
                if (!isset($tags[$name])) {
580 4
                    $tags[$name] = trim($matches[2]);
581 4
                } elseif (is_array($tags[$name])) {
582
                    $tags[$name][] = trim($matches[2]);
583
                } else {
584
                    $tags[$name] = [$tags[$name], trim($matches[2])];
585
                }
586 4
            }
587 4
        }
588 4
        return $tags;
589
    }
590
591
    /**
592
     * Returns the first line of docblock.
593
     *
594
     * @param \Reflector $reflection
595
     * @return string
596
     */
597 2
    protected function parseDocCommentSummary($reflection)
598
    {
599 2
        $docLines = preg_split('~\R~u', $reflection->getDocComment());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Reflector as the method getDocComment() does only exist in the following implementations of said interface: ReflectionClass, ReflectionFunction, ReflectionFunctionAbstract, ReflectionMethod, ReflectionObject, ReflectionProperty.

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...
600 2
        if (isset($docLines[1])) {
601 2
            return trim($docLines[1], "\t *");
602
        }
603
        return '';
604
    }
605
606
    /**
607
     * Returns full description from the docblock.
608
     *
609
     * @param \Reflector $reflection
610
     * @return string
611
     */
612 2
    protected function parseDocCommentDetail($reflection)
613
    {
614 2
        $comment = strtr(trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($reflection->getDocComment(), '/'))), "\r", '');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Reflector as the method getDocComment() does only exist in the following implementations of said interface: ReflectionClass, ReflectionFunction, ReflectionFunctionAbstract, ReflectionMethod, ReflectionObject, ReflectionProperty.

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...
615 2
        if (preg_match('/^\s*@\w+/m', $comment, $matches, PREG_OFFSET_CAPTURE)) {
616 2
            $comment = trim(substr($comment, 0, $matches[0][1]));
617 2
        }
618 2
        if ($comment !== '') {
619 2
            return rtrim(Console::renderColoredString(Console::markdownToAnsi($comment)));
620
        }
621
        return '';
622
    }
623
}
624