PluginProbe ʕ •ᴥ•ʔ
Kubio AI Page Builder / 2.8.3
Kubio AI Page Builder v2.8.3
2.8.4 2.8.3 2.8.2 2.8.1 trunk 1.0.0 1.0.1 1.1.0 1.2.0 1.2.1 1.2.2 1.2.3 1.3.0 1.3.1 1.3.2 1.4.0 1.4.1 1.4.2 1.4.3 1.5.0 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.7.0 1.7.1 1.7.2 1.7.3 1.8.0 1.8.1 1.8.2 1.9.0 2.0.0 2.1.1 2.1.2 2.1.3 2.2.0 2.2.3 2.2.4 2.2.5 2.3.0 2.3.1 2.3.3 2.3.4 2.4.0 2.4.1 2.4.2 2.4.3 2.4.5 2.5.0 2.5.1 2.5.2 2.5.3 2.6.0 2.6.1 2.6.2 2.6.3 2.6.5 2.6.6 2.6.7 2.7.0 2.7.1 2.7.2 2.7.3 2.8.0
kubio / vendor / symfony / property-info / Extractor / ReflectionExtractor.php
kubio / vendor / symfony / property-info / Extractor Last commit date
ConstructorArgumentTypeExtractorInterface.php 1 year ago ConstructorExtractor.php 1 year ago PhpDocExtractor.php 1 year ago PhpStanExtractor.php 1 year ago ReflectionExtractor.php 1 year ago SerializerExtractor.php 1 year ago
ReflectionExtractor.php
910 lines
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
12 namespace Symfony\Component\PropertyInfo\Extractor;
13
14 use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
15 use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface;
16 use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
17 use Symfony\Component\PropertyInfo\PropertyReadInfo;
18 use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface;
19 use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
20 use Symfony\Component\PropertyInfo\PropertyWriteInfo;
21 use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface;
22 use Symfony\Component\PropertyInfo\Type;
23 use Symfony\Component\String\Inflector\EnglishInflector;
24 use Symfony\Component\String\Inflector\InflectorInterface;
25
26 /**
27 * Extracts data using the reflection API.
28 *
29 * @author Kévin Dunglas <dunglas@gmail.com>
30 *
31 * @final
32 */
33 class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface, PropertyInitializableExtractorInterface, PropertyReadInfoExtractorInterface, PropertyWriteInfoExtractorInterface, ConstructorArgumentTypeExtractorInterface
34 {
35 /**
36 * @internal
37 */
38 public static $defaultMutatorPrefixes = ['add', 'remove', 'set'];
39
40 /**
41 * @internal
42 */
43 public static $defaultAccessorPrefixes = ['get', 'is', 'has', 'can'];
44
45 /**
46 * @internal
47 */
48 public static $defaultArrayMutatorPrefixes = ['add', 'remove'];
49
50 public const ALLOW_PRIVATE = 1;
51 public const ALLOW_PROTECTED = 2;
52 public const ALLOW_PUBLIC = 4;
53
54 /** @var int Allow none of the magic methods */
55 public const DISALLOW_MAGIC_METHODS = 0;
56 /** @var int Allow magic __get methods */
57 public const ALLOW_MAGIC_GET = 1 << 0;
58 /** @var int Allow magic __set methods */
59 public const ALLOW_MAGIC_SET = 1 << 1;
60 /** @var int Allow magic __call methods */
61 public const ALLOW_MAGIC_CALL = 1 << 2;
62
63 private const MAP_TYPES = [
64 'integer' => Type::BUILTIN_TYPE_INT,
65 'boolean' => Type::BUILTIN_TYPE_BOOL,
66 'double' => Type::BUILTIN_TYPE_FLOAT,
67 ];
68
69 private $mutatorPrefixes;
70 private $accessorPrefixes;
71 private $arrayMutatorPrefixes;
72 private $enableConstructorExtraction;
73 private $methodReflectionFlags;
74 private $magicMethodsFlags;
75 private $propertyReflectionFlags;
76 private $inflector;
77
78 private $arrayMutatorPrefixesFirst;
79 private $arrayMutatorPrefixesLast;
80
81 /**
82 * @param string[]|null $mutatorPrefixes
83 * @param string[]|null $accessorPrefixes
84 * @param string[]|null $arrayMutatorPrefixes
85 */
86 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)
87 {
88 $this->mutatorPrefixes = $mutatorPrefixes ?? self::$defaultMutatorPrefixes;
89 $this->accessorPrefixes = $accessorPrefixes ?? self::$defaultAccessorPrefixes;
90 $this->arrayMutatorPrefixes = $arrayMutatorPrefixes ?? self::$defaultArrayMutatorPrefixes;
91 $this->enableConstructorExtraction = $enableConstructorExtraction;
92 $this->methodReflectionFlags = $this->getMethodsFlags($accessFlags);
93 $this->propertyReflectionFlags = $this->getPropertyFlags($accessFlags);
94 $this->magicMethodsFlags = $magicMethodsFlags;
95 $this->inflector = $inflector ?? new EnglishInflector();
96
97 $this->arrayMutatorPrefixesFirst = array_merge($this->arrayMutatorPrefixes, array_diff($this->mutatorPrefixes, $this->arrayMutatorPrefixes));
98 $this->arrayMutatorPrefixesLast = array_reverse($this->arrayMutatorPrefixesFirst);
99 }
100
101 /**
102 * {@inheritdoc}
103 */
104 public function getProperties(string $class, array $context = []): ?array
105 {
106 try {
107 $reflectionClass = new \ReflectionClass($class);
108 } catch (\ReflectionException $e) {
109 return null;
110 }
111
112 $reflectionProperties = $reflectionClass->getProperties();
113
114 $properties = [];
115 foreach ($reflectionProperties as $reflectionProperty) {
116 if ($reflectionProperty->getModifiers() & $this->propertyReflectionFlags) {
117 $properties[$reflectionProperty->name] = $reflectionProperty->name;
118 }
119 }
120
121 foreach ($reflectionClass->getMethods($this->methodReflectionFlags) as $reflectionMethod) {
122 if ($reflectionMethod->isStatic()) {
123 continue;
124 }
125
126 $propertyName = $this->getPropertyName($reflectionMethod->name, $reflectionProperties);
127 if (!$propertyName || isset($properties[$propertyName])) {
128 continue;
129 }
130 if ($reflectionClass->hasProperty($lowerCasedPropertyName = lcfirst($propertyName)) || (!$reflectionClass->hasProperty($propertyName) && !preg_match('/^[A-Z]{2,}/', $propertyName))) {
131 $propertyName = $lowerCasedPropertyName;
132 }
133 $properties[$propertyName] = $propertyName;
134 }
135
136 return $properties ? array_values($properties) : null;
137 }
138
139 /**
140 * {@inheritdoc}
141 */
142 public function getTypes(string $class, string $property, array $context = []): ?array
143 {
144 if ($fromMutator = $this->extractFromMutator($class, $property)) {
145 return $fromMutator;
146 }
147
148 if ($fromAccessor = $this->extractFromAccessor($class, $property)) {
149 return $fromAccessor;
150 }
151
152 if (
153 ($context['enable_constructor_extraction'] ?? $this->enableConstructorExtraction) &&
154 $fromConstructor = $this->extractFromConstructor($class, $property)
155 ) {
156 return $fromConstructor;
157 }
158
159 if ($fromPropertyDeclaration = $this->extractFromPropertyDeclaration($class, $property)) {
160 return $fromPropertyDeclaration;
161 }
162
163 return null;
164 }
165
166 /**
167 * {@inheritdoc}
168 */
169 public function getTypesFromConstructor(string $class, string $property): ?array
170 {
171 try {
172 $reflection = new \ReflectionClass($class);
173 } catch (\ReflectionException $e) {
174 return null;
175 }
176 if (!$reflectionConstructor = $reflection->getConstructor()) {
177 return null;
178 }
179 if (!$reflectionParameter = $this->getReflectionParameterFromConstructor($property, $reflectionConstructor)) {
180 return null;
181 }
182 if (!$reflectionType = $reflectionParameter->getType()) {
183 return null;
184 }
185 if (!$types = $this->extractFromReflectionType($reflectionType, $reflectionConstructor->getDeclaringClass())) {
186 return null;
187 }
188
189 return $types;
190 }
191
192 private function getReflectionParameterFromConstructor(string $property, \ReflectionMethod $reflectionConstructor): ?\ReflectionParameter
193 {
194 $reflectionParameter = null;
195 foreach ($reflectionConstructor->getParameters() as $reflectionParameter) {
196 if ($reflectionParameter->getName() === $property) {
197 return $reflectionParameter;
198 }
199 }
200
201 return null;
202 }
203
204 /**
205 * {@inheritdoc}
206 */
207 public function isReadable(string $class, string $property, array $context = []): ?bool
208 {
209 if ($this->isAllowedProperty($class, $property)) {
210 return true;
211 }
212
213 return null !== $this->getReadInfo($class, $property, $context);
214 }
215
216 /**
217 * {@inheritdoc}
218 */
219 public function isWritable(string $class, string $property, array $context = []): ?bool
220 {
221 if ($this->isAllowedProperty($class, $property, true)) {
222 return true;
223 }
224
225 [$reflectionMethod] = $this->getMutatorMethod($class, $property);
226
227 return null !== $reflectionMethod;
228 }
229
230 /**
231 * {@inheritdoc}
232 */
233 public function isInitializable(string $class, string $property, array $context = []): ?bool
234 {
235 try {
236 $reflectionClass = new \ReflectionClass($class);
237 } catch (\ReflectionException $e) {
238 return null;
239 }
240
241 if (!$reflectionClass->isInstantiable()) {
242 return false;
243 }
244
245 if ($constructor = $reflectionClass->getConstructor()) {
246 foreach ($constructor->getParameters() as $parameter) {
247 if ($property === $parameter->name) {
248 return true;
249 }
250 }
251 } elseif ($parentClass = $reflectionClass->getParentClass()) {
252 return $this->isInitializable($parentClass->getName(), $property);
253 }
254
255 return false;
256 }
257
258 /**
259 * {@inheritdoc}
260 */
261 public function getReadInfo(string $class, string $property, array $context = []): ?PropertyReadInfo
262 {
263 try {
264 $reflClass = new \ReflectionClass($class);
265 } catch (\ReflectionException $e) {
266 return null;
267 }
268
269 $allowGetterSetter = $context['enable_getter_setter_extraction'] ?? false;
270 $magicMethods = $context['enable_magic_methods_extraction'] ?? $this->magicMethodsFlags;
271 $allowMagicCall = (bool) ($magicMethods & self::ALLOW_MAGIC_CALL);
272 $allowMagicGet = (bool) ($magicMethods & self::ALLOW_MAGIC_GET);
273
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
277 $allowMagicCall = $context['enable_magic_call_extraction'] ?? false;
278 }
279
280 $hasProperty = $reflClass->hasProperty($property);
281 $camelProp = $this->camelize($property);
282 $getsetter = lcfirst($camelProp); // jQuery style, e.g. read: last(), write: last($item)
283
284 foreach ($this->accessorPrefixes as $prefix) {
285 $methodName = $prefix.$camelProp;
286
287 if ($reflClass->hasMethod($methodName) && $reflClass->getMethod($methodName)->getModifiers() & $this->methodReflectionFlags && !$reflClass->getMethod($methodName)->getNumberOfRequiredParameters()) {
288 $method = $reflClass->getMethod($methodName);
289
290 return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, $methodName, $this->getReadVisiblityForMethod($method), $method->isStatic(), false);
291 }
292 }
293
294 if ($allowGetterSetter && $reflClass->hasMethod($getsetter) && ($reflClass->getMethod($getsetter)->getModifiers() & $this->methodReflectionFlags)) {
295 $method = $reflClass->getMethod($getsetter);
296
297 return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, $getsetter, $this->getReadVisiblityForMethod($method), $method->isStatic(), false);
298 }
299
300 if ($allowMagicGet && $reflClass->hasMethod('__get') && ($reflClass->getMethod('__get')->getModifiers() & $this->methodReflectionFlags)) {
301 return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY, $property, PropertyReadInfo::VISIBILITY_PUBLIC, false, false);
302 }
303
304 if ($hasProperty && ($reflClass->getProperty($property)->getModifiers() & $this->propertyReflectionFlags)) {
305 $reflProperty = $reflClass->getProperty($property);
306
307 return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY, $property, $this->getReadVisiblityForProperty($reflProperty), $reflProperty->isStatic(), true);
308 }
309
310 if ($allowMagicCall && $reflClass->hasMethod('__call') && ($reflClass->getMethod('__call')->getModifiers() & $this->methodReflectionFlags)) {
311 return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, 'get'.$camelProp, PropertyReadInfo::VISIBILITY_PUBLIC, false, false);
312 }
313
314 return null;
315 }
316
317 /**
318 * {@inheritdoc}
319 */
320 public function getWriteInfo(string $class, string $property, array $context = []): ?PropertyWriteInfo
321 {
322 try {
323 $reflClass = new \ReflectionClass($class);
324 } catch (\ReflectionException $e) {
325 return null;
326 }
327
328 $allowGetterSetter = $context['enable_getter_setter_extraction'] ?? false;
329 $magicMethods = $context['enable_magic_methods_extraction'] ?? $this->magicMethodsFlags;
330 $allowMagicCall = (bool) ($magicMethods & self::ALLOW_MAGIC_CALL);
331 $allowMagicSet = (bool) ($magicMethods & self::ALLOW_MAGIC_SET);
332
333 if (isset($context['enable_magic_call_extraction'])) {
334 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__);
335
336 $allowMagicCall = $context['enable_magic_call_extraction'] ?? false;
337 }
338
339 $allowConstruct = $context['enable_constructor_extraction'] ?? $this->enableConstructorExtraction;
340 $allowAdderRemover = $context['enable_adder_remover_extraction'] ?? true;
341
342 $camelized = $this->camelize($property);
343 $constructor = $reflClass->getConstructor();
344 $singulars = $this->inflector->singularize($camelized);
345 $errors = [];
346
347 if (null !== $constructor && $allowConstruct) {
348 foreach ($constructor->getParameters() as $parameter) {
349 if ($parameter->getName() === $property) {
350 return new PropertyWriteInfo(PropertyWriteInfo::TYPE_CONSTRUCTOR, $property);
351 }
352 }
353 }
354
355 [$adderAccessName, $removerAccessName, $adderAndRemoverErrors] = $this->findAdderAndRemover($reflClass, $singulars);
356 if ($allowAdderRemover && null !== $adderAccessName && null !== $removerAccessName) {
357 $adderMethod = $reflClass->getMethod($adderAccessName);
358 $removerMethod = $reflClass->getMethod($removerAccessName);
359
360 $mutator = new PropertyWriteInfo(PropertyWriteInfo::TYPE_ADDER_AND_REMOVER);
361 $mutator->setAdderInfo(new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $adderAccessName, $this->getWriteVisiblityForMethod($adderMethod), $adderMethod->isStatic()));
362 $mutator->setRemoverInfo(new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $removerAccessName, $this->getWriteVisiblityForMethod($removerMethod), $removerMethod->isStatic()));
363
364 return $mutator;
365 }
366
367 $errors[] = $adderAndRemoverErrors;
368
369 foreach ($this->mutatorPrefixes as $mutatorPrefix) {
370 $methodName = $mutatorPrefix.$camelized;
371
372 [$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, $methodName, 1);
373 if (!$accessible) {
374 $errors[] = $methodAccessibleErrors;
375 continue;
376 }
377
378 $method = $reflClass->getMethod($methodName);
379
380 if (!\in_array($mutatorPrefix, $this->arrayMutatorPrefixes, true)) {
381 return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $methodName, $this->getWriteVisiblityForMethod($method), $method->isStatic());
382 }
383 }
384
385 $getsetter = lcfirst($camelized);
386
387 if ($allowGetterSetter) {
388 [$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, $getsetter, 1);
389 if ($accessible) {
390 $method = $reflClass->getMethod($getsetter);
391
392 return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $getsetter, $this->getWriteVisiblityForMethod($method), $method->isStatic());
393 }
394
395 $errors[] = $methodAccessibleErrors;
396 }
397
398 if ($reflClass->hasProperty($property) && ($reflClass->getProperty($property)->getModifiers() & $this->propertyReflectionFlags)) {
399 $reflProperty = $reflClass->getProperty($property);
400 if (\PHP_VERSION_ID < 80100 || !$reflProperty->isReadOnly()) {
401 return new PropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY, $property, $this->getWriteVisiblityForProperty($reflProperty), $reflProperty->isStatic());
402 }
403
404 $errors[] = [sprintf('The property "%s" in class "%s" is a promoted readonly property.', $property, $reflClass->getName())];
405 $allowMagicSet = $allowMagicCall = false;
406 }
407
408 if ($allowMagicSet) {
409 [$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, '__set', 2);
410 if ($accessible) {
411 return new PropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY, $property, PropertyWriteInfo::VISIBILITY_PUBLIC, false);
412 }
413
414 $errors[] = $methodAccessibleErrors;
415 }
416
417 if ($allowMagicCall) {
418 [$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, '__call', 2);
419 if ($accessible) {
420 return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, 'set'.$camelized, PropertyWriteInfo::VISIBILITY_PUBLIC, false);
421 }
422
423 $errors[] = $methodAccessibleErrors;
424 }
425
426 if (!$allowAdderRemover && null !== $adderAccessName && null !== $removerAccessName) {
427 $errors[] = [sprintf(
428 'The property "%s" in class "%s" can be defined with the methods "%s()" but '.
429 'the new value must be an array or an instance of \Traversable',
430 $property,
431 $reflClass->getName(),
432 implode('()", "', [$adderAccessName, $removerAccessName])
433 )];
434 }
435
436 $noneProperty = new PropertyWriteInfo();
437 $noneProperty->setErrors(array_merge([], ...$errors));
438
439 return $noneProperty;
440 }
441
442 /**
443 * @return Type[]|null
444 */
445 private function extractFromMutator(string $class, string $property): ?array
446 {
447 [$reflectionMethod, $prefix] = $this->getMutatorMethod($class, $property);
448 if (null === $reflectionMethod) {
449 return null;
450 }
451
452 $reflectionParameters = $reflectionMethod->getParameters();
453 $reflectionParameter = $reflectionParameters[0];
454
455 if (!$reflectionType = $reflectionParameter->getType()) {
456 return null;
457 }
458 $type = $this->extractFromReflectionType($reflectionType, $reflectionMethod->getDeclaringClass());
459
460 if (1 === \count($type) && \in_array($prefix, $this->arrayMutatorPrefixes)) {
461 $type = [new Type(Type::BUILTIN_TYPE_ARRAY, $this->isNullableProperty($class, $property), null, true, new Type(Type::BUILTIN_TYPE_INT), $type[0])];
462 }
463
464 return $type;
465 }
466
467 /**
468 * Tries to extract type information from accessors.
469 *
470 * @return Type[]|null
471 */
472 private function extractFromAccessor(string $class, string $property): ?array
473 {
474 [$reflectionMethod, $prefix] = $this->getAccessorMethod($class, $property);
475 if (null === $reflectionMethod) {
476 return null;
477 }
478
479 if ($reflectionType = $reflectionMethod->getReturnType()) {
480 return $this->extractFromReflectionType($reflectionType, $reflectionMethod->getDeclaringClass());
481 }
482
483 if (\in_array($prefix, ['is', 'can', 'has'])) {
484 return [new Type(Type::BUILTIN_TYPE_BOOL)];
485 }
486
487 return null;
488 }
489
490 /**
491 * Tries to extract type information from constructor.
492 *
493 * @return Type[]|null
494 */
495 private function extractFromConstructor(string $class, string $property): ?array
496 {
497 try {
498 $reflectionClass = new \ReflectionClass($class);
499 } catch (\ReflectionException $e) {
500 return null;
501 }
502
503 $constructor = $reflectionClass->getConstructor();
504
505 if (!$constructor) {
506 return null;
507 }
508
509 foreach ($constructor->getParameters() as $parameter) {
510 if ($property !== $parameter->name) {
511 continue;
512 }
513 $reflectionType = $parameter->getType();
514
515 return $reflectionType ? $this->extractFromReflectionType($reflectionType, $constructor->getDeclaringClass()) : null;
516 }
517
518 if ($parentClass = $reflectionClass->getParentClass()) {
519 return $this->extractFromConstructor($parentClass->getName(), $property);
520 }
521
522 return null;
523 }
524
525 private function extractFromPropertyDeclaration(string $class, string $property): ?array
526 {
527 try {
528 $reflectionClass = new \ReflectionClass($class);
529
530 if (\PHP_VERSION_ID >= 70400) {
531 $reflectionProperty = $reflectionClass->getProperty($property);
532 $reflectionPropertyType = $reflectionProperty->getType();
533
534 if (null !== $reflectionPropertyType && $types = $this->extractFromReflectionType($reflectionPropertyType, $reflectionProperty->getDeclaringClass())) {
535 return $types;
536 }
537 }
538 } catch (\ReflectionException $e) {
539 return null;
540 }
541
542 $defaultValue = $reflectionClass->getDefaultProperties()[$property] ?? null;
543
544 if (null === $defaultValue) {
545 return null;
546 }
547
548 $type = \gettype($defaultValue);
549 $type = static::MAP_TYPES[$type] ?? $type;
550
551 return [new Type($type, $this->isNullableProperty($class, $property), null, Type::BUILTIN_TYPE_ARRAY === $type)];
552 }
553
554 private function extractFromReflectionType(\ReflectionType $reflectionType, \ReflectionClass $declaringClass): array
555 {
556 $types = [];
557 $nullable = $reflectionType->allowsNull();
558
559 foreach (($reflectionType instanceof \ReflectionUnionType || $reflectionType instanceof \ReflectionIntersectionType) ? $reflectionType->getTypes() : [$reflectionType] as $type) {
560 if (!$type instanceof \ReflectionNamedType) {
561 // Nested composite types are not supported yet.
562 return [];
563 }
564
565 $phpTypeOrClass = $type->getName();
566 if ('null' === $phpTypeOrClass || 'mixed' === $phpTypeOrClass || 'never' === $phpTypeOrClass) {
567 continue;
568 }
569
570 if (Type::BUILTIN_TYPE_ARRAY === $phpTypeOrClass) {
571 $types[] = new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true);
572 } elseif ('void' === $phpTypeOrClass) {
573 $types[] = new Type(Type::BUILTIN_TYPE_NULL, $nullable);
574 } elseif ($type->isBuiltin()) {
575 $types[] = new Type($phpTypeOrClass, $nullable);
576 } else {
577 $types[] = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $this->resolveTypeName($phpTypeOrClass, $declaringClass));
578 }
579 }
580
581 return $types;
582 }
583
584 private function resolveTypeName(string $name, \ReflectionClass $declaringClass): string
585 {
586 if ('self' === $lcName = strtolower($name)) {
587 return $declaringClass->name;
588 }
589 if ('parent' === $lcName && $parent = $declaringClass->getParentClass()) {
590 return $parent->name;
591 }
592
593 return $name;
594 }
595
596 private function isNullableProperty(string $class, string $property): bool
597 {
598 try {
599 $reflectionProperty = new \ReflectionProperty($class, $property);
600
601 if (\PHP_VERSION_ID >= 70400) {
602 $reflectionPropertyType = $reflectionProperty->getType();
603
604 return null !== $reflectionPropertyType && $reflectionPropertyType->allowsNull();
605 }
606
607 return false;
608 } catch (\ReflectionException $e) {
609 // Return false if the property doesn't exist
610 }
611
612 return false;
613 }
614
615 private function isAllowedProperty(string $class, string $property, bool $writeAccessRequired = false): bool
616 {
617 try {
618 $reflectionProperty = new \ReflectionProperty($class, $property);
619
620 if ($writeAccessRequired) {
621 if (\PHP_VERSION_ID >= 80100 && $reflectionProperty->isReadOnly()) {
622 return false;
623 }
624
625 if (\PHP_VERSION_ID >= 80400 && ($reflectionProperty->isProtectedSet() || $reflectionProperty->isPrivateSet())) {
626 return false;
627 }
628
629 if (\PHP_VERSION_ID >= 80400 &&$reflectionProperty->isVirtual() && !$reflectionProperty->hasHook(\PropertyHookType::Set)) {
630 return false;
631 }
632 }
633
634 return (bool) ($reflectionProperty->getModifiers() & $this->propertyReflectionFlags);
635 } catch (\ReflectionException $e) {
636 // Return false if the property doesn't exist
637 }
638
639 return false;
640 }
641
642 /**
643 * Gets the accessor method.
644 *
645 * Returns an array with a the instance of \ReflectionMethod as first key
646 * and the prefix of the method as second or null if not found.
647 */
648 private function getAccessorMethod(string $class, string $property): ?array
649 {
650 $ucProperty = ucfirst($property);
651
652 foreach ($this->accessorPrefixes as $prefix) {
653 try {
654 $reflectionMethod = new \ReflectionMethod($class, $prefix.$ucProperty);
655 if ($reflectionMethod->isStatic()) {
656 continue;
657 }
658
659 if (0 === $reflectionMethod->getNumberOfRequiredParameters()) {
660 return [$reflectionMethod, $prefix];
661 }
662 } catch (\ReflectionException $e) {
663 // Return null if the property doesn't exist
664 }
665 }
666
667 return null;
668 }
669
670 /**
671 * Returns an array with a the instance of \ReflectionMethod as first key
672 * and the prefix of the method as second or null if not found.
673 */
674 private function getMutatorMethod(string $class, string $property): ?array
675 {
676 $ucProperty = ucfirst($property);
677 $ucSingulars = $this->inflector->singularize($ucProperty);
678
679 $mutatorPrefixes = \in_array($ucProperty, $ucSingulars, true) ? $this->arrayMutatorPrefixesLast : $this->arrayMutatorPrefixesFirst;
680
681 foreach ($mutatorPrefixes as $prefix) {
682 $names = [$ucProperty];
683 if (\in_array($prefix, $this->arrayMutatorPrefixes)) {
684 $names = array_merge($names, $ucSingulars);
685 }
686
687 foreach ($names as $name) {
688 try {
689 $reflectionMethod = new \ReflectionMethod($class, $prefix.$name);
690 if ($reflectionMethod->isStatic()) {
691 continue;
692 }
693
694 // Parameter can be optional to allow things like: method(?array $foo = null)
695 if ($reflectionMethod->getNumberOfParameters() >= 1) {
696 return [$reflectionMethod, $prefix];
697 }
698 } catch (\ReflectionException $e) {
699 // Try the next prefix if the method doesn't exist
700 }
701 }
702 }
703
704 return null;
705 }
706
707 private function getPropertyName(string $methodName, array $reflectionProperties): ?string
708 {
709 $pattern = implode('|', array_merge($this->accessorPrefixes, $this->mutatorPrefixes));
710
711 if ('' !== $pattern && preg_match('/^('.$pattern.')(.+)$/i', $methodName, $matches)) {
712 if (!\in_array($matches[1], $this->arrayMutatorPrefixes)) {
713 return $matches[2];
714 }
715
716 foreach ($reflectionProperties as $reflectionProperty) {
717 foreach ($this->inflector->singularize($reflectionProperty->name) as $name) {
718 if (strtolower($name) === strtolower($matches[2])) {
719 return $reflectionProperty->name;
720 }
721 }
722 }
723
724 return $matches[2];
725 }
726
727 return null;
728 }
729
730 /**
731 * Searches for add and remove methods.
732 *
733 * @param \ReflectionClass $reflClass The reflection class for the given object
734 * @param array $singulars The singular form of the property name or null
735 *
736 * @return array An array containing the adder and remover when found and errors
737 */
738 private function findAdderAndRemover(\ReflectionClass $reflClass, array $singulars): array
739 {
740 if (!\is_array($this->arrayMutatorPrefixes) && 2 !== \count($this->arrayMutatorPrefixes)) {
741 return [null, null, []];
742 }
743
744 [$addPrefix, $removePrefix] = $this->arrayMutatorPrefixes;
745 $errors = [];
746
747 foreach ($singulars as $singular) {
748 $addMethod = $addPrefix.$singular;
749 $removeMethod = $removePrefix.$singular;
750
751 [$addMethodFound, $addMethodAccessibleErrors] = $this->isMethodAccessible($reflClass, $addMethod, 1);
752 [$removeMethodFound, $removeMethodAccessibleErrors] = $this->isMethodAccessible($reflClass, $removeMethod, 1);
753 $errors[] = $addMethodAccessibleErrors;
754 $errors[] = $removeMethodAccessibleErrors;
755
756 if ($addMethodFound && $removeMethodFound) {
757 return [$addMethod, $removeMethod, []];
758 }
759
760 if ($addMethodFound && !$removeMethodFound) {
761 $errors[] = [sprintf('The add method "%s" in class "%s" was found, but the corresponding remove method "%s" was not found', $addMethod, $reflClass->getName(), $removeMethod)];
762 } elseif (!$addMethodFound && $removeMethodFound) {
763 $errors[] = [sprintf('The remove method "%s" in class "%s" was found, but the corresponding add method "%s" was not found', $removeMethod, $reflClass->getName(), $addMethod)];
764 }
765 }
766
767 return [null, null, array_merge([], ...$errors)];
768 }
769
770 /**
771 * Returns whether a method is public and has the number of required parameters and errors.
772 */
773 private function isMethodAccessible(\ReflectionClass $class, string $methodName, int $parameters): array
774 {
775 $errors = [];
776
777 if ($class->hasMethod($methodName)) {
778 $method = $class->getMethod($methodName);
779
780 if (\ReflectionMethod::IS_PUBLIC === $this->methodReflectionFlags && !$method->isPublic()) {
781 $errors[] = sprintf('The method "%s" in class "%s" was found but does not have public access.', $methodName, $class->getName());
782 } elseif ($method->getNumberOfRequiredParameters() > $parameters || $method->getNumberOfParameters() < $parameters) {
783 $errors[] = sprintf('The method "%s" in class "%s" requires %d arguments, but should accept only %d.', $methodName, $class->getName(), $method->getNumberOfRequiredParameters(), $parameters);
784 } else {
785 return [true, $errors];
786 }
787 }
788
789 return [false, $errors];
790 }
791
792 /**
793 * Camelizes a given string.
794 */
795 private function camelize(string $string): string
796 {
797 return str_replace(' ', '', ucwords(str_replace('_', ' ', $string)));
798 }
799
800 /**
801 * Return allowed reflection method flags.
802 */
803 private function getMethodsFlags(int $accessFlags): int
804 {
805 $methodFlags = 0;
806
807 if ($accessFlags & self::ALLOW_PUBLIC) {
808 $methodFlags |= \ReflectionMethod::IS_PUBLIC;
809 }
810
811 if ($accessFlags & self::ALLOW_PRIVATE) {
812 $methodFlags |= \ReflectionMethod::IS_PRIVATE;
813 }
814
815 if ($accessFlags & self::ALLOW_PROTECTED) {
816 $methodFlags |= \ReflectionMethod::IS_PROTECTED;
817 }
818
819 return $methodFlags;
820 }
821
822 /**
823 * Return allowed reflection property flags.
824 */
825 private function getPropertyFlags(int $accessFlags): int
826 {
827 $propertyFlags = 0;
828
829 if ($accessFlags & self::ALLOW_PUBLIC) {
830 $propertyFlags |= \ReflectionProperty::IS_PUBLIC;
831 }
832
833 if ($accessFlags & self::ALLOW_PRIVATE) {
834 $propertyFlags |= \ReflectionProperty::IS_PRIVATE;
835 }
836
837 if ($accessFlags & self::ALLOW_PROTECTED) {
838 $propertyFlags |= \ReflectionProperty::IS_PROTECTED;
839 }
840
841 return $propertyFlags;
842 }
843
844 private function getReadVisiblityForProperty(\ReflectionProperty $reflectionProperty): string
845 {
846 if ($reflectionProperty->isPrivate()) {
847 return PropertyReadInfo::VISIBILITY_PRIVATE;
848 }
849
850 if ($reflectionProperty->isProtected()) {
851 return PropertyReadInfo::VISIBILITY_PROTECTED;
852 }
853
854 return PropertyReadInfo::VISIBILITY_PUBLIC;
855 }
856
857 private function getReadVisiblityForMethod(\ReflectionMethod $reflectionMethod): string
858 {
859 if ($reflectionMethod->isPrivate()) {
860 return PropertyReadInfo::VISIBILITY_PRIVATE;
861 }
862
863 if ($reflectionMethod->isProtected()) {
864 return PropertyReadInfo::VISIBILITY_PROTECTED;
865 }
866
867 return PropertyReadInfo::VISIBILITY_PUBLIC;
868 }
869
870 private function getWriteVisiblityForProperty(\ReflectionProperty $reflectionProperty): string
871 {
872 if (\PHP_VERSION_ID >= 80400) {
873 if ($reflectionProperty->isVirtual() && !$reflectionProperty->hasHook(\PropertyHookType::Set)) {
874 return PropertyWriteInfo::VISIBILITY_PRIVATE;
875 }
876
877 if ($reflectionProperty->isPrivateSet()) {
878 return PropertyWriteInfo::VISIBILITY_PRIVATE;
879 }
880
881 if ($reflectionProperty->isProtectedSet()) {
882 return PropertyWriteInfo::VISIBILITY_PROTECTED;
883 }
884 }
885
886 if ($reflectionProperty->isPrivate()) {
887 return PropertyWriteInfo::VISIBILITY_PRIVATE;
888 }
889
890 if ($reflectionProperty->isProtected()) {
891 return PropertyWriteInfo::VISIBILITY_PROTECTED;
892 }
893
894 return PropertyWriteInfo::VISIBILITY_PUBLIC;
895 }
896
897 private function getWriteVisiblityForMethod(\ReflectionMethod $reflectionMethod): string
898 {
899 if ($reflectionMethod->isPrivate()) {
900 return PropertyWriteInfo::VISIBILITY_PRIVATE;
901 }
902
903 if ($reflectionMethod->isProtected()) {
904 return PropertyWriteInfo::VISIBILITY_PROTECTED;
905 }
906
907 return PropertyWriteInfo::VISIBILITY_PUBLIC;
908 }
909 }
910