CodeIssue::getShortLocationWithPrevious()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 11
rs 9.9
c 0
b 0
f 0
1
<?php
2
namespace Psalm\Issue;
3
4
use Psalm\CodeLocation;
5
use Psalm\Config;
6
use function explode;
7
use function get_called_class;
8
use function array_pop;
9
10
abstract class CodeIssue
11
{
12
    const ERROR_LEVEL = -1;
13
    const SHORTCODE = 0;
14
15
    /**
16
     * @var CodeLocation
17
     */
18
    protected $code_location;
19
20
    /**
21
     * @var string
22
     */
23
    protected $message;
24
25
    /**
26
     * @param string        $message
27
     * @param CodeLocation  $code_location
28
     */
29
    public function __construct(
30
        $message,
31
        CodeLocation $code_location
32
    ) {
33
        $this->code_location = $code_location;
34
        $this->message = $message;
35
    }
36
37
    /**
38
     * @return CodeLocation
39
     */
40
    public function getLocation()
41
    {
42
        return $this->code_location;
43
    }
44
45
    /**
46
     * @return string
47
     */
48
    public function getShortLocationWithPrevious()
49
    {
50
        $previous_text = '';
51
52
        if ($this->code_location->previous_location) {
53
            $previous_location = $this->code_location->previous_location;
54
            $previous_text = ' from ' . $previous_location->file_name . ':' . $previous_location->getLineNumber();
55
        }
56
57
        return $this->code_location->file_name . ':' . $this->code_location->getLineNumber() . $previous_text;
58
    }
59
60
    /**
61
     * @return string
62
     */
63
    public function getShortLocation()
64
    {
65
        return $this->code_location->file_name . ':' . $this->code_location->getLineNumber();
66
    }
67
68
    /**
69
     * @return string
70
     */
71
    public function getFilePath()
72
    {
73
        return $this->code_location->file_path;
74
    }
75
76
    /**
77
     * @return string
78
     *
79
     * @psalm-suppress PossiblyUnusedMethod for convenience
80
     */
81
    public function getFileName()
82
    {
83
        return $this->code_location->file_name;
84
    }
85
86
    /**
87
     * @return string
88
     */
89
    public function getMessage()
90
    {
91
        return $this->message;
92
    }
93
94
    /**
95
     * @param  string          $severity
96
     *
97
     * @return \Psalm\Internal\Analyzer\IssueData
98
     */
99
    public function toIssueData($severity = Config::REPORT_ERROR)
100
    {
101
        $location = $this->getLocation();
102
        $selection_bounds = $location->getSelectionBounds();
103
        $snippet_bounds = $location->getSnippetBounds();
104
105
        $fqcn_parts = explode('\\', get_called_class());
106
        $issue_type = array_pop($fqcn_parts);
107
108
        return new \Psalm\Internal\Analyzer\IssueData(
109
            $severity,
110
            $location->getLineNumber(),
111
            $location->getEndLineNumber(),
112
            $issue_type,
113
            $this->getMessage(),
114
            $location->file_name,
115
            $location->file_path,
116
            $location->getSnippet(),
117
            $location->getSelectedText(),
118
            $selection_bounds[0],
119
            $selection_bounds[1],
120
            $snippet_bounds[0],
121
            $snippet_bounds[1],
122
            $location->getColumn(),
123
            $location->getEndColumn(),
124
            (int) static::SHORTCODE,
125
            (int) static::ERROR_LEVEL,
126
            $this instanceof TaintedInput ? $this->getTaintTrace() : null
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Psalm\Issue\CodeIssue as the method getTaintTrace() does only exist in the following sub-classes of Psalm\Issue\CodeIssue: Psalm\Issue\TaintedInput. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
127
        );
128
    }
129
}
130