PluginProbe ʕ •ᴥ•ʔ
Cookiebot by Usercentrics – Automatic Cookie Banner for GDPR/CCPA & Google Consent Mode / 3.10.0
Cookiebot by Usercentrics – Automatic Cookie Banner for GDPR/CCPA & Google Consent Mode v3.10.0
4.7.2 4.7.1 trunk 2.3.0 2.4.0 2.4.1 2.4.2 2.5.0 3.0.0 3.0.1 3.1.0 3.10.0 3.10.1 3.11.1 3.11.2 3.11.3 3.2.0 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.6.1 3.6.2 3.6.5 3.6.6 3.7.0 3.7.1 3.8.0 3.9.0 4.0.0 4.0.1 4.0.2 4.0.3 4.1.0 4.1.1 4.2.0 4.2.1 4.2.10 4.2.11 4.2.12 4.2.13 4.2.14 4.2.2 4.2.3 4.2.4 4.2.5 4.2.6 4.2.7 4.2.8 4.2.9 4.3.0 4.3.1 4.3.10 4.3.11 4.3.12 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.3.7 4.3.7.1 4.3.8 4.3.9 4.3.9.1 4.4.0 4.4.1 4.4.2 4.5.0 4.5.1 4.5.10 4.5.11 4.5.2 4.5.3 4.5.4 4.5.5 4.5.6 4.5.7 4.5.8 4.5.9 4.6.0 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.6.6 4.6.7 4.7.0
cookiebot / addons / lib / ioc / php-di / phpdoc-reader / src / PhpDocReader / PhpDocReader.php
cookiebot / addons / lib / ioc / php-di / phpdoc-reader / src / PhpDocReader Last commit date
PhpParser 7 years ago AnnotationException.php 7 years ago PhpDocReader.php 6 years ago
PhpDocReader.php
313 lines
1 <?php
2
3 namespace PhpDocReader;
4
5 use PhpDocReader\PhpParser\UseStatementParser;
6 use ReflectionClass;
7 use ReflectionMethod;
8 use ReflectionParameter;
9 use ReflectionProperty;
10 use Reflector;
11
12 /**
13 * PhpDoc reader
14 *
15 * @author Matthieu Napoli <matthieu@mnapoli.fr>
16 */
17 class PhpDocReader
18 {
19 /**
20 * @var UseStatementParser
21 */
22 private $parser;
23
24 private $ignoredTypes = array(
25 'bool',
26 'boolean',
27 'string',
28 'int',
29 'integer',
30 'float',
31 'double',
32 'array',
33 'object',
34 'callable',
35 'resource',
36 'mixed',
37 'iterable',
38 );
39
40 /**
41 * Enable or disable throwing errors when PhpDoc Errors occur (when parsing annotations)
42 *
43 * @var bool
44 */
45 private $ignorePhpDocErrors;
46
47 /**
48 *
49 * @param bool $ignorePhpDocErrors
50 */
51 public function __construct($ignorePhpDocErrors = false)
52 {
53 $this->parser = new UseStatementParser();
54 $this->ignorePhpDocErrors = $ignorePhpDocErrors;
55 }
56
57 /**
58 * Parse the docblock of the property to get the class of the var annotation.
59 *
60 * @param ReflectionProperty $property
61 *
62 * @throws AnnotationException
63 * @return string|null Type of the property (content of var annotation)
64 *
65 * @deprecated Use getPropertyClass instead.
66 */
67 public function getPropertyType(ReflectionProperty $property)
68 {
69 return $this->getPropertyClass($property);
70 }
71
72 /**
73 * Parse the docblock of the property to get the class of the var annotation.
74 *
75 * @param ReflectionProperty $property
76 *
77 * @throws AnnotationException
78 * @return string|null Type of the property (content of var annotation)
79 */
80 public function getPropertyClass(ReflectionProperty $property)
81 {
82 // Get the content of the @var annotation
83 if (preg_match('/@var\s+([^\s]+)/', $property->getDocComment(), $matches)) {
84 list(, $type) = $matches;
85 } else {
86 return null;
87 }
88
89 // Ignore primitive types
90 if (in_array($type, $this->ignoredTypes)) {
91 return null;
92 }
93
94 // Ignore types containing special characters ([], <> ...)
95 if (! preg_match('/^[a-zA-Z0-9\\\\_]+$/', $type)) {
96 return null;
97 }
98
99 $class = $property->getDeclaringClass();
100
101 // If the class name is not fully qualified (i.e. doesn't start with a \)
102 if ($type[0] !== '\\') {
103 // Try to resolve the FQN using the class context
104 $resolvedType = $this->tryResolveFqn($type, $class, $property);
105
106 if (!$resolvedType && !$this->ignorePhpDocErrors) {
107 throw new AnnotationException(sprintf(
108 'The @var annotation on %s::%s contains a non existent class "%s". '
109 . 'Did you maybe forget to add a "use" statement for this annotation?',
110 $class->name,
111 $property->getName(),
112 $type
113 ));
114 }
115
116 $type = $resolvedType;
117 }
118
119 if (!$this->classExists($type) && !$this->ignorePhpDocErrors) {
120 throw new AnnotationException(sprintf(
121 'The @var annotation on %s::%s contains a non existent class "%s"',
122 $class->name,
123 $property->getName(),
124 $type
125 ));
126 }
127
128 // Remove the leading \ (FQN shouldn't contain it)
129 $type = ltrim($type, '\\');
130
131 return $type;
132 }
133
134 /**
135 * Parse the docblock of the property to get the class of the param annotation.
136 *
137 * @param ReflectionParameter $parameter
138 *
139 * @throws AnnotationException
140 * @return string|null Type of the property (content of var annotation)
141 *
142 * @deprecated Use getParameterClass instead.
143 */
144 public function getParameterType(ReflectionParameter $parameter)
145 {
146 return $this->getParameterClass($parameter);
147 }
148
149 /**
150 * Parse the docblock of the property to get the class of the param annotation.
151 *
152 * @param ReflectionParameter $parameter
153 *
154 * @throws AnnotationException
155 * @return string|null Type of the property (content of var annotation)
156 */
157 public function getParameterClass(ReflectionParameter $parameter)
158 {
159 // Use reflection
160 $parameterClass = $parameter->getClass();
161 if ($parameterClass !== null) {
162 return $parameterClass->name;
163 }
164
165 $parameterName = $parameter->name;
166 // Get the content of the @param annotation
167 $method = $parameter->getDeclaringFunction();
168 if (preg_match('/@param\s+([^\s]+)\s+\$' . $parameterName . '/', $method->getDocComment(), $matches)) {
169 list(, $type) = $matches;
170 } else {
171 return null;
172 }
173
174 // Ignore primitive types
175 if (in_array($type, $this->ignoredTypes)) {
176 return null;
177 }
178
179 // Ignore types containing special characters ([], <> ...)
180 if (! preg_match('/^[a-zA-Z0-9\\\\_]+$/', $type)) {
181 return null;
182 }
183
184 $class = $parameter->getDeclaringClass();
185
186 // If the class name is not fully qualified (i.e. doesn't start with a \)
187 if ($type[0] !== '\\') {
188 // Try to resolve the FQN using the class context
189 $resolvedType = $this->tryResolveFqn($type, $class, $parameter);
190
191 if (!$resolvedType && !$this->ignorePhpDocErrors) {
192 throw new AnnotationException(sprintf(
193 'The @param annotation for parameter "%s" of %s::%s contains a non existent class "%s". '
194 . 'Did you maybe forget to add a "use" statement for this annotation?',
195 $parameterName,
196 $class->name,
197 $method->name,
198 $type
199 ));
200 }
201
202 $type = $resolvedType;
203 }
204
205 if (!$this->classExists($type) && !$this->ignorePhpDocErrors) {
206 throw new AnnotationException(sprintf(
207 'The @param annotation for parameter "%s" of %s::%s contains a non existent class "%s"',
208 $parameterName,
209 $class->name,
210 $method->name,
211 $type
212 ));
213 }
214
215 // Remove the leading \ (FQN shouldn't contain it)
216 $type = ltrim($type, '\\');
217
218 return $type;
219 }
220
221 /**
222 * Attempts to resolve the FQN of the provided $type based on the $class and $member context.
223 *
224 * @param string $type
225 * @param ReflectionClass $class
226 * @param Reflector $member
227 *
228 * @return string|null Fully qualified name of the type, or null if it could not be resolved
229 */
230 private function tryResolveFqn($type, ReflectionClass $class, Reflector $member)
231 {
232 $alias = ($pos = strpos($type, '\\')) === false ? $type : substr($type, 0, $pos);
233 $loweredAlias = strtolower($alias);
234
235 // Retrieve "use" statements
236 $uses = $this->parser->parseUseStatements($class);
237
238 if (isset($uses[$loweredAlias])) {
239 // Imported classes
240 if ($pos !== false) {
241 return $uses[$loweredAlias] . substr($type, $pos);
242 } else {
243 return $uses[$loweredAlias];
244 }
245 } elseif ($this->classExists($class->getNamespaceName() . '\\' . $type)) {
246 return $class->getNamespaceName() . '\\' . $type;
247 } elseif (isset($uses['__NAMESPACE__']) && $this->classExists($uses['__NAMESPACE__'] . '\\' . $type)) {
248 // Class namespace
249 return $uses['__NAMESPACE__'] . '\\' . $type;
250 } elseif ($this->classExists($type)) {
251 // No namespace
252 return $type;
253 }
254
255 if (version_compare(phpversion(), '5.4.0', '<')) {
256 return null;
257 } else {
258 // If all fail, try resolving through related traits
259 return $this->tryResolveFqnInTraits($type, $class, $member);
260 }
261 }
262
263 /**
264 * Attempts to resolve the FQN of the provided $type based on the $class and $member context, specifically searching
265 * through the traits that are used by the provided $class.
266 *
267 * @param string $type
268 * @param ReflectionClass $class
269 * @param Reflector $member
270 *
271 * @return string|null Fully qualified name of the type, or null if it could not be resolved
272 */
273 private function tryResolveFqnInTraits($type, ReflectionClass $class, Reflector $member)
274 {
275 /** @var ReflectionClass[] $traits */
276 $traits = array();
277
278 // Get traits for the class and its parents
279 while ($class) {
280 $traits = array_merge($traits, $class->getTraits());
281 $class = $class->getParentClass();
282 }
283
284 foreach ($traits as $trait) {
285 // Eliminate traits that don't have the property/method/parameter
286 if ($member instanceof ReflectionProperty && !$trait->hasProperty($member->name)) {
287 continue;
288 } elseif ($member instanceof ReflectionMethod && !$trait->hasMethod($member->name)) {
289 continue;
290 } elseif ($member instanceof ReflectionParameter && !$trait->hasMethod($member->getDeclaringFunction()->name)) {
291 continue;
292 }
293
294 // Run the resolver again with the ReflectionClass instance for the trait
295 $resolvedType = $this->tryResolveFqn($type, $trait, $member);
296
297 if ($resolvedType) {
298 return $resolvedType;
299 }
300 }
301 return null;
302 }
303
304 /**
305 * @param string $class
306 * @return bool
307 */
308 private function classExists($class)
309 {
310 return class_exists($class) || interface_exists($class);
311 }
312 }
313