1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types = 1); |
4
|
|
|
|
5
|
|
|
namespace Leaditin\Annotations; |
6
|
|
|
|
7
|
|
|
use Leaditin\Annotations\Exception\ReaderException; |
8
|
|
|
|
9
|
|
|
/** |
10
|
|
|
* Class Reader |
11
|
|
|
* |
12
|
|
|
* @package Leaditin\Annotations |
13
|
|
|
* @author Igor Vuckovic <[email protected]> |
14
|
|
|
*/ |
15
|
|
|
class Reader |
16
|
|
|
{ |
17
|
|
|
/** @var \ReflectionClass */ |
18
|
|
|
protected $reflectionClass; |
19
|
|
|
|
20
|
|
|
/** @var Tokenizer */ |
21
|
|
|
protected $tokenizer; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* @param string $class |
25
|
|
|
* @return Reflection |
26
|
|
|
* @throws ReaderException |
27
|
|
|
*/ |
28
|
7 |
|
public function parse(string $class) : Reflection |
29
|
|
|
{ |
30
|
|
|
try { |
31
|
7 |
|
$this->reflectionClass = new \ReflectionClass($class); |
32
|
6 |
|
$this->tokenizer = new Tokenizer($this->reflectionClass); |
33
|
|
|
|
34
|
6 |
|
return new Reflection( |
35
|
6 |
|
$this->readAnnotationsFromClass(), |
36
|
6 |
|
$this->readAnnotationsFromMethods(), |
37
|
6 |
|
$this->readAnnotationsFromProperties() |
38
|
|
|
); |
39
|
1 |
|
} catch (\ReflectionException $ex) { |
40
|
1 |
|
throw ReaderException::unableToReadClass($class, $ex); |
41
|
|
|
} |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* @return Collection |
46
|
|
|
*/ |
47
|
6 |
|
protected function readAnnotationsFromClass() : Collection |
48
|
|
|
{ |
49
|
6 |
|
$collection = $this->parseComment($this->reflectionClass->getDocComment()); |
50
|
|
|
|
51
|
6 |
|
return new Collection(...$collection); |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* @return array|Collection[] |
56
|
|
|
*/ |
57
|
6 |
View Code Duplication |
protected function readAnnotationsFromMethods() : array |
|
|
|
|
58
|
|
|
{ |
59
|
6 |
|
$annotationsFromMethods = []; |
60
|
6 |
|
foreach ($this->reflectionClass->getMethods() as $method) { |
61
|
6 |
|
$methodCollection = $this->parseComment($method->getDocComment()); |
62
|
6 |
|
if (empty($methodCollection)) { |
63
|
6 |
|
continue; |
64
|
|
|
} |
65
|
|
|
|
66
|
6 |
|
$annotationsFromMethods[$method->name] = new Collection(...$methodCollection); |
67
|
|
|
} |
68
|
|
|
|
69
|
6 |
|
return $annotationsFromMethods; |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* @return array|Collection[] |
74
|
|
|
*/ |
75
|
6 |
View Code Duplication |
protected function readAnnotationsFromProperties() : array |
|
|
|
|
76
|
|
|
{ |
77
|
6 |
|
$annotationsFromProperties = []; |
78
|
6 |
|
foreach ($this->reflectionClass->getProperties() as $property) { |
79
|
6 |
|
$propertyCollection = $this->parseComment($property->getDocComment()); |
80
|
6 |
|
if (empty($propertyCollection)) { |
81
|
6 |
|
continue; |
82
|
|
|
} |
83
|
|
|
|
84
|
6 |
|
$annotationsFromProperties[$property->name] = new Collection(...$propertyCollection); |
85
|
|
|
} |
86
|
|
|
|
87
|
6 |
|
return $annotationsFromProperties; |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* @param string|bool $comment |
92
|
|
|
* @return array|Annotation[] |
93
|
|
|
*/ |
94
|
6 |
|
protected function parseComment($comment = false) : array |
95
|
|
|
{ |
96
|
6 |
|
$annotations = []; |
97
|
|
|
|
98
|
6 |
|
if ($comment === false) { |
99
|
6 |
|
return $annotations; |
100
|
|
|
} |
101
|
|
|
|
102
|
6 |
|
preg_match_all('/@([^*]+)/', $comment, $matches); |
103
|
6 |
|
foreach ($matches[1] as $match) { |
104
|
6 |
|
$annotations[] = $this->parseLine($match); |
105
|
|
|
} |
106
|
|
|
|
107
|
6 |
|
return $annotations; |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* @param string $line |
112
|
|
|
* @return Annotation |
113
|
|
|
*/ |
114
|
6 |
|
protected function parseLine(string $line) : Annotation |
115
|
|
|
{ |
116
|
6 |
|
preg_match('/(\w+)[\s|\(]*([^\)]*)/', $line, $matches); |
117
|
6 |
|
$name = $matches[1]; |
118
|
6 |
|
$arguments = $this->parseArgumentsFromLine($matches[2] ?: ''); |
119
|
6 |
|
$this->filterArguments($name, $arguments); |
120
|
|
|
|
121
|
6 |
|
return new Annotation($name, $arguments); |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* @param string $line |
126
|
|
|
* @return array |
127
|
|
|
*/ |
128
|
6 |
|
protected function parseArgumentsFromLine(string $line) : array |
129
|
|
|
{ |
130
|
6 |
|
$line = trim($line); |
131
|
6 |
|
$arguments = []; |
132
|
|
|
|
133
|
6 |
|
if ($line === '') { |
134
|
6 |
|
return $arguments; |
135
|
|
|
} |
136
|
|
|
|
137
|
6 |
|
$data = $this->exportArgumentsFromLine($line); |
138
|
6 |
|
foreach ($data as $index => $property) { |
139
|
6 |
|
$properties = explode('=', $property, 2); |
140
|
6 |
|
if (count($properties) === 1) { |
141
|
6 |
|
$arguments[$index] = $this->filterValue($property); |
142
|
|
|
} else { |
143
|
6 |
|
$arguments[$this->filterName($properties[0])] = $this->filterValue($properties[1]); |
144
|
|
|
} |
145
|
|
|
} |
146
|
|
|
|
147
|
6 |
|
return $arguments; |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
/** |
151
|
|
|
* @param string $line |
152
|
|
|
* @return array |
153
|
|
|
*/ |
154
|
6 |
|
protected function exportArgumentsFromLine(string $line) : array |
155
|
|
|
{ |
156
|
6 |
|
if (false !== strpos($line, '|')) { |
157
|
6 |
|
return explode('|', $line); |
158
|
|
|
} |
159
|
|
|
|
160
|
6 |
|
return explode(',', $line); |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* @param string $name |
165
|
|
|
* @return string |
166
|
|
|
*/ |
167
|
|
|
protected function filterName(string $name) : string |
168
|
|
|
{ |
169
|
6 |
|
return preg_replace_callback('/^"([^"]*)"$|^\'([^\']*)\'$/', function ($matches) { |
170
|
6 |
|
return $matches[2] ?? $matches[1]; |
171
|
6 |
|
}, trim($name)); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* @param string $value |
176
|
|
|
* @return int|string|bool|float|array |
177
|
|
|
*/ |
178
|
6 |
|
protected function filterValue(string $value) |
179
|
|
|
{ |
180
|
6 |
|
$value = $this->filterName($value); |
181
|
6 |
|
$data = json_decode(str_replace('\'', '"', $value), true); |
182
|
|
|
|
183
|
6 |
|
return $data ?? $value; |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
/** |
187
|
|
|
* @param string $name |
188
|
|
|
* @param array $arguments |
189
|
|
|
*/ |
190
|
6 |
|
protected function filterArguments(string $name, array &$arguments) |
191
|
|
|
{ |
192
|
|
|
$allowed = [ |
193
|
6 |
|
'var', 'param', 'property', 'method', 'return', 'throws', |
194
|
|
|
]; |
195
|
|
|
|
196
|
6 |
|
if (!in_array($name, $allowed, false)) { |
197
|
6 |
|
return; |
198
|
|
|
} |
199
|
|
|
|
200
|
6 |
|
array_walk($arguments, function (&$val) { |
201
|
6 |
|
$val = preg_replace('/(\$\S+)/', '', $val); |
202
|
6 |
|
$val = trim($val); |
203
|
6 |
|
$val = $this->tokenizer->getFullyQualifiedClassName($val); |
204
|
6 |
|
}); |
205
|
|
|
} |
206
|
|
|
} |
207
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.