PluginProbe ʕ •ᴥ•ʔ
Cookiebot by Usercentrics – Automatic Cookie Banner for GDPR/CCPA & Google Consent Mode / 2.4.1
Cookiebot by Usercentrics – Automatic Cookie Banner for GDPR/CCPA & Google Consent Mode v2.4.1
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 7 years ago
PhpDocReader.php
312 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 );
38
39 /**
40 * Enable or disable throwing errors when PhpDoc Errors occur (when parsing annotations)
41 *
42 * @var bool
43 */
44 private $ignorePhpDocErrors;
45
46 /**
47 *
48 * @param bool $ignorePhpDocErrors
49 */
50 public function __construct($ignorePhpDocErrors = false)
51 {
52 $this->parser = new UseStatementParser();
53 $this->ignorePhpDocErrors = $ignorePhpDocErrors;
54 }
55
56 /**
57 * Parse the docblock of the property to get the class of the var annotation.
58 *
59 * @param ReflectionProperty $property
60 *
61 * @throws AnnotationException
62 * @return string|null Type of the property (content of var annotation)
63 *
64 * @deprecated Use getPropertyClass instead.
65 */
66 public function getPropertyType(ReflectionProperty $property)
67 {
68 return $this->getPropertyClass($property);
69 }
70
71 /**
72 * Parse the docblock of the property to get the class of the var annotation.
73 *
74 * @param ReflectionProperty $property
75 *
76 * @throws AnnotationException
77 * @return string|null Type of the property (content of var annotation)
78 */
79 public function getPropertyClass(ReflectionProperty $property)
80 {
81 // Get the content of the @var annotation
82 if (preg_match('/@var\s+([^\s]+)/', $property->getDocComment(), $matches)) {
83 list(, $type) = $matches;
84 } else {
85 return null;
86 }
87
88 // Ignore primitive types
89 if (in_array($type, $this->ignoredTypes)) {
90 return null;
91 }
92
93 // Ignore types containing special characters ([], <> ...)
94 if (! preg_match('/^[a-zA-Z0-9\\\\_]+$/', $type)) {
95 return null;
96 }
97
98 $class = $property->getDeclaringClass();
99
100 // If the class name is not fully qualified (i.e. doesn't start with a \)
101 if ($type[0] !== '\\') {
102 // Try to resolve the FQN using the class context
103 $resolvedType = $this->tryResolveFqn($type, $class, $property);
104
105 if (!$resolvedType && !$this->ignorePhpDocErrors) {
106 throw new AnnotationException(sprintf(
107 'The @var annotation on %s::%s contains a non existent class "%s". '
108 . 'Did you maybe forget to add a "use" statement for this annotation?',
109 $class->name,
110 $property->getName(),
111 $type
112 ));
113 }
114
115 $type = $resolvedType;
116 }
117
118 if (!$this->classExists($type) && !$this->ignorePhpDocErrors) {
119 throw new AnnotationException(sprintf(
120 'The @var annotation on %s::%s contains a non existent class "%s"',
121 $class->name,
122 $property->getName(),
123 $type
124 ));
125 }
126
127 // Remove the leading \ (FQN shouldn't contain it)
128 $type = ltrim($type, '\\');
129
130 return $type;
131 }
132
133 /**
134 * Parse the docblock of the property to get the class of the param annotation.
135 *
136 * @param ReflectionParameter $parameter
137 *
138 * @throws AnnotationException
139 * @return string|null Type of the property (content of var annotation)
140 *
141 * @deprecated Use getParameterClass instead.
142 */
143 public function getParameterType(ReflectionParameter $parameter)
144 {
145 return $this->getParameterClass($parameter);
146 }
147
148 /**
149 * Parse the docblock of the property to get the class of the param annotation.
150 *
151 * @param ReflectionParameter $parameter
152 *
153 * @throws AnnotationException
154 * @return string|null Type of the property (content of var annotation)
155 */
156 public function getParameterClass(ReflectionParameter $parameter)
157 {
158 // Use reflection
159 $parameterClass = $parameter->getClass();
160 if ($parameterClass !== null) {
161 return $parameterClass->name;
162 }
163
164 $parameterName = $parameter->name;
165 // Get the content of the @param annotation
166 $method = $parameter->getDeclaringFunction();
167 if (preg_match('/@param\s+([^\s]+)\s+\$' . $parameterName . '/', $method->getDocComment(), $matches)) {
168 list(, $type) = $matches;
169 } else {
170 return null;
171 }
172
173 // Ignore primitive types
174 if (in_array($type, $this->ignoredTypes)) {
175 return null;
176 }
177
178 // Ignore types containing special characters ([], <> ...)
179 if (! preg_match('/^[a-zA-Z0-9\\\\_]+$/', $type)) {
180 return null;
181 }
182
183 $class = $parameter->getDeclaringClass();
184
185 // If the class name is not fully qualified (i.e. doesn't start with a \)
186 if ($type[0] !== '\\') {
187 // Try to resolve the FQN using the class context
188 $resolvedType = $this->tryResolveFqn($type, $class, $parameter);
189
190 if (!$resolvedType && !$this->ignorePhpDocErrors) {
191 throw new AnnotationException(sprintf(
192 'The @param annotation for parameter "%s" of %s::%s contains a non existent class "%s". '
193 . 'Did you maybe forget to add a "use" statement for this annotation?',
194 $parameterName,
195 $class->name,
196 $method->name,
197 $type
198 ));
199 }
200
201 $type = $resolvedType;
202 }
203
204 if (!$this->classExists($type) && !$this->ignorePhpDocErrors) {
205 throw new AnnotationException(sprintf(
206 'The @param annotation for parameter "%s" of %s::%s contains a non existent class "%s"',
207 $parameterName,
208 $class->name,
209 $method->name,
210 $type
211 ));
212 }
213
214 // Remove the leading \ (FQN shouldn't contain it)
215 $type = ltrim($type, '\\');
216
217 return $type;
218 }
219
220 /**
221 * Attempts to resolve the FQN of the provided $type based on the $class and $member context.
222 *
223 * @param string $type
224 * @param ReflectionClass $class
225 * @param Reflector $member
226 *
227 * @return string|null Fully qualified name of the type, or null if it could not be resolved
228 */
229 private function tryResolveFqn($type, ReflectionClass $class, Reflector $member)
230 {
231 $alias = ($pos = strpos($type, '\\')) === false ? $type : substr($type, 0, $pos);
232 $loweredAlias = strtolower($alias);
233
234 // Retrieve "use" statements
235 $uses = $this->parser->parseUseStatements($class);
236
237 if (isset($uses[$loweredAlias])) {
238 // Imported classes
239 if ($pos !== false) {
240 return $uses[$loweredAlias] . substr($type, $pos);
241 } else {
242 return $uses[$loweredAlias];
243 }
244 } elseif ($this->classExists($class->getNamespaceName() . '\\' . $type)) {
245 return $class->getNamespaceName() . '\\' . $type;
246 } elseif (isset($uses['__NAMESPACE__']) && $this->classExists($uses['__NAMESPACE__'] . '\\' . $type)) {
247 // Class namespace
248 return $uses['__NAMESPACE__'] . '\\' . $type;
249 } elseif ($this->classExists($type)) {
250 // No namespace
251 return $type;
252 }
253
254 if (version_compare(phpversion(), '5.4.0', '<')) {
255 return null;
256 } else {
257 // If all fail, try resolving through related traits
258 return $this->tryResolveFqnInTraits($type, $class, $member);
259 }
260 }
261
262 /**
263 * Attempts to resolve the FQN of the provided $type based on the $class and $member context, specifically searching
264 * through the traits that are used by the provided $class.
265 *
266 * @param string $type
267 * @param ReflectionClass $class
268 * @param Reflector $member
269 *
270 * @return string|null Fully qualified name of the type, or null if it could not be resolved
271 */
272 private function tryResolveFqnInTraits($type, ReflectionClass $class, Reflector $member)
273 {
274 /** @var ReflectionClass[] $traits */
275 $traits = array();
276
277 // Get traits for the class and its parents
278 while ($class) {
279 $traits = array_merge($traits, $class->getTraits());
280 $class = $class->getParentClass();
281 }
282
283 foreach ($traits as $trait) {
284 // Eliminate traits that don't have the property/method/parameter
285 if ($member instanceof ReflectionProperty && !$trait->hasProperty($member->name)) {
286 continue;
287 } elseif ($member instanceof ReflectionMethod && !$trait->hasMethod($member->name)) {
288 continue;
289 } elseif ($member instanceof ReflectionParameter && !$trait->hasMethod($member->getDeclaringFunction()->name)) {
290 continue;
291 }
292
293 // Run the resolver again with the ReflectionClass instance for the trait
294 $resolvedType = $this->tryResolveFqn($type, $trait, $member);
295
296 if ($resolvedType) {
297 return $resolvedType;
298 }
299 }
300 return null;
301 }
302
303 /**
304 * @param string $class
305 * @return bool
306 */
307 private function classExists($class)
308 {
309 return class_exists($class) || interface_exists($class);
310 }
311 }
312