PluginProbe
WindPress – Tailwind CSS integration for WordPress / 3.2.90
WindPress – Tailwind CSS integration for WordPress v3.2.90
3.2.90 3.2.89 3.2.88 3.2.87 3.2.86 3.2.85 3.2.84 3.2.83 3.2.82 3.2.81 trunk 3.0.0 3.0.1 3.0.10 3.0.11 3.0.12 3.0.13 3.0.14 3.0.15 3.0.16 3.0.17 3.0.2 3.0.3 3.0.4 3.0.5 All 144 releases
windpress / vendor / symfony / property-info / Extractor / ReflectionExtractor.php

ReflectionExtractor.php in WindPress – Tailwind CSS integration for WordPress 3.2.90, at vendor/symfony/property-info/Extractor/ReflectionExtractor.php

727 lines 33.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11 namespace WindPressDeps\Symfony\Component\PropertyInfo\Extractor;
12
13 use WindPressDeps\Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
14 use WindPressDeps\Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface;
15 use WindPressDeps\Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
16 use WindPressDeps\Symfony\Component\PropertyInfo\PropertyReadInfo;
17 use WindPressDeps\Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface;
18 use WindPressDeps\Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
19 use WindPressDeps\Symfony\Component\PropertyInfo\PropertyWriteInfo;
20 use WindPressDeps\Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface;
21 use WindPressDeps\Symfony\Component\PropertyInfo\Type;
22 use WindPressDeps\Symfony\Component\String\Inflector\EnglishInflector;
23 use WindPressDeps\Symfony\Component\String\Inflector\InflectorInterface;
24 /**
25 * Extracts data using the reflection API.
26 *
27 * @author Kévin Dunglas <dunglas@gmail.com>
28 *
29 * @final
30 */
31 class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface, PropertyInitializableExtractorInterface, PropertyReadInfoExtractorInterface, PropertyWriteInfoExtractorInterface, ConstructorArgumentTypeExtractorInterface
32 {
33 /**
34 * @internal
35 */
36 public static $defaultMutatorPrefixes = ['add', 'remove', 'set'];
37 /**
38 * @internal
39 */
40 public static $defaultAccessorPrefixes = ['get', 'is', 'has', 'can'];
41 /**
42 * @internal
43 */
44 public static $defaultArrayMutatorPrefixes = ['add', 'remove'];
45 public const ALLOW_PRIVATE = 1;
46 public const ALLOW_PROTECTED = 2;
47 public const ALLOW_PUBLIC = 4;
48 /** @var int Allow none of the magic methods */
49 public const DISALLOW_MAGIC_METHODS = 0;
50 /** @var int Allow magic __get methods */
51 public const ALLOW_MAGIC_GET = 1 << 0;
52 /** @var int Allow magic __set methods */
53 public const ALLOW_MAGIC_SET = 1 << 1;
54 /** @var int Allow magic __call methods */
55 public const ALLOW_MAGIC_CALL = 1 << 2;
56 private const MAP_TYPES = ['integer' => Type::BUILTIN_TYPE_INT, 'boolean' => Type::BUILTIN_TYPE_BOOL, 'double' => Type::BUILTIN_TYPE_FLOAT];
57 private $mutatorPrefixes;
58 private $accessorPrefixes;
59 private $arrayMutatorPrefixes;
60 private $enableConstructorExtraction;
61 private $methodReflectionFlags;
62 private $magicMethodsFlags;
63 private $propertyReflectionFlags;
64 private $inflector;
65 private $arrayMutatorPrefixesFirst;
66 private $arrayMutatorPrefixesLast;
67 /**
68 * @param string[]|null $mutatorPrefixes
69 * @param string[]|null $accessorPrefixes
70 * @param string[]|null $arrayMutatorPrefixes
71 */
72 public function __construct(?array $mutatorPrefixes = null, ?array $accessorPrefixes = null, ?array $arrayMutatorPrefixes = null, bool $enableConstructorExtraction = \true, int $accessFlags = self::ALLOW_PUBLIC, ?InflectorInterface $inflector = null, int $magicMethodsFlags = self::ALLOW_MAGIC_GET | self::ALLOW_MAGIC_SET)
73 {
74 $this->mutatorPrefixes = $mutatorPrefixes ?? self::$defaultMutatorPrefixes;
75 $this->accessorPrefixes = $accessorPrefixes ?? self::$defaultAccessorPrefixes;
76 $this->arrayMutatorPrefixes = $arrayMutatorPrefixes ?? self::$defaultArrayMutatorPrefixes;
77 $this->enableConstructorExtraction = $enableConstructorExtraction;
78 $this->methodReflectionFlags = $this->getMethodsFlags($accessFlags);
79 $this->propertyReflectionFlags = $this->getPropertyFlags($accessFlags);
80 $this->magicMethodsFlags = $magicMethodsFlags;
81 $this->inflector = $inflector ?? new EnglishInflector();
82 $this->arrayMutatorPrefixesFirst = array_merge($this->arrayMutatorPrefixes, array_diff($this->mutatorPrefixes, $this->arrayMutatorPrefixes));
83 $this->arrayMutatorPrefixesLast = array_reverse($this->arrayMutatorPrefixesFirst);
84 }
85 /**
86 * {@inheritdoc}
87 */
88 public function getProperties(string $class, array $context = []): ?array
89 {
90 try {
91 $reflectionClass = new \ReflectionClass($class);
92 } catch (\ReflectionException $e) {
93 return null;
94 }
95 $reflectionProperties = $reflectionClass->getProperties();
96 $properties = [];
97 foreach ($reflectionProperties as $reflectionProperty) {
98 if ($reflectionProperty->getModifiers() & $this->propertyReflectionFlags) {
99 $properties[$reflectionProperty->name] = $reflectionProperty->name;
100 }
101 }
102 foreach ($reflectionClass->getMethods($this->methodReflectionFlags) as $reflectionMethod) {
103 if ($reflectionMethod->isStatic()) {
104 continue;
105 }
106 $propertyName = $this->getPropertyName($reflectionMethod->name, $reflectionProperties);
107 if (!$propertyName || isset($properties[$propertyName])) {
108 continue;
109 }
110 if ($reflectionClass->hasProperty($lowerCasedPropertyName = lcfirst($propertyName)) || !$reflectionClass->hasProperty($propertyName) && !preg_match('/^[A-Z]{2,}/', $propertyName)) {
111 $propertyName = $lowerCasedPropertyName;
112 }
113 $properties[$propertyName] = $propertyName;
114 }
115 return $properties ? array_values($properties) : null;
116 }
117 /**
118 * {@inheritdoc}
119 */
120 public function getTypes(string $class, string $property, array $context = []): ?array
121 {
122 if ($fromMutator = $this->extractFromMutator($class, $property)) {
123 return $fromMutator;
124 }
125 if ($fromAccessor = $this->extractFromAccessor($class, $property)) {
126 return $fromAccessor;
127 }
128 if (($context['enable_constructor_extraction'] ?? $this->enableConstructorExtraction) && $fromConstructor = $this->extractFromConstructor($class, $property)) {
129 return $fromConstructor;
130 }
131 if ($fromPropertyDeclaration = $this->extractFromPropertyDeclaration($class, $property)) {
132 return $fromPropertyDeclaration;
133 }
134 return null;
135 }
136 /**
137 * {@inheritdoc}
138 */
139 public function getTypesFromConstructor(string $class, string $property): ?array
140 {
141 try {
142 $reflection = new \ReflectionClass($class);
143 } catch (\ReflectionException $e) {
144 return null;
145 }
146 if (!$reflectionConstructor = $reflection->getConstructor()) {
147 return null;
148 }
149 if (!$reflectionParameter = $this->getReflectionParameterFromConstructor($property, $reflectionConstructor)) {
150 return null;
151 }
152 if (!$reflectionType = $reflectionParameter->getType()) {
153 return null;
154 }
155 if (!$types = $this->extractFromReflectionType($reflectionType, $reflectionConstructor->getDeclaringClass())) {
156 return null;
157 }
158 return $types;
159 }
160 private function getReflectionParameterFromConstructor(string $property, \ReflectionMethod $reflectionConstructor): ?\ReflectionParameter
161 {
162 $reflectionParameter = null;
163 foreach ($reflectionConstructor->getParameters() as $reflectionParameter) {
164 if ($reflectionParameter->getName() === $property) {
165 return $reflectionParameter;
166 }
167 }
168 return null;
169 }
170 /**
171 * {@inheritdoc}
172 */
173 public function isReadable(string $class, string $property, array $context = []): ?bool
174 {
175 if ($this->isAllowedProperty($class, $property)) {
176 return \true;
177 }
178 return null !== $this->getReadInfo($class, $property, $context);
179 }
180 /**
181 * {@inheritdoc}
182 */
183 public function isWritable(string $class, string $property, array $context = []): ?bool
184 {
185 if ($this->isAllowedProperty($class, $property, \true)) {
186 return \true;
187 }
188 [$reflectionMethod] = $this->getMutatorMethod($class, $property);
189 return null !== $reflectionMethod;
190 }
191 /**
192 * {@inheritdoc}
193 */
194 public function isInitializable(string $class, string $property, array $context = []): ?bool
195 {
196 try {
197 $reflectionClass = new \ReflectionClass($class);
198 } catch (\ReflectionException $e) {
199 return null;
200 }
201 if (!$reflectionClass->isInstantiable()) {
202 return \false;
203 }
204 if ($constructor = $reflectionClass->getConstructor()) {
205 foreach ($constructor->getParameters() as $parameter) {
206 if ($property === $parameter->name) {
207 return \true;
208 }
209 }
210 } elseif ($parentClass = $reflectionClass->getParentClass()) {
211 return $this->isInitializable($parentClass->getName(), $property);
212 }
213 return \false;
214 }
215 /**
216 * {@inheritdoc}
217 */
218 public function getReadInfo(string $class, string $property, array $context = []): ?PropertyReadInfo
219 {
220 try {
221 $reflClass = new \ReflectionClass($class);
222 } catch (\ReflectionException $e) {
223 return null;
224 }
225 $allowGetterSetter = $context['enable_getter_setter_extraction'] ?? \false;
226 $magicMethods = $context['enable_magic_methods_extraction'] ?? $this->magicMethodsFlags;
227 $allowMagicCall = (bool) ($magicMethods & self::ALLOW_MAGIC_CALL);
228 $allowMagicGet = (bool) ($magicMethods & self::ALLOW_MAGIC_GET);
229 if (isset($context['enable_magic_call_extraction'])) {
230 trigger_deprecation('symfony/property-info', '5.2', 'Using the "enable_magic_call_extraction" context option in "%s()" is deprecated. Use "enable_magic_methods_extraction" instead.', __METHOD__);
231 $allowMagicCall = $context['enable_magic_call_extraction'] ?? \false;
232 }
233 $hasProperty = $reflClass->hasProperty($property);
234 $camelProp = $this->camelize($property);
235 $getsetter = lcfirst($camelProp);
236 // jQuery style, e.g. read: last(), write: last($item)
237 foreach ($this->accessorPrefixes as $prefix) {
238 $methodName = $prefix . $camelProp;
239 if ($reflClass->hasMethod($methodName) && $reflClass->getMethod($methodName)->getModifiers() & $this->methodReflectionFlags && !$reflClass->getMethod($methodName)->getNumberOfRequiredParameters()) {
240 $method = $reflClass->getMethod($methodName);
241 return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, $methodName, $this->getReadVisiblityForMethod($method), $method->isStatic(), \false);
242 }
243 }
244 if ($allowGetterSetter && $reflClass->hasMethod($getsetter) && $reflClass->getMethod($getsetter)->getModifiers() & $this->methodReflectionFlags) {
245 $method = $reflClass->getMethod($getsetter);
246 return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, $getsetter, $this->getReadVisiblityForMethod($method), $method->isStatic(), \false);
247 }
248 if ($allowMagicGet && $reflClass->hasMethod('__get') && $reflClass->getMethod('__get')->getModifiers() & $this->methodReflectionFlags) {
249 return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY, $property, PropertyReadInfo::VISIBILITY_PUBLIC, \false, \false);
250 }
251 if ($hasProperty && $reflClass->getProperty($property)->getModifiers() & $this->propertyReflectionFlags) {
252 $reflProperty = $reflClass->getProperty($property);
253 return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY, $property, $this->getReadVisiblityForProperty($reflProperty), $reflProperty->isStatic(), \true);
254 }
255 if ($allowMagicCall && $reflClass->hasMethod('__call') && $reflClass->getMethod('__call')->getModifiers() & $this->methodReflectionFlags) {
256 return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, 'get' . $camelProp, PropertyReadInfo::VISIBILITY_PUBLIC, \false, \false);
257 }
258 return null;
259 }
260 /**
261 * {@inheritdoc}
262 */
263 public function getWriteInfo(string $class, string $property, array $context = []): ?PropertyWriteInfo
264 {
265 try {
266 $reflClass = new \ReflectionClass($class);
267 } catch (\ReflectionException $e) {
268 return null;
269 }
270 $allowGetterSetter = $context['enable_getter_setter_extraction'] ?? \false;
271 $magicMethods = $context['enable_magic_methods_extraction'] ?? $this->magicMethodsFlags;
272 $allowMagicCall = (bool) ($magicMethods & self::ALLOW_MAGIC_CALL);
273 $allowMagicSet = (bool) ($magicMethods & self::ALLOW_MAGIC_SET);
274 if (isset($context['enable_magic_call_extraction'])) {
275 trigger_deprecation('symfony/property-info', '5.2', 'Using the "enable_magic_call_extraction" context option in "%s()" is deprecated. Use "enable_magic_methods_extraction" instead.', __METHOD__);
276 $allowMagicCall = $context['enable_magic_call_extraction'] ?? \false;
277 }
278 $allowConstruct = $context['enable_constructor_extraction'] ?? $this->enableConstructorExtraction;
279 $allowAdderRemover = $context['enable_adder_remover_extraction'] ?? \true;
280 $camelized = $this->camelize($property);
281 $constructor = $reflClass->getConstructor();
282 $singulars = $this->inflector->singularize($camelized);
283 $errors = [];
284 if (null !== $constructor && $allowConstruct) {
285 foreach ($constructor->getParameters() as $parameter) {
286 if ($parameter->getName() === $property) {
287 return new PropertyWriteInfo(PropertyWriteInfo::TYPE_CONSTRUCTOR, $property);
288 }
289 }
290 }
291 [$adderAccessName, $removerAccessName, $adderAndRemoverErrors] = $this->findAdderAndRemover($reflClass, $singulars);
292 if ($allowAdderRemover && null !== $adderAccessName && null !== $removerAccessName) {
293 $adderMethod = $reflClass->getMethod($adderAccessName);
294 $removerMethod = $reflClass->getMethod($removerAccessName);
295 $mutator = new PropertyWriteInfo(PropertyWriteInfo::TYPE_ADDER_AND_REMOVER);
296 $mutator->setAdderInfo(new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $adderAccessName, $this->getWriteVisiblityForMethod($adderMethod), $adderMethod->isStatic()));
297 $mutator->setRemoverInfo(new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $removerAccessName, $this->getWriteVisiblityForMethod($removerMethod), $removerMethod->isStatic()));
298 return $mutator;
299 }
300 $errors[] = $adderAndRemoverErrors;
301 foreach ($this->mutatorPrefixes as $mutatorPrefix) {
302 $methodName = $mutatorPrefix . $camelized;
303 [$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, $methodName, 1);
304 if (!$accessible) {
305 $errors[] = $methodAccessibleErrors;
306 continue;
307 }
308 $method = $reflClass->getMethod($methodName);
309 if (!\in_array($mutatorPrefix, $this->arrayMutatorPrefixes, \true)) {
310 return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $methodName, $this->getWriteVisiblityForMethod($method), $method->isStatic());
311 }
312 }
313 $getsetter = lcfirst($camelized);
314 if ($allowGetterSetter) {
315 [$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, $getsetter, 1);
316 if ($accessible) {
317 $method = $reflClass->getMethod($getsetter);
318 return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $getsetter, $this->getWriteVisiblityForMethod($method), $method->isStatic());
319 }
320 $errors[] = $methodAccessibleErrors;
321 }
322 if ($reflClass->hasProperty($property) && $reflClass->getProperty($property)->getModifiers() & $this->propertyReflectionFlags) {
323 $reflProperty = $reflClass->getProperty($property);
324 if (\PHP_VERSION_ID < 80100 || !$reflProperty->isReadOnly()) {
325 return new PropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY, $property, $this->getWriteVisiblityForProperty($reflProperty), $reflProperty->isStatic());
326 }
327 $errors[] = [sprintf('The property "%s" in class "%s" is a promoted readonly property.', $property, $reflClass->getName())];
328 $allowMagicSet = $allowMagicCall = \false;
329 }
330 if ($allowMagicSet) {
331 [$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, '__set', 2);
332 if ($accessible) {
333 return new PropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY, $property, PropertyWriteInfo::VISIBILITY_PUBLIC, \false);
334 }
335 $errors[] = $methodAccessibleErrors;
336 }
337 if ($allowMagicCall) {
338 [$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, '__call', 2);
339 if ($accessible) {
340 return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, 'set' . $camelized, PropertyWriteInfo::VISIBILITY_PUBLIC, \false);
341 }
342 $errors[] = $methodAccessibleErrors;
343 }
344 if (!$allowAdderRemover && null !== $adderAccessName && null !== $removerAccessName) {
345 $errors[] = [sprintf('The property "%s" in class "%s" can be defined with the methods "%s()" but ' . 'the new value must be an array or an instance of \Traversable', $property, $reflClass->getName(), implode('()", "', [$adderAccessName, $removerAccessName]))];
346 }
347 $noneProperty = new PropertyWriteInfo();
348 $noneProperty->setErrors(array_merge([], ...$errors));
349 return $noneProperty;
350 }
351 /**
352 * @return Type[]|null
353 */
354 private function extractFromMutator(string $class, string $property): ?array
355 {
356 [$reflectionMethod, $prefix] = $this->getMutatorMethod($class, $property);
357 if (null === $reflectionMethod) {
358 return null;
359 }
360 $reflectionParameters = $reflectionMethod->getParameters();
361 $reflectionParameter = $reflectionParameters[0];
362 if (!$reflectionType = $reflectionParameter->getType()) {
363 return null;
364 }
365 $type = $this->extractFromReflectionType($reflectionType, $reflectionMethod->getDeclaringClass());
366 if (1 === \count($type) && \in_array($prefix, $this->arrayMutatorPrefixes)) {
367 $type = [new Type(Type::BUILTIN_TYPE_ARRAY, $this->isNullableProperty($class, $property), null, \true, new Type(Type::BUILTIN_TYPE_INT), $type[0])];
368 }
369 return $type;
370 }
371 /**
372 * Tries to extract type information from accessors.
373 *
374 * @return Type[]|null
375 */
376 private function extractFromAccessor(string $class, string $property): ?array
377 {
378 [$reflectionMethod, $prefix] = $this->getAccessorMethod($class, $property);
379 if (null === $reflectionMethod) {
380 return null;
381 }
382 if ($reflectionType = $reflectionMethod->getReturnType()) {
383 return $this->extractFromReflectionType($reflectionType, $reflectionMethod->getDeclaringClass());
384 }
385 if (\in_array($prefix, ['is', 'can', 'has'])) {
386 return [new Type(Type::BUILTIN_TYPE_BOOL)];
387 }
388 return null;
389 }
390 /**
391 * Tries to extract type information from constructor.
392 *
393 * @return Type[]|null
394 */
395 private function extractFromConstructor(string $class, string $property): ?array
396 {
397 try {
398 $reflectionClass = new \ReflectionClass($class);
399 } catch (\ReflectionException $e) {
400 return null;
401 }
402 $constructor = $reflectionClass->getConstructor();
403 if (!$constructor) {
404 return null;
405 }
406 foreach ($constructor->getParameters() as $parameter) {
407 if ($property !== $parameter->name) {
408 continue;
409 }
410 $reflectionType = $parameter->getType();
411 return $reflectionType ? $this->extractFromReflectionType($reflectionType, $constructor->getDeclaringClass()) : null;
412 }
413 if ($parentClass = $reflectionClass->getParentClass()) {
414 return $this->extractFromConstructor($parentClass->getName(), $property);
415 }
416 return null;
417 }
418 private function extractFromPropertyDeclaration(string $class, string $property): ?array
419 {
420 try {
421 $reflectionClass = new \ReflectionClass($class);
422 if (\PHP_VERSION_ID >= 70400) {
423 $reflectionProperty = $reflectionClass->getProperty($property);
424 $reflectionPropertyType = $reflectionProperty->getType();
425 if (null !== $reflectionPropertyType && $types = $this->extractFromReflectionType($reflectionPropertyType, $reflectionProperty->getDeclaringClass())) {
426 return $types;
427 }
428 }
429 } catch (\ReflectionException $e) {
430 return null;
431 }
432 $defaultValue = $reflectionClass->getDefaultProperties()[$property] ?? null;
433 if (null === $defaultValue) {
434 return null;
435 }
436 $type = \gettype($defaultValue);
437 $type = static::MAP_TYPES[$type] ?? $type;
438 return [new Type($type, $this->isNullableProperty($class, $property), null, Type::BUILTIN_TYPE_ARRAY === $type)];
439 }
440 private function extractFromReflectionType(\ReflectionType $reflectionType, \ReflectionClass $declaringClass): array
441 {
442 $types = [];
443 $nullable = $reflectionType->allowsNull();
444 foreach ($reflectionType instanceof \ReflectionUnionType || $reflectionType instanceof \ReflectionIntersectionType ? $reflectionType->getTypes() : [$reflectionType] as $type) {
445 if (!$type instanceof \ReflectionNamedType) {
446 // Nested composite types are not supported yet.
447 return [];
448 }
449 $phpTypeOrClass = $type->getName();
450 if ('null' === $phpTypeOrClass || 'mixed' === $phpTypeOrClass || 'never' === $phpTypeOrClass) {
451 continue;
452 }
453 if (Type::BUILTIN_TYPE_ARRAY === $phpTypeOrClass) {
454 $types[] = new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, \true);
455 } elseif ('void' === $phpTypeOrClass) {
456 $types[] = new Type(Type::BUILTIN_TYPE_NULL, $nullable);
457 } elseif ($type->isBuiltin()) {
458 $types[] = new Type($phpTypeOrClass, $nullable);
459 } else {
460 $types[] = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $this->resolveTypeName($phpTypeOrClass, $declaringClass));
461 }
462 }
463 return $types;
464 }
465 private function resolveTypeName(string $name, \ReflectionClass $declaringClass): string
466 {
467 if ('self' === $lcName = strtolower($name)) {
468 return $declaringClass->name;
469 }
470 if ('parent' === $lcName && $parent = $declaringClass->getParentClass()) {
471 return $parent->name;
472 }
473 return $name;
474 }
475 private function isNullableProperty(string $class, string $property): bool
476 {
477 try {
478 $reflectionProperty = new \ReflectionProperty($class, $property);
479 if (\PHP_VERSION_ID >= 70400) {
480 $reflectionPropertyType = $reflectionProperty->getType();
481 return null !== $reflectionPropertyType && $reflectionPropertyType->allowsNull();
482 }
483 return \false;
484 } catch (\ReflectionException $e) {
485 // Return false if the property doesn't exist
486 }
487 return \false;
488 }
489 private function isAllowedProperty(string $class, string $property, bool $writeAccessRequired = \false): bool
490 {
491 try {
492 $reflectionProperty = new \ReflectionProperty($class, $property);
493 if ($writeAccessRequired) {
494 if (\PHP_VERSION_ID >= 80100 && $reflectionProperty->isReadOnly()) {
495 return \false;
496 }
497 if (\PHP_VERSION_ID >= 80400 && ($reflectionProperty->isProtectedSet() || $reflectionProperty->isPrivateSet())) {
498 return \false;
499 }
500 if (\PHP_VERSION_ID >= 80400 && $reflectionProperty->isVirtual() && !$reflectionProperty->hasHook(\PropertyHookType::Set)) {
501 return \false;
502 }
503 }
504 return (bool) ($reflectionProperty->getModifiers() & $this->propertyReflectionFlags);
505 } catch (\ReflectionException $e) {
506 // Return false if the property doesn't exist
507 }
508 return \false;
509 }
510 /**
511 * Gets the accessor method.
512 *
513 * Returns an array with a the instance of \ReflectionMethod as first key
514 * and the prefix of the method as second or null if not found.
515 */
516 private function getAccessorMethod(string $class, string $property): ?array
517 {
518 $ucProperty = ucfirst($property);
519 foreach ($this->accessorPrefixes as $prefix) {
520 try {
521 $reflectionMethod = new \ReflectionMethod($class, $prefix . $ucProperty);
522 if ($reflectionMethod->isStatic()) {
523 continue;
524 }
525 if (0 === $reflectionMethod->getNumberOfRequiredParameters()) {
526 return [$reflectionMethod, $prefix];
527 }
528 } catch (\ReflectionException $e) {
529 // Return null if the property doesn't exist
530 }
531 }
532 return null;
533 }
534 /**
535 * Returns an array with a the instance of \ReflectionMethod as first key
536 * and the prefix of the method as second or null if not found.
537 */
538 private function getMutatorMethod(string $class, string $property): ?array
539 {
540 $ucProperty = ucfirst($property);
541 $ucSingulars = $this->inflector->singularize($ucProperty);
542 $mutatorPrefixes = \in_array($ucProperty, $ucSingulars, \true) ? $this->arrayMutatorPrefixesLast : $this->arrayMutatorPrefixesFirst;
543 foreach ($mutatorPrefixes as $prefix) {
544 $names = [$ucProperty];
545 if (\in_array($prefix, $this->arrayMutatorPrefixes)) {
546 $names = array_merge($names, $ucSingulars);
547 }
548 foreach ($names as $name) {
549 try {
550 $reflectionMethod = new \ReflectionMethod($class, $prefix . $name);
551 if ($reflectionMethod->isStatic()) {
552 continue;
553 }
554 // Parameter can be optional to allow things like: method(?array $foo = null)
555 if ($reflectionMethod->getNumberOfParameters() >= 1) {
556 return [$reflectionMethod, $prefix];
557 }
558 } catch (\ReflectionException $e) {
559 // Try the next prefix if the method doesn't exist
560 }
561 }
562 }
563 return null;
564 }
565 private function getPropertyName(string $methodName, array $reflectionProperties): ?string
566 {
567 $pattern = implode('|', array_merge($this->accessorPrefixes, $this->mutatorPrefixes));
568 if ('' !== $pattern && preg_match('/^(' . $pattern . ')(.+)$/i', $methodName, $matches)) {
569 if (!\in_array($matches[1], $this->arrayMutatorPrefixes)) {
570 return $matches[2];
571 }
572 foreach ($reflectionProperties as $reflectionProperty) {
573 foreach ($this->inflector->singularize($reflectionProperty->name) as $name) {
574 if (strtolower($name) === strtolower($matches[2])) {
575 return $reflectionProperty->name;
576 }
577 }
578 }
579 return $matches[2];
580 }
581 return null;
582 }
583 /**
584 * Searches for add and remove methods.
585 *
586 * @param \ReflectionClass $reflClass The reflection class for the given object
587 * @param array $singulars The singular form of the property name or null
588 *
589 * @return array An array containing the adder and remover when found and errors
590 */
591 private function findAdderAndRemover(\ReflectionClass $reflClass, array $singulars): array
592 {
593 if (!\is_array($this->arrayMutatorPrefixes) && 2 !== \count($this->arrayMutatorPrefixes)) {
594 return [null, null, []];
595 }
596 [$addPrefix, $removePrefix] = $this->arrayMutatorPrefixes;
597 $errors = [];
598 foreach ($singulars as $singular) {
599 $addMethod = $addPrefix . $singular;
600 $removeMethod = $removePrefix . $singular;
601 [$addMethodFound, $addMethodAccessibleErrors] = $this->isMethodAccessible($reflClass, $addMethod, 1);
602 [$removeMethodFound, $removeMethodAccessibleErrors] = $this->isMethodAccessible($reflClass, $removeMethod, 1);
603 $errors[] = $addMethodAccessibleErrors;
604 $errors[] = $removeMethodAccessibleErrors;
605 if ($addMethodFound && $removeMethodFound) {
606 return [$addMethod, $removeMethod, []];
607 }
608 if ($addMethodFound && !$removeMethodFound) {
609 $errors[] = [sprintf('The add method "%s" in class "%s" was found, but the corresponding remove method "%s" was not found', $addMethod, $reflClass->getName(), $removeMethod)];
610 } elseif (!$addMethodFound && $removeMethodFound) {
611 $errors[] = [sprintf('The remove method "%s" in class "%s" was found, but the corresponding add method "%s" was not found', $removeMethod, $reflClass->getName(), $addMethod)];
612 }
613 }
614 return [null, null, array_merge([], ...$errors)];
615 }
616 /**
617 * Returns whether a method is public and has the number of required parameters and errors.
618 */
619 private function isMethodAccessible(\ReflectionClass $class, string $methodName, int $parameters): array
620 {
621 $errors = [];
622 if ($class->hasMethod($methodName)) {
623 $method = $class->getMethod($methodName);
624 if (\ReflectionMethod::IS_PUBLIC === $this->methodReflectionFlags && !$method->isPublic()) {
625 $errors[] = sprintf('The method "%s" in class "%s" was found but does not have public access.', $methodName, $class->getName());
626 } elseif ($method->getNumberOfRequiredParameters() > $parameters || $method->getNumberOfParameters() < $parameters) {
627 $errors[] = sprintf('The method "%s" in class "%s" requires %d arguments, but should accept only %d.', $methodName, $class->getName(), $method->getNumberOfRequiredParameters(), $parameters);
628 } else {
629 return [\true, $errors];
630 }
631 }
632 return [\false, $errors];
633 }
634 /**
635 * Camelizes a given string.
636 */
637 private function camelize(string $string): string
638 {
639 return str_replace(' ', '', ucwords(str_replace('_', ' ', $string)));
640 }
641 /**
642 * Return allowed reflection method flags.
643 */
644 private function getMethodsFlags(int $accessFlags): int
645 {
646 $methodFlags = 0;
647 if ($accessFlags & self::ALLOW_PUBLIC) {
648 $methodFlags |= \ReflectionMethod::IS_PUBLIC;
649 }
650 if ($accessFlags & self::ALLOW_PRIVATE) {
651 $methodFlags |= \ReflectionMethod::IS_PRIVATE;
652 }
653 if ($accessFlags & self::ALLOW_PROTECTED) {
654 $methodFlags |= \ReflectionMethod::IS_PROTECTED;
655 }
656 return $methodFlags;
657 }
658 /**
659 * Return allowed reflection property flags.
660 */
661 private function getPropertyFlags(int $accessFlags): int
662 {
663 $propertyFlags = 0;
664 if ($accessFlags & self::ALLOW_PUBLIC) {
665 $propertyFlags |= \ReflectionProperty::IS_PUBLIC;
666 }
667 if ($accessFlags & self::ALLOW_PRIVATE) {
668 $propertyFlags |= \ReflectionProperty::IS_PRIVATE;
669 }
670 if ($accessFlags & self::ALLOW_PROTECTED) {
671 $propertyFlags |= \ReflectionProperty::IS_PROTECTED;
672 }
673 return $propertyFlags;
674 }
675 private function getReadVisiblityForProperty(\ReflectionProperty $reflectionProperty): string
676 {
677 if ($reflectionProperty->isPrivate()) {
678 return PropertyReadInfo::VISIBILITY_PRIVATE;
679 }
680 if ($reflectionProperty->isProtected()) {
681 return PropertyReadInfo::VISIBILITY_PROTECTED;
682 }
683 return PropertyReadInfo::VISIBILITY_PUBLIC;
684 }
685 private function getReadVisiblityForMethod(\ReflectionMethod $reflectionMethod): string
686 {
687 if ($reflectionMethod->isPrivate()) {
688 return PropertyReadInfo::VISIBILITY_PRIVATE;
689 }
690 if ($reflectionMethod->isProtected()) {
691 return PropertyReadInfo::VISIBILITY_PROTECTED;
692 }
693 return PropertyReadInfo::VISIBILITY_PUBLIC;
694 }
695 private function getWriteVisiblityForProperty(\ReflectionProperty $reflectionProperty): string
696 {
697 if (\PHP_VERSION_ID >= 80400) {
698 if ($reflectionProperty->isVirtual() && !$reflectionProperty->hasHook(\PropertyHookType::Set)) {
699 return PropertyWriteInfo::VISIBILITY_PRIVATE;
700 }
701 if ($reflectionProperty->isPrivateSet()) {
702 return PropertyWriteInfo::VISIBILITY_PRIVATE;
703 }
704 if ($reflectionProperty->isProtectedSet()) {
705 return PropertyWriteInfo::VISIBILITY_PROTECTED;
706 }
707 }
708 if ($reflectionProperty->isPrivate()) {
709 return PropertyWriteInfo::VISIBILITY_PRIVATE;
710 }
711 if ($reflectionProperty->isProtected()) {
712 return PropertyWriteInfo::VISIBILITY_PROTECTED;
713 }
714 return PropertyWriteInfo::VISIBILITY_PUBLIC;
715 }
716 private function getWriteVisiblityForMethod(\ReflectionMethod $reflectionMethod): string
717 {
718 if ($reflectionMethod->isPrivate()) {
719 return PropertyWriteInfo::VISIBILITY_PRIVATE;
720 }
721 if ($reflectionMethod->isProtected()) {
722 return PropertyWriteInfo::VISIBILITY_PROTECTED;
723 }
724 return PropertyWriteInfo::VISIBILITY_PUBLIC;
725 }
726 }
727