PluginProbe
Elementor Website Builder – more than just a page builder / 3.27.4
Elementor Website Builder – more than just a page builder v3.27.4
4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 4.0.8 4.1.0-beta1 4.1.0-dev1 4.0.7 All 451 releases
elementor / vendor_prefixed / php-di / phpdoc-reader / src / PhpDocReader / PhpDocReader.php

PhpDocReader.php in Elementor Website Builder – more than just a page builder 3.27.4, at vendor_prefixed/php-di/phpdoc-reader/src/PhpDocReader/PhpDocReader.php

228 lines 9.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare (strict_types=1);
4 namespace ElementorDeps\PhpDocReader;
5
6 use ElementorDeps\PhpDocReader\PhpParser\UseStatementParser;
7 use ReflectionClass;
8 use ReflectionMethod;
9 use ReflectionParameter;
10 use ReflectionProperty;
11 use Reflector;
12 /**
13 * PhpDoc reader
14 */
15 class PhpDocReader
16 {
17 /** @var UseStatementParser */
18 private $parser;
19 private const PRIMITIVE_TYPES = ['bool' => 'bool', 'boolean' => 'bool', 'string' => 'string', 'int' => 'int', 'integer' => 'int', 'float' => 'float', 'double' => 'float', 'array' => 'array', 'object' => 'object', 'callable' => 'callable', 'resource' => 'resource', 'mixed' => 'mixed', 'iterable' => 'iterable'];
20 /** @var bool */
21 private $ignorePhpDocErrors;
22 /**
23 * @param bool $ignorePhpDocErrors Enable or disable throwing errors when PhpDoc errors occur (when parsing annotations).
24 */
25 public function __construct(bool $ignorePhpDocErrors = \false)
26 {
27 $this->parser = new UseStatementParser();
28 $this->ignorePhpDocErrors = $ignorePhpDocErrors;
29 }
30 /**
31 * Parse the docblock of the property to get the type (class or primitive type) of the var annotation.
32 *
33 * @return string|null Type of the property (content of var annotation)
34 * @throws AnnotationException
35 */
36 public function getPropertyType(ReflectionProperty $property) : ?string
37 {
38 return $this->readPropertyType($property, \true);
39 }
40 /**
41 * Parse the docblock of the property to get the class of the var annotation.
42 *
43 * @return string|null Type of the property (content of var annotation)
44 * @throws AnnotationException
45 */
46 public function getPropertyClass(ReflectionProperty $property) : ?string
47 {
48 return $this->readPropertyType($property, \false);
49 }
50 private function readPropertyType(ReflectionProperty $property, bool $allowPrimitiveTypes) : ?string
51 {
52 // Get the content of the @var annotation
53 $docComment = $property->getDocComment();
54 if (!$docComment) {
55 return null;
56 }
57 if (\preg_match('/@var\\s+([^\\s]+)/', $docComment, $matches)) {
58 [, $type] = $matches;
59 } else {
60 return null;
61 }
62 // Ignore primitive types
63 if (isset(self::PRIMITIVE_TYPES[$type])) {
64 if ($allowPrimitiveTypes) {
65 return self::PRIMITIVE_TYPES[$type];
66 }
67 return null;
68 }
69 // Ignore types containing special characters ([], <> ...)
70 if (!\preg_match('/^[a-zA-Z0-9\\\\_]+$/', $type)) {
71 return null;
72 }
73 $class = $property->getDeclaringClass();
74 // If the class name is not fully qualified (i.e. doesn't start with a \)
75 if ($type[0] !== '\\') {
76 // Try to resolve the FQN using the class context
77 $resolvedType = $this->tryResolveFqn($type, $class, $property);
78 if (!$resolvedType && !$this->ignorePhpDocErrors) {
79 throw new AnnotationException(\sprintf('The @var annotation on %s::%s contains a non existent class "%s". ' . 'Did you maybe forget to add a "use" statement for this annotation?', $class->name, $property->getName(), $type));
80 }
81 $type = $resolvedType;
82 }
83 if (!$this->ignorePhpDocErrors && !$this->classExists($type)) {
84 throw new AnnotationException(\sprintf('The @var annotation on %s::%s contains a non existent class "%s"', $class->name, $property->getName(), $type));
85 }
86 // Remove the leading \ (FQN shouldn't contain it)
87 $type = \is_string($type) ? \ltrim($type, '\\') : null;
88 return $type;
89 }
90 /**
91 * Parse the docblock of the property to get the type (class or primitive type) of the param annotation.
92 *
93 * @return string|null Type of the property (content of var annotation)
94 * @throws AnnotationException
95 */
96 public function getParameterType(ReflectionParameter $parameter) : ?string
97 {
98 return $this->readParameterClass($parameter, \true);
99 }
100 /**
101 * Parse the docblock of the property to get the class of the param annotation.
102 *
103 * @return string|null Type of the property (content of var annotation)
104 * @throws AnnotationException
105 */
106 public function getParameterClass(ReflectionParameter $parameter) : ?string
107 {
108 return $this->readParameterClass($parameter, \false);
109 }
110 private function readParameterClass(ReflectionParameter $parameter, bool $allowPrimitiveTypes) : ?string
111 {
112 // Use reflection
113 $parameterType = $parameter->getType();
114 if ($parameterType && $parameterType instanceof \ReflectionNamedType && !$parameterType->isBuiltin()) {
115 return $parameterType->getName();
116 }
117 $parameterName = $parameter->name;
118 // Get the content of the @param annotation
119 $method = $parameter->getDeclaringFunction();
120 $docComment = $method->getDocComment();
121 if (!$docComment) {
122 return null;
123 }
124 if (\preg_match('/@param\\s+([^\\s]+)\\s+\\$' . $parameterName . '/', $docComment, $matches)) {
125 [, $type] = $matches;
126 } else {
127 return null;
128 }
129 // Ignore primitive types
130 if (isset(self::PRIMITIVE_TYPES[$type])) {
131 if ($allowPrimitiveTypes) {
132 return self::PRIMITIVE_TYPES[$type];
133 }
134 return null;
135 }
136 // Ignore types containing special characters ([], <> ...)
137 if (!\preg_match('/^[a-zA-Z0-9\\\\_]+$/', $type)) {
138 return null;
139 }
140 $class = $parameter->getDeclaringClass();
141 // If the class name is not fully qualified (i.e. doesn't start with a \)
142 if ($type[0] !== '\\') {
143 // Try to resolve the FQN using the class context
144 $resolvedType = $this->tryResolveFqn($type, $class, $parameter);
145 if (!$resolvedType && !$this->ignorePhpDocErrors) {
146 throw new AnnotationException(\sprintf('The @param annotation for parameter "%s" of %s::%s contains a non existent class "%s". ' . 'Did you maybe forget to add a "use" statement for this annotation?', $parameterName, $class->name, $method->name, $type));
147 }
148 $type = $resolvedType;
149 }
150 if (!$this->ignorePhpDocErrors && !$this->classExists($type)) {
151 throw new AnnotationException(\sprintf('The @param annotation for parameter "%s" of %s::%s contains a non existent class "%s"', $parameterName, $class->name, $method->name, $type));
152 }
153 // Remove the leading \ (FQN shouldn't contain it)
154 $type = \is_string($type) ? \ltrim($type, '\\') : null;
155 return $type;
156 }
157 /**
158 * Attempts to resolve the FQN of the provided $type based on the $class and $member context.
159 *
160 * @return string|null Fully qualified name of the type, or null if it could not be resolved
161 */
162 private function tryResolveFqn(string $type, ReflectionClass $class, Reflector $member) : ?string
163 {
164 $alias = ($pos = \strpos($type, '\\')) === \false ? $type : \substr($type, 0, $pos);
165 $loweredAlias = \strtolower($alias);
166 // Retrieve "use" statements
167 $uses = $this->parser->parseUseStatements($class);
168 if (isset($uses[$loweredAlias])) {
169 // Imported classes
170 if ($pos !== \false) {
171 return $uses[$loweredAlias] . \substr($type, $pos);
172 }
173 return $uses[$loweredAlias];
174 }
175 if ($this->classExists($class->getNamespaceName() . '\\' . $type)) {
176 return $class->getNamespaceName() . '\\' . $type;
177 }
178 if (isset($uses['__NAMESPACE__']) && $this->classExists($uses['__NAMESPACE__'] . '\\' . $type)) {
179 // Class namespace
180 return $uses['__NAMESPACE__'] . '\\' . $type;
181 }
182 if ($this->classExists($type)) {
183 // No namespace
184 return $type;
185 }
186 // If all fail, try resolving through related traits
187 return $this->tryResolveFqnInTraits($type, $class, $member);
188 }
189 /**
190 * Attempts to resolve the FQN of the provided $type based on the $class and $member context, specifically searching
191 * through the traits that are used by the provided $class.
192 *
193 * @return string|null Fully qualified name of the type, or null if it could not be resolved
194 */
195 private function tryResolveFqnInTraits(string $type, ReflectionClass $class, Reflector $member) : ?string
196 {
197 /** @var ReflectionClass[] $traits */
198 $traits = [];
199 // Get traits for the class and its parents
200 while ($class) {
201 $traits = \array_merge($traits, $class->getTraits());
202 $class = $class->getParentClass();
203 }
204 foreach ($traits as $trait) {
205 // Eliminate traits that don't have the property/method/parameter
206 if ($member instanceof ReflectionProperty && !$trait->hasProperty($member->name)) {
207 continue;
208 }
209 if ($member instanceof ReflectionMethod && !$trait->hasMethod($member->name)) {
210 continue;
211 }
212 if ($member instanceof ReflectionParameter && !$trait->hasMethod($member->getDeclaringFunction()->name)) {
213 continue;
214 }
215 // Run the resolver again with the ReflectionClass instance for the trait
216 $resolvedType = $this->tryResolveFqn($type, $trait, $member);
217 if ($resolvedType) {
218 return $resolvedType;
219 }
220 }
221 return null;
222 }
223 private function classExists(string $class) : bool
224 {
225 return \class_exists($class) || \interface_exists($class);
226 }
227 }
228