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) { |
|
|
|
|
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 |
|
|
|
|
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
|
|
|
|
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.