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 ( f9fd4d...f5c98f )
by Robert
11:43
created

Controller   C

Complexity

Total Complexity 74

Size/Duplication

Total Lines 617
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 80.54%

Importance

Changes 0
Metric Value
wmc 74
lcom 1
cbo 6
dl 0
loc 617
ccs 149
cts 185
cp 0.8054
rs 5.4432
c 0
b 0
f 0

24 Methods

Rating   Name   Duplication   Size   Complexity  
A isColorEnabled() 0 4 2
C runAction() 0 41 12
C bindActionParams() 0 30 8
A ansiFormat() 0 10 2
A stdout() 0 10 2
A stderr() 0 10 2
A prompt() 0 8 3
A confirm() 0 8 2
A select() 0 4 1
A options() 0 5 1
A optionAliases() 0 6 1
A getOptionValues() 0 10 2
A getPassedOptions() 0 4 1
A getPassedOptionValues() 0 9 2
A getHelpSummary() 0 4 1
A getHelp() 0 4 1
A getActionHelpSummary() 0 4 1
A getActionHelp() 0 4 1
C getActionArgsHelp() 0 41 7
D getActionOptionsHelp() 0 44 9
A getActionMethodReflection() 0 12 3
B parseDocCommentTags() 0 21 5
A parseDocCommentDetail() 0 12 3
A parseDocCommentSummary() 0 9 2

How to fix   Complexity   

Complex Class

Complex classes like Controller often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Controller, and based on these observations, apply Extract Interface, too.

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