Name::parseName()   A
last analyzed

Complexity

Conditions 5
Paths 4

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 18
c 0
b 0
f 0
rs 9.6111
cc 5
nc 4
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ray\Di;
6
7
use Ray\Di\Di\Named;
8
use Ray\Di\Di\Qualifier;
9
use ReflectionAttribute;
10
use ReflectionClass;
11
use ReflectionException;
12
use ReflectionMethod;
13
use ReflectionParameter;
14
15
use function assert;
16
use function class_exists;
17
use function explode;
18
use function get_class;
19
use function is_string;
20
use function preg_match;
21
use function substr;
22
use function trim;
23
24
final class Name
25
{
26
    /**
27
     * 'Unnamed' name
28
     */
29
    public const ANY = '';
30
31
    /** @var string */
32
    private $name = '';
33
34
    /**
35
     * Named database
36
     *
37
     * format: array<varName, NamedName>
38
     *
39
     * @var array<string, string>
40
     */
41
    private $names;
42
43
    /**
44
     * @param string|array<string, string>|null $name
45
     */
46
    public function __construct($name = null)
47
    {
48
        if ($name === null) {
49
            return;
50
        }
51
52
        if (is_string($name)) {
53
            $this->setName($name);
54
55
            return;
56
        }
57
58
        $this->names = $name;
59
    }
60
61
    /**
62
     * Create instance from PHP8 attributes
63
     *
64
     * psalm does not know ReflectionAttribute?? PHPStan produces no type error here.
65
     */
66
    public static function withAttributes(ReflectionMethod $method): ?self
67
    {
68
        $params = $method->getParameters();
69
        $names = [];
70
        foreach ($params as $param) {
71
            /** @var array<ReflectionAttribute> $attributes */
72
            $attributes = $param->getAttributes();
73
            if ($attributes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $attributes of type ReflectionAttribute[] 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...
74
                $names[$param->name] = self::getName($attributes);
75
            }
76
        }
77
78
        if ($names) {
79
            return new self($names);
80
        }
81
82
        return null;
83
    }
84
85
    /**
86
     * @param non-empty-array<ReflectionAttribute> $attributes
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-array<ReflectionAttribute> at position 0 could not be parsed: Unknown type name 'non-empty-array' at position 0 in non-empty-array<ReflectionAttribute>.
Loading history...
87
     *
88
     * @throws ReflectionException
89
     *
90
     * @psalm-suppress MixedAssignment
91
     * @psalm-suppress MixedArgument
92
     */
93
    private static function getName(array $attributes): string
94
    {
95
        $refAttribute = $attributes[0];
96
        $attribute = $refAttribute->newInstance();
97
        if ($attribute instanceof Named) {
98
            return $attribute->value;
99
        }
100
101
        $isQualifier = (bool) (new ReflectionClass($attribute))->getAttributes(Qualifier::class);
102
        if ($isQualifier) {
103
            return get_class($attribute);
104
        }
105
106
        return '';
107
    }
108
109
    public function __invoke(ReflectionParameter $parameter): string
110
    {
111
        // single variable named binding
112
        if ($this->name) {
113
            return $this->name;
114
        }
115
116
        // multiple variable named binding
117
        return $this->names[$parameter->name] ?? $this->names[self::ANY] ?? self::ANY;
118
    }
119
120
    private function setName(string $name): void
121
    {
122
        // annotation
123
        if (class_exists($name, false)) {
124
            $this->name = $name;
125
126
            return;
127
        }
128
129
        // single name
130
        // @Named(name)
131
        if ($name === self::ANY || preg_match('/^\w+$/', $name)) {
132
            $this->name = $name;
133
134
            return;
135
        }
136
137
        // name list
138
        // @Named(varName1=name1, varName2=name2)]
139
        $this->names = $this->parseName($name);
140
    }
141
142
    /**
143
     * @return array<string, string>
144
     */
145
    private function parseName(string $name): array
146
    {
147
        $names = [];
148
        $keyValues = explode(',', $name);
149
        foreach ($keyValues as $keyValue) {
150
            $exploded = explode('=', $keyValue);
151
            if (isset($exploded[1])) {
152
                [$key, $value] = $exploded;
153
                assert(is_string($key));
154
                if (isset($key[0]) && $key[0] === '$') {
155
                    $key = substr($key, 1);
156
                }
157
158
                $names[trim($key)] = trim($value);
159
            }
160
        }
161
162
        return $names;
163
    }
164
}
165