Completed
Push — param-inject ( 83c5f6...e32603 )
by Akihito
02:28 queued 01:17
created

Name::__invoke()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 9.6
c 0
b 0
f 0
cc 4
nc 4
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ray\Di;
6
7
use phpDocumentor\Reflection\Types\ClassString;
8
use Ray\Aop\ReflectionMethod;
9
use Ray\Di\Di\Named;
10
use Reflection;
11
use ReflectionAttribute;
12
use ReflectionParameter;
13
14
use function assert;
15
use function class_exists;
16
use function explode;
17
use function is_string;
18
use function preg_match;
19
use function substr;
20
use function trim;
21
22
final class Name
23
{
24
    /**
25
     * 'Unnamed' name
26
     */
27
    public const ANY = '';
28
29
    /** @var string */
30
    private $name = '';
31
32
    /**
33
     * Named database
34
     *
35
     * format: array<varName, NamedName>
36
     *
37
     * @var array<string, string>
38
     */
39
    private $names = [];
40
41
    public function __construct(?string $name = null)
42
    {
43
        if ($name !== null) {
44
            $this->setName($name);
45
        }
46
    }
47
48
    /**
49
     * Create instance from PHP8 attributes
50
     *
51
     * @psalm-suppress MixedAssignment
52
     * @psalm-suppress UndefinedMethod
53
     * @psalm-suppress MixedMethodCall
54
     * @psalm-suppress MixedArrayAccess
55
     *
56
     * psalm does not know ReflectionAttribute?? PHPStan produces no type error here.
57
     */
58
    public function createFromAttributes(\ReflectionMethod $method): ?self
59
    {
60
        $params = $method->getParameters();
61
        foreach ($params as $param) {
62
            $attribue = $param->getAttributes(Named::class);
63
            if ($attribue) {
64
                $name = $attribue[0]->newInstance();
65
                assert($name instanceof Named);
66
                $this->names[$param->getName()] = $name->value;
0 ignored issues
show
Bug introduced by
Consider using $param->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
67
            }
68
        }
69
70
        if ($this->names) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->names of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
71
            return clone $this;
72
        }
73
74
        return null;
75
    }
76
77
    public function __invoke(ReflectionParameter $parameter): string
78
    {
79
        // single variable named binding
80
        if ($this->name) {
81
            return $this->name;
82
        }
83
84
        // multiple variable named binding
85
        if (isset($this->names[$parameter->name])) {
86
            return $this->names[$parameter->name];
87
        }
88
89
        // ANY match
90
        if (isset($this->names[self::ANY])) {
91
            return $this->names[self::ANY];
92
        }
93
94
        // not matched
95
        return self::ANY;
96
    }
97
98
    private function setName(string $name): void
99
    {
100
        // annotation
101
        if (class_exists($name, false)) {
102
            $this->name = $name;
103
104
            return;
105
        }
106
107
        // single name
108
        // @Named(name)
109
        if ($name === self::ANY || preg_match('/^\w+$/', $name)) {
110
            $this->name = $name;
111
112
            return;
113
        }
114
115
        // name list
116
        // @Named(varName1=name1, varName2=name2)]
117
        $this->names = $this->parseName($name);
118
    }
119
120
    /**
121
     * @return array<string, string>
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
122
     */
123
    private function parseName(string $name): array
124
    {
125
        $names = [];
126
        $keyValues = explode(',', $name);
127
        foreach ($keyValues as $keyValue) {
128
            $exploded = explode('=', $keyValue);
129
            if (isset($exploded[1])) {
130
                [$key, $value] = $exploded;
0 ignored issues
show
Bug introduced by
The variable $key does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $value does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
131
                assert(is_string($key));
132
                if (isset($key[0]) && $key[0] === '$') {
133
                    $key = substr($key, 1);
134
                }
135
136
                $names[trim($key)] = trim($value);
137
            }
138
        }
139
140
        return $names;
141
    }
142
}
143