login consent app sql

This commit is contained in:
2022-05-03 08:54:45 +02:00
parent e7253acfd8
commit f9a6535906
1652 changed files with 187600 additions and 45 deletions

View File

@ -0,0 +1,39 @@
CHANGELOG
=========
5.4
---
* Add PhpStanExtractor
5.3
---
* Add support for multiple types for collection keys & values
* Deprecate the `Type::getCollectionKeyType()` and `Type::getCollectionValueType()` methods, use `Type::getCollectionKeyTypes()` and `Type::getCollectionValueTypes()` instead
5.2.0
-----
* deprecated the `enable_magic_call_extraction` context option in `ReflectionExtractor::getWriteInfo()` and `ReflectionExtractor::getReadInfo()` in favor of `enable_magic_methods_extraction`
5.1.0
-----
* Add support for extracting accessor and mutator via PHP Reflection
4.3.0
-----
* Added the ability to extract private and protected properties and methods on `ReflectionExtractor`
* Added the ability to extract property type based on its initial value
4.2.0
-----
* added `PropertyInitializableExtractorInterface` to test if a property can be initialized through the constructor (implemented by `ReflectionExtractor`)
3.3.0
-----
* Added `PropertyInfoPass`

View File

@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyInfo\DependencyInjection;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Adds extractors to the property_info.constructor_extractor service.
*
* @author Dmitrii Poddubnyi <dpoddubny@gmail.com>
*/
final class PropertyInfoConstructorPass implements CompilerPassInterface
{
use PriorityTaggedServiceTrait;
private $service;
private $tag;
public function __construct(string $service = 'property_info.constructor_extractor', string $tag = 'property_info.constructor_extractor')
{
if (0 < \func_num_args()) {
trigger_deprecation('symfony/property-info', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
}
$this->service = $service;
$this->tag = $tag;
}
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->service)) {
return;
}
$definition = $container->getDefinition($this->service);
$listExtractors = $this->findAndSortTaggedServices($this->tag, $container);
$definition->replaceArgument(0, new IteratorArgument($listExtractors));
}
}

View File

@ -0,0 +1,75 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyInfo\DependencyInjection;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Adds extractors to the property_info service.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class PropertyInfoPass implements CompilerPassInterface
{
use PriorityTaggedServiceTrait;
private $propertyInfoService;
private $listExtractorTag;
private $typeExtractorTag;
private $descriptionExtractorTag;
private $accessExtractorTag;
private $initializableExtractorTag;
public function __construct(string $propertyInfoService = 'property_info', string $listExtractorTag = 'property_info.list_extractor', string $typeExtractorTag = 'property_info.type_extractor', string $descriptionExtractorTag = 'property_info.description_extractor', string $accessExtractorTag = 'property_info.access_extractor', string $initializableExtractorTag = 'property_info.initializable_extractor')
{
if (0 < \func_num_args()) {
trigger_deprecation('symfony/property-info', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
}
$this->propertyInfoService = $propertyInfoService;
$this->listExtractorTag = $listExtractorTag;
$this->typeExtractorTag = $typeExtractorTag;
$this->descriptionExtractorTag = $descriptionExtractorTag;
$this->accessExtractorTag = $accessExtractorTag;
$this->initializableExtractorTag = $initializableExtractorTag;
}
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->propertyInfoService)) {
return;
}
$definition = $container->getDefinition($this->propertyInfoService);
$listExtractors = $this->findAndSortTaggedServices($this->listExtractorTag, $container);
$definition->replaceArgument(0, new IteratorArgument($listExtractors));
$typeExtractors = $this->findAndSortTaggedServices($this->typeExtractorTag, $container);
$definition->replaceArgument(1, new IteratorArgument($typeExtractors));
$descriptionExtractors = $this->findAndSortTaggedServices($this->descriptionExtractorTag, $container);
$definition->replaceArgument(2, new IteratorArgument($descriptionExtractors));
$accessExtractors = $this->findAndSortTaggedServices($this->accessExtractorTag, $container);
$definition->replaceArgument(3, new IteratorArgument($accessExtractors));
$initializableExtractors = $this->findAndSortTaggedServices($this->initializableExtractorTag, $container);
$definition->setArgument(4, new IteratorArgument($initializableExtractors));
}
}

View File

@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyInfo\Extractor;
use Symfony\Component\PropertyInfo\Type;
/**
* Infers the constructor argument type.
*
* @author Dmitrii Poddubnyi <dpoddubny@gmail.com>
*
* @internal
*/
interface ConstructorArgumentTypeExtractorInterface
{
/**
* Gets types of an argument from constructor.
*
* @return Type[]|null
*
* @internal
*/
public function getTypesFromConstructor(string $class, string $property): ?array;
}

View File

@ -0,0 +1,47 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyInfo\Extractor;
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
/**
* Extracts the constructor argument type using ConstructorArgumentTypeExtractorInterface implementations.
*
* @author Dmitrii Poddubnyi <dpoddubny@gmail.com>
*/
final class ConstructorExtractor implements PropertyTypeExtractorInterface
{
private $extractors;
/**
* @param iterable<int, ConstructorArgumentTypeExtractorInterface> $extractors
*/
public function __construct(iterable $extractors = [])
{
$this->extractors = $extractors;
}
/**
* {@inheritdoc}
*/
public function getTypes(string $class, string $property, array $context = []): ?array
{
foreach ($this->extractors as $extractor) {
$value = $extractor->getTypesFromConstructor($class, $property);
if (null !== $value) {
return $value;
}
}
return null;
}
}

View File

@ -0,0 +1,360 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyInfo\Extractor;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag;
use phpDocumentor\Reflection\DocBlockFactory;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\Types\Context;
use phpDocumentor\Reflection\Types\ContextFactory;
use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
use Symfony\Component\PropertyInfo\Type;
use Symfony\Component\PropertyInfo\Util\PhpDocTypeHelper;
/**
* Extracts data using a PHPDoc parser.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*
* @final
*/
class PhpDocExtractor implements PropertyDescriptionExtractorInterface, PropertyTypeExtractorInterface, ConstructorArgumentTypeExtractorInterface
{
public const PROPERTY = 0;
public const ACCESSOR = 1;
public const MUTATOR = 2;
/**
* @var array<string, array{DocBlock|null, int|null, string|null}>
*/
private $docBlocks = [];
/**
* @var Context[]
*/
private $contexts = [];
private $docBlockFactory;
private $contextFactory;
private $phpDocTypeHelper;
private $mutatorPrefixes;
private $accessorPrefixes;
private $arrayMutatorPrefixes;
/**
* @param string[]|null $mutatorPrefixes
* @param string[]|null $accessorPrefixes
* @param string[]|null $arrayMutatorPrefixes
*/
public function __construct(DocBlockFactoryInterface $docBlockFactory = null, array $mutatorPrefixes = null, array $accessorPrefixes = null, array $arrayMutatorPrefixes = null)
{
if (!class_exists(DocBlockFactory::class)) {
throw new \LogicException(sprintf('Unable to use the "%s" class as the "phpdocumentor/reflection-docblock" package is not installed.', __CLASS__));
}
$this->docBlockFactory = $docBlockFactory ?: DocBlockFactory::createInstance();
$this->contextFactory = new ContextFactory();
$this->phpDocTypeHelper = new PhpDocTypeHelper();
$this->mutatorPrefixes = $mutatorPrefixes ?? ReflectionExtractor::$defaultMutatorPrefixes;
$this->accessorPrefixes = $accessorPrefixes ?? ReflectionExtractor::$defaultAccessorPrefixes;
$this->arrayMutatorPrefixes = $arrayMutatorPrefixes ?? ReflectionExtractor::$defaultArrayMutatorPrefixes;
}
/**
* {@inheritdoc}
*/
public function getShortDescription(string $class, string $property, array $context = []): ?string
{
/** @var $docBlock DocBlock */
[$docBlock] = $this->getDocBlock($class, $property);
if (!$docBlock) {
return null;
}
$shortDescription = $docBlock->getSummary();
if (!empty($shortDescription)) {
return $shortDescription;
}
foreach ($docBlock->getTagsByName('var') as $var) {
if ($var && !$var instanceof InvalidTag) {
$varDescription = $var->getDescription()->render();
if (!empty($varDescription)) {
return $varDescription;
}
}
}
return null;
}
/**
* {@inheritdoc}
*/
public function getLongDescription(string $class, string $property, array $context = []): ?string
{
/** @var $docBlock DocBlock */
[$docBlock] = $this->getDocBlock($class, $property);
if (!$docBlock) {
return null;
}
$contents = $docBlock->getDescription()->render();
return '' === $contents ? null : $contents;
}
/**
* {@inheritdoc}
*/
public function getTypes(string $class, string $property, array $context = []): ?array
{
/** @var $docBlock DocBlock */
[$docBlock, $source, $prefix] = $this->getDocBlock($class, $property);
if (!$docBlock) {
return null;
}
switch ($source) {
case self::PROPERTY:
$tag = 'var';
break;
case self::ACCESSOR:
$tag = 'return';
break;
case self::MUTATOR:
$tag = 'param';
break;
}
$parentClass = null;
$types = [];
/** @var DocBlock\Tags\Var_|DocBlock\Tags\Return_|DocBlock\Tags\Param $tag */
foreach ($docBlock->getTagsByName($tag) as $tag) {
if ($tag && !$tag instanceof InvalidTag && null !== $tag->getType()) {
foreach ($this->phpDocTypeHelper->getTypes($tag->getType()) as $type) {
switch ($type->getClassName()) {
case 'self':
case 'static':
$resolvedClass = $class;
break;
case 'parent':
if (false !== $resolvedClass = $parentClass ?? $parentClass = get_parent_class($class)) {
break;
}
// no break
default:
$types[] = $type;
continue 2;
}
$types[] = new Type(Type::BUILTIN_TYPE_OBJECT, $type->isNullable(), $resolvedClass, $type->isCollection(), $type->getCollectionKeyTypes(), $type->getCollectionValueTypes());
}
}
}
if (!isset($types[0])) {
return null;
}
if (!\in_array($prefix, $this->arrayMutatorPrefixes)) {
return $types;
}
return [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), $types[0])];
}
/**
* {@inheritdoc}
*/
public function getTypesFromConstructor(string $class, string $property): ?array
{
$docBlock = $this->getDocBlockFromConstructor($class, $property);
if (!$docBlock) {
return null;
}
$types = [];
/** @var DocBlock\Tags\Var_|DocBlock\Tags\Return_|DocBlock\Tags\Param $tag */
foreach ($docBlock->getTagsByName('param') as $tag) {
if ($tag && null !== $tag->getType()) {
$types[] = $this->phpDocTypeHelper->getTypes($tag->getType());
}
}
if (!isset($types[0])) {
return null;
}
return array_merge([], ...$types);
}
private function getDocBlockFromConstructor(string $class, string $property): ?DocBlock
{
try {
$reflectionClass = new \ReflectionClass($class);
} catch (\ReflectionException $e) {
return null;
}
$reflectionConstructor = $reflectionClass->getConstructor();
if (!$reflectionConstructor) {
return null;
}
try {
$docBlock = $this->docBlockFactory->create($reflectionConstructor, $this->contextFactory->createFromReflector($reflectionConstructor));
return $this->filterDocBlockParams($docBlock, $property);
} catch (\InvalidArgumentException $e) {
return null;
}
}
private function filterDocBlockParams(DocBlock $docBlock, string $allowedParam): DocBlock
{
$tags = array_values(array_filter($docBlock->getTagsByName('param'), function ($tag) use ($allowedParam) {
return $tag instanceof DocBlock\Tags\Param && $allowedParam === $tag->getVariableName();
}));
return new DocBlock($docBlock->getSummary(), $docBlock->getDescription(), $tags, $docBlock->getContext(),
$docBlock->getLocation(), $docBlock->isTemplateStart(), $docBlock->isTemplateEnd());
}
/**
* @return array{DocBlock|null, int|null, string|null}
*/
private function getDocBlock(string $class, string $property): array
{
$propertyHash = sprintf('%s::%s', $class, $property);
if (isset($this->docBlocks[$propertyHash])) {
return $this->docBlocks[$propertyHash];
}
$ucFirstProperty = ucfirst($property);
switch (true) {
case $docBlock = $this->getDocBlockFromProperty($class, $property):
$data = [$docBlock, self::PROPERTY, null];
break;
case [$docBlock] = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::ACCESSOR):
$data = [$docBlock, self::ACCESSOR, null];
break;
case [$docBlock, $prefix] = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::MUTATOR):
$data = [$docBlock, self::MUTATOR, $prefix];
break;
default:
$data = [null, null, null];
}
return $this->docBlocks[$propertyHash] = $data;
}
private function getDocBlockFromProperty(string $class, string $property): ?DocBlock
{
// Use a ReflectionProperty instead of $class to get the parent class if applicable
try {
$reflectionProperty = new \ReflectionProperty($class, $property);
} catch (\ReflectionException $e) {
return null;
}
$reflector = $reflectionProperty->getDeclaringClass();
foreach ($reflector->getTraits() as $trait) {
if ($trait->hasProperty($property)) {
return $this->getDocBlockFromProperty($trait->getName(), $property);
}
}
try {
return $this->docBlockFactory->create($reflectionProperty, $this->createFromReflector($reflector));
} catch (\InvalidArgumentException|\RuntimeException $e) {
return null;
}
}
/**
* @return array{DocBlock, string}|null
*/
private function getDocBlockFromMethod(string $class, string $ucFirstProperty, int $type): ?array
{
$prefixes = self::ACCESSOR === $type ? $this->accessorPrefixes : $this->mutatorPrefixes;
$prefix = null;
foreach ($prefixes as $prefix) {
$methodName = $prefix.$ucFirstProperty;
try {
$reflectionMethod = new \ReflectionMethod($class, $methodName);
if ($reflectionMethod->isStatic()) {
continue;
}
if (
(self::ACCESSOR === $type && 0 === $reflectionMethod->getNumberOfRequiredParameters()) ||
(self::MUTATOR === $type && $reflectionMethod->getNumberOfParameters() >= 1)
) {
break;
}
} catch (\ReflectionException $e) {
// Try the next prefix if the method doesn't exist
}
}
if (!isset($reflectionMethod)) {
return null;
}
$reflector = $reflectionMethod->getDeclaringClass();
foreach ($reflector->getTraits() as $trait) {
if ($trait->hasMethod($methodName)) {
return $this->getDocBlockFromMethod($trait->getName(), $ucFirstProperty, $type);
}
}
try {
return [$this->docBlockFactory->create($reflectionMethod, $this->createFromReflector($reflector)), $prefix];
} catch (\InvalidArgumentException|\RuntimeException $e) {
return null;
}
}
/**
* Prevents a lot of redundant calls to ContextFactory::createForNamespace().
*/
private function createFromReflector(\ReflectionClass $reflector): Context
{
$cacheKey = $reflector->getNamespaceName().':'.$reflector->getFileName();
if (isset($this->contexts[$cacheKey])) {
return $this->contexts[$cacheKey];
}
$this->contexts[$cacheKey] = $this->contextFactory->createFromReflector($reflector);
return $this->contexts[$cacheKey];
}
}

View File

@ -0,0 +1,277 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyInfo\Extractor;
use PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use PHPStan\PhpDocParser\Lexer\Lexer;
use PHPStan\PhpDocParser\Parser\ConstExprParser;
use PHPStan\PhpDocParser\Parser\PhpDocParser;
use PHPStan\PhpDocParser\Parser\TokenIterator;
use PHPStan\PhpDocParser\Parser\TypeParser;
use Symfony\Component\PropertyInfo\PhpStan\NameScopeFactory;
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
use Symfony\Component\PropertyInfo\Type;
use Symfony\Component\PropertyInfo\Util\PhpStanTypeHelper;
/**
* Extracts data using PHPStan parser.
*
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*/
final class PhpStanExtractor implements PropertyTypeExtractorInterface, ConstructorArgumentTypeExtractorInterface
{
private const PROPERTY = 0;
private const ACCESSOR = 1;
private const MUTATOR = 2;
/** @var PhpDocParser */
private $phpDocParser;
/** @var Lexer */
private $lexer;
/** @var NameScopeFactory */
private $nameScopeFactory;
/** @var array<string, array{PhpDocNode|null, int|null, string|null, string|null}> */
private $docBlocks = [];
private $phpStanTypeHelper;
private $mutatorPrefixes;
private $accessorPrefixes;
private $arrayMutatorPrefixes;
/**
* @param list<string>|null $mutatorPrefixes
* @param list<string>|null $accessorPrefixes
* @param list<string>|null $arrayMutatorPrefixes
*/
public function __construct(array $mutatorPrefixes = null, array $accessorPrefixes = null, array $arrayMutatorPrefixes = null)
{
$this->phpStanTypeHelper = new PhpStanTypeHelper();
$this->mutatorPrefixes = $mutatorPrefixes ?? ReflectionExtractor::$defaultMutatorPrefixes;
$this->accessorPrefixes = $accessorPrefixes ?? ReflectionExtractor::$defaultAccessorPrefixes;
$this->arrayMutatorPrefixes = $arrayMutatorPrefixes ?? ReflectionExtractor::$defaultArrayMutatorPrefixes;
$this->phpDocParser = new PhpDocParser(new TypeParser(new ConstExprParser()), new ConstExprParser());
$this->lexer = new Lexer();
$this->nameScopeFactory = new NameScopeFactory();
}
public function getTypes(string $class, string $property, array $context = []): ?array
{
/** @var PhpDocNode|null $docNode */
[$docNode, $source, $prefix, $declaringClass] = $this->getDocBlock($class, $property);
$nameScope = $this->nameScopeFactory->create($class, $declaringClass);
if (null === $docNode) {
return null;
}
switch ($source) {
case self::PROPERTY:
$tag = '@var';
break;
case self::ACCESSOR:
$tag = '@return';
break;
case self::MUTATOR:
$tag = '@param';
break;
}
$parentClass = null;
$types = [];
foreach ($docNode->getTagsByName($tag) as $tagDocNode) {
if ($tagDocNode->value instanceof InvalidTagValueNode) {
continue;
}
foreach ($this->phpStanTypeHelper->getTypes($tagDocNode->value, $nameScope) as $type) {
switch ($type->getClassName()) {
case 'self':
case 'static':
$resolvedClass = $class;
break;
case 'parent':
if (false !== $resolvedClass = $parentClass ?? $parentClass = get_parent_class($class)) {
break;
}
// no break
default:
$types[] = $type;
continue 2;
}
$types[] = new Type(Type::BUILTIN_TYPE_OBJECT, $type->isNullable(), $resolvedClass, $type->isCollection(), $type->getCollectionKeyTypes(), $type->getCollectionValueTypes());
}
}
if (!isset($types[0])) {
return null;
}
if (!\in_array($prefix, $this->arrayMutatorPrefixes, true)) {
return $types;
}
return [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), $types[0])];
}
public function getTypesFromConstructor(string $class, string $property): ?array
{
if (null === $tagDocNode = $this->getDocBlockFromConstructor($class, $property)) {
return null;
}
$types = [];
foreach ($this->phpStanTypeHelper->getTypes($tagDocNode, $this->nameScopeFactory->create($class)) as $type) {
$types[] = $type;
}
if (!isset($types[0])) {
return null;
}
return $types;
}
private function getDocBlockFromConstructor(string $class, string $property): ?ParamTagValueNode
{
try {
$reflectionClass = new \ReflectionClass($class);
} catch (\ReflectionException $e) {
return null;
}
if (null === $reflectionConstructor = $reflectionClass->getConstructor()) {
return null;
}
$rawDocNode = $reflectionConstructor->getDocComment();
$tokens = new TokenIterator($this->lexer->tokenize($rawDocNode));
$phpDocNode = $this->phpDocParser->parse($tokens);
$tokens->consumeTokenType(Lexer::TOKEN_END);
return $this->filterDocBlockParams($phpDocNode, $property);
}
private function filterDocBlockParams(PhpDocNode $docNode, string $allowedParam): ?ParamTagValueNode
{
$tags = array_values(array_filter($docNode->getTagsByName('@param'), function ($tagNode) use ($allowedParam) {
return $tagNode instanceof PhpDocTagNode && ('$'.$allowedParam) === $tagNode->value->parameterName;
}));
if (!$tags) {
return null;
}
return $tags[0]->value;
}
/**
* @return array{PhpDocNode|null, int|null, string|null, string|null}
*/
private function getDocBlock(string $class, string $property): array
{
$propertyHash = $class.'::'.$property;
if (isset($this->docBlocks[$propertyHash])) {
return $this->docBlocks[$propertyHash];
}
$ucFirstProperty = ucfirst($property);
if ([$docBlock, $declaringClass] = $this->getDocBlockFromProperty($class, $property)) {
$data = [$docBlock, self::PROPERTY, null, $declaringClass];
} elseif ([$docBlock, $_, $declaringClass] = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::ACCESSOR)) {
$data = [$docBlock, self::ACCESSOR, null, $declaringClass];
} elseif ([$docBlock, $prefix, $declaringClass] = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::MUTATOR)) {
$data = [$docBlock, self::MUTATOR, $prefix, $declaringClass];
} else {
$data = [null, null, null, null];
}
return $this->docBlocks[$propertyHash] = $data;
}
/**
* @return array{PhpDocNode, string}|null
*/
private function getDocBlockFromProperty(string $class, string $property): ?array
{
// Use a ReflectionProperty instead of $class to get the parent class if applicable
try {
$reflectionProperty = new \ReflectionProperty($class, $property);
} catch (\ReflectionException $e) {
return null;
}
if (null === $rawDocNode = $reflectionProperty->getDocComment() ?: null) {
return null;
}
$tokens = new TokenIterator($this->lexer->tokenize($rawDocNode));
$phpDocNode = $this->phpDocParser->parse($tokens);
$tokens->consumeTokenType(Lexer::TOKEN_END);
return [$phpDocNode, $reflectionProperty->class];
}
/**
* @return array{PhpDocNode, string, string}|null
*/
private function getDocBlockFromMethod(string $class, string $ucFirstProperty, int $type): ?array
{
$prefixes = self::ACCESSOR === $type ? $this->accessorPrefixes : $this->mutatorPrefixes;
$prefix = null;
foreach ($prefixes as $prefix) {
$methodName = $prefix.$ucFirstProperty;
try {
$reflectionMethod = new \ReflectionMethod($class, $methodName);
if ($reflectionMethod->isStatic()) {
continue;
}
if (
(self::ACCESSOR === $type && 0 === $reflectionMethod->getNumberOfRequiredParameters())
|| (self::MUTATOR === $type && $reflectionMethod->getNumberOfParameters() >= 1)
) {
break;
}
} catch (\ReflectionException $e) {
// Try the next prefix if the method doesn't exist
}
}
if (!isset($reflectionMethod)) {
return null;
}
if (null === $rawDocNode = $reflectionMethod->getDocComment() ?: null) {
return null;
}
$tokens = new TokenIterator($this->lexer->tokenize($rawDocNode));
$phpDocNode = $this->phpDocParser->parse($tokens);
$tokens->consumeTokenType(Lexer::TOKEN_END);
return [$phpDocNode, $prefix, $reflectionMethod->class];
}
}

View File

@ -0,0 +1,872 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyInfo\Extractor;
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyReadInfo;
use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyWriteInfo;
use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface;
use Symfony\Component\PropertyInfo\Type;
use Symfony\Component\String\Inflector\EnglishInflector;
use Symfony\Component\String\Inflector\InflectorInterface;
/**
* Extracts data using the reflection API.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*
* @final
*/
class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface, PropertyInitializableExtractorInterface, PropertyReadInfoExtractorInterface, PropertyWriteInfoExtractorInterface, ConstructorArgumentTypeExtractorInterface
{
/**
* @internal
*/
public static $defaultMutatorPrefixes = ['add', 'remove', 'set'];
/**
* @internal
*/
public static $defaultAccessorPrefixes = ['get', 'is', 'has', 'can'];
/**
* @internal
*/
public static $defaultArrayMutatorPrefixes = ['add', 'remove'];
public const ALLOW_PRIVATE = 1;
public const ALLOW_PROTECTED = 2;
public const ALLOW_PUBLIC = 4;
/** @var int Allow none of the magic methods */
public const DISALLOW_MAGIC_METHODS = 0;
/** @var int Allow magic __get methods */
public const ALLOW_MAGIC_GET = 1 << 0;
/** @var int Allow magic __set methods */
public const ALLOW_MAGIC_SET = 1 << 1;
/** @var int Allow magic __call methods */
public const ALLOW_MAGIC_CALL = 1 << 2;
private const MAP_TYPES = [
'integer' => Type::BUILTIN_TYPE_INT,
'boolean' => Type::BUILTIN_TYPE_BOOL,
'double' => Type::BUILTIN_TYPE_FLOAT,
];
private $mutatorPrefixes;
private $accessorPrefixes;
private $arrayMutatorPrefixes;
private $enableConstructorExtraction;
private $methodReflectionFlags;
private $magicMethodsFlags;
private $propertyReflectionFlags;
private $inflector;
private $arrayMutatorPrefixesFirst;
private $arrayMutatorPrefixesLast;
/**
* @param string[]|null $mutatorPrefixes
* @param string[]|null $accessorPrefixes
* @param string[]|null $arrayMutatorPrefixes
*/
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)
{
$this->mutatorPrefixes = $mutatorPrefixes ?? self::$defaultMutatorPrefixes;
$this->accessorPrefixes = $accessorPrefixes ?? self::$defaultAccessorPrefixes;
$this->arrayMutatorPrefixes = $arrayMutatorPrefixes ?? self::$defaultArrayMutatorPrefixes;
$this->enableConstructorExtraction = $enableConstructorExtraction;
$this->methodReflectionFlags = $this->getMethodsFlags($accessFlags);
$this->propertyReflectionFlags = $this->getPropertyFlags($accessFlags);
$this->magicMethodsFlags = $magicMethodsFlags;
$this->inflector = $inflector ?? new EnglishInflector();
$this->arrayMutatorPrefixesFirst = array_merge($this->arrayMutatorPrefixes, array_diff($this->mutatorPrefixes, $this->arrayMutatorPrefixes));
$this->arrayMutatorPrefixesLast = array_reverse($this->arrayMutatorPrefixesFirst);
}
/**
* {@inheritdoc}
*/
public function getProperties(string $class, array $context = []): ?array
{
try {
$reflectionClass = new \ReflectionClass($class);
} catch (\ReflectionException $e) {
return null;
}
$reflectionProperties = $reflectionClass->getProperties();
$properties = [];
foreach ($reflectionProperties as $reflectionProperty) {
if ($reflectionProperty->getModifiers() & $this->propertyReflectionFlags) {
$properties[$reflectionProperty->name] = $reflectionProperty->name;
}
}
foreach ($reflectionClass->getMethods($this->methodReflectionFlags) as $reflectionMethod) {
if ($reflectionMethod->isStatic()) {
continue;
}
$propertyName = $this->getPropertyName($reflectionMethod->name, $reflectionProperties);
if (!$propertyName || isset($properties[$propertyName])) {
continue;
}
if ($reflectionClass->hasProperty($lowerCasedPropertyName = lcfirst($propertyName)) || (!$reflectionClass->hasProperty($propertyName) && !preg_match('/^[A-Z]{2,}/', $propertyName))) {
$propertyName = $lowerCasedPropertyName;
}
$properties[$propertyName] = $propertyName;
}
return $properties ? array_values($properties) : null;
}
/**
* {@inheritdoc}
*/
public function getTypes(string $class, string $property, array $context = []): ?array
{
if ($fromMutator = $this->extractFromMutator($class, $property)) {
return $fromMutator;
}
if ($fromAccessor = $this->extractFromAccessor($class, $property)) {
return $fromAccessor;
}
if (
($context['enable_constructor_extraction'] ?? $this->enableConstructorExtraction) &&
$fromConstructor = $this->extractFromConstructor($class, $property)
) {
return $fromConstructor;
}
if ($fromPropertyDeclaration = $this->extractFromPropertyDeclaration($class, $property)) {
return $fromPropertyDeclaration;
}
return null;
}
/**
* {@inheritdoc}
*/
public function getTypesFromConstructor(string $class, string $property): ?array
{
try {
$reflection = new \ReflectionClass($class);
} catch (\ReflectionException $e) {
return null;
}
if (!$reflectionConstructor = $reflection->getConstructor()) {
return null;
}
if (!$reflectionParameter = $this->getReflectionParameterFromConstructor($property, $reflectionConstructor)) {
return null;
}
if (!$reflectionType = $reflectionParameter->getType()) {
return null;
}
if (!$types = $this->extractFromReflectionType($reflectionType, $reflectionConstructor->getDeclaringClass())) {
return null;
}
return $types;
}
private function getReflectionParameterFromConstructor(string $property, \ReflectionMethod $reflectionConstructor): ?\ReflectionParameter
{
$reflectionParameter = null;
foreach ($reflectionConstructor->getParameters() as $reflectionParameter) {
if ($reflectionParameter->getName() === $property) {
return $reflectionParameter;
}
}
return null;
}
/**
* {@inheritdoc}
*/
public function isReadable(string $class, string $property, array $context = []): ?bool
{
if ($this->isAllowedProperty($class, $property)) {
return true;
}
return null !== $this->getReadInfo($class, $property, $context);
}
/**
* {@inheritdoc}
*/
public function isWritable(string $class, string $property, array $context = []): ?bool
{
if ($this->isAllowedProperty($class, $property)) {
return true;
}
[$reflectionMethod] = $this->getMutatorMethod($class, $property);
return null !== $reflectionMethod;
}
/**
* {@inheritdoc}
*/
public function isInitializable(string $class, string $property, array $context = []): ?bool
{
try {
$reflectionClass = new \ReflectionClass($class);
} catch (\ReflectionException $e) {
return null;
}
if (!$reflectionClass->isInstantiable()) {
return false;
}
if ($constructor = $reflectionClass->getConstructor()) {
foreach ($constructor->getParameters() as $parameter) {
if ($property === $parameter->name) {
return true;
}
}
} elseif ($parentClass = $reflectionClass->getParentClass()) {
return $this->isInitializable($parentClass->getName(), $property);
}
return false;
}
/**
* {@inheritdoc}
*/
public function getReadInfo(string $class, string $property, array $context = []): ?PropertyReadInfo
{
try {
$reflClass = new \ReflectionClass($class);
} catch (\ReflectionException $e) {
return null;
}
$allowGetterSetter = $context['enable_getter_setter_extraction'] ?? false;
$magicMethods = $context['enable_magic_methods_extraction'] ?? $this->magicMethodsFlags;
$allowMagicCall = (bool) ($magicMethods & self::ALLOW_MAGIC_CALL);
$allowMagicGet = (bool) ($magicMethods & self::ALLOW_MAGIC_GET);
if (isset($context['enable_magic_call_extraction'])) {
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__);
$allowMagicCall = $context['enable_magic_call_extraction'] ?? false;
}
$hasProperty = $reflClass->hasProperty($property);
$camelProp = $this->camelize($property);
$getsetter = lcfirst($camelProp); // jQuery style, e.g. read: last(), write: last($item)
foreach ($this->accessorPrefixes as $prefix) {
$methodName = $prefix.$camelProp;
if ($reflClass->hasMethod($methodName) && $reflClass->getMethod($methodName)->getModifiers() & $this->methodReflectionFlags && !$reflClass->getMethod($methodName)->getNumberOfRequiredParameters()) {
$method = $reflClass->getMethod($methodName);
return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, $methodName, $this->getReadVisiblityForMethod($method), $method->isStatic(), false);
}
}
if ($allowGetterSetter && $reflClass->hasMethod($getsetter) && ($reflClass->getMethod($getsetter)->getModifiers() & $this->methodReflectionFlags)) {
$method = $reflClass->getMethod($getsetter);
return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, $getsetter, $this->getReadVisiblityForMethod($method), $method->isStatic(), false);
}
if ($allowMagicGet && $reflClass->hasMethod('__get') && ($reflClass->getMethod('__get')->getModifiers() & $this->methodReflectionFlags)) {
return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY, $property, PropertyReadInfo::VISIBILITY_PUBLIC, false, false);
}
if ($hasProperty && ($reflClass->getProperty($property)->getModifiers() & $this->propertyReflectionFlags)) {
$reflProperty = $reflClass->getProperty($property);
return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY, $property, $this->getReadVisiblityForProperty($reflProperty), $reflProperty->isStatic(), true);
}
if ($allowMagicCall && $reflClass->hasMethod('__call') && ($reflClass->getMethod('__call')->getModifiers() & $this->methodReflectionFlags)) {
return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, 'get'.$camelProp, PropertyReadInfo::VISIBILITY_PUBLIC, false, false);
}
return null;
}
/**
* {@inheritdoc}
*/
public function getWriteInfo(string $class, string $property, array $context = []): ?PropertyWriteInfo
{
try {
$reflClass = new \ReflectionClass($class);
} catch (\ReflectionException $e) {
return null;
}
$allowGetterSetter = $context['enable_getter_setter_extraction'] ?? false;
$magicMethods = $context['enable_magic_methods_extraction'] ?? $this->magicMethodsFlags;
$allowMagicCall = (bool) ($magicMethods & self::ALLOW_MAGIC_CALL);
$allowMagicSet = (bool) ($magicMethods & self::ALLOW_MAGIC_SET);
if (isset($context['enable_magic_call_extraction'])) {
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__);
$allowMagicCall = $context['enable_magic_call_extraction'] ?? false;
}
$allowConstruct = $context['enable_constructor_extraction'] ?? $this->enableConstructorExtraction;
$allowAdderRemover = $context['enable_adder_remover_extraction'] ?? true;
$camelized = $this->camelize($property);
$constructor = $reflClass->getConstructor();
$singulars = $this->inflector->singularize($camelized);
$errors = [];
if (null !== $constructor && $allowConstruct) {
foreach ($constructor->getParameters() as $parameter) {
if ($parameter->getName() === $property) {
return new PropertyWriteInfo(PropertyWriteInfo::TYPE_CONSTRUCTOR, $property);
}
}
}
[$adderAccessName, $removerAccessName, $adderAndRemoverErrors] = $this->findAdderAndRemover($reflClass, $singulars);
if ($allowAdderRemover && null !== $adderAccessName && null !== $removerAccessName) {
$adderMethod = $reflClass->getMethod($adderAccessName);
$removerMethod = $reflClass->getMethod($removerAccessName);
$mutator = new PropertyWriteInfo(PropertyWriteInfo::TYPE_ADDER_AND_REMOVER);
$mutator->setAdderInfo(new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $adderAccessName, $this->getWriteVisiblityForMethod($adderMethod), $adderMethod->isStatic()));
$mutator->setRemoverInfo(new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $removerAccessName, $this->getWriteVisiblityForMethod($removerMethod), $removerMethod->isStatic()));
return $mutator;
}
$errors[] = $adderAndRemoverErrors;
foreach ($this->mutatorPrefixes as $mutatorPrefix) {
$methodName = $mutatorPrefix.$camelized;
[$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, $methodName, 1);
if (!$accessible) {
$errors[] = $methodAccessibleErrors;
continue;
}
$method = $reflClass->getMethod($methodName);
if (!\in_array($mutatorPrefix, $this->arrayMutatorPrefixes, true)) {
return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $methodName, $this->getWriteVisiblityForMethod($method), $method->isStatic());
}
}
$getsetter = lcfirst($camelized);
if ($allowGetterSetter) {
[$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, $getsetter, 1);
if ($accessible) {
$method = $reflClass->getMethod($getsetter);
return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $getsetter, $this->getWriteVisiblityForMethod($method), $method->isStatic());
}
$errors[] = $methodAccessibleErrors;
}
if ($reflClass->hasProperty($property) && ($reflClass->getProperty($property)->getModifiers() & $this->propertyReflectionFlags)) {
$reflProperty = $reflClass->getProperty($property);
return new PropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY, $property, $this->getWriteVisiblityForProperty($reflProperty), $reflProperty->isStatic());
}
if ($allowMagicSet) {
[$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, '__set', 2);
if ($accessible) {
return new PropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY, $property, PropertyWriteInfo::VISIBILITY_PUBLIC, false);
}
$errors[] = $methodAccessibleErrors;
}
if ($allowMagicCall) {
[$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, '__call', 2);
if ($accessible) {
return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, 'set'.$camelized, PropertyWriteInfo::VISIBILITY_PUBLIC, false);
}
$errors[] = $methodAccessibleErrors;
}
if (!$allowAdderRemover && null !== $adderAccessName && null !== $removerAccessName) {
$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])
)];
}
$noneProperty = new PropertyWriteInfo();
$noneProperty->setErrors(array_merge([], ...$errors));
return $noneProperty;
}
/**
* @return Type[]|null
*/
private function extractFromMutator(string $class, string $property): ?array
{
[$reflectionMethod, $prefix] = $this->getMutatorMethod($class, $property);
if (null === $reflectionMethod) {
return null;
}
$reflectionParameters = $reflectionMethod->getParameters();
$reflectionParameter = $reflectionParameters[0];
if (!$reflectionType = $reflectionParameter->getType()) {
return null;
}
$type = $this->extractFromReflectionType($reflectionType, $reflectionMethod->getDeclaringClass());
if (1 === \count($type) && \in_array($prefix, $this->arrayMutatorPrefixes)) {
$type = [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), $type[0])];
}
return $type;
}
/**
* Tries to extract type information from accessors.
*
* @return Type[]|null
*/
private function extractFromAccessor(string $class, string $property): ?array
{
[$reflectionMethod, $prefix] = $this->getAccessorMethod($class, $property);
if (null === $reflectionMethod) {
return null;
}
if ($reflectionType = $reflectionMethod->getReturnType()) {
return $this->extractFromReflectionType($reflectionType, $reflectionMethod->getDeclaringClass());
}
if (\in_array($prefix, ['is', 'can', 'has'])) {
return [new Type(Type::BUILTIN_TYPE_BOOL)];
}
return null;
}
/**
* Tries to extract type information from constructor.
*
* @return Type[]|null
*/
private function extractFromConstructor(string $class, string $property): ?array
{
try {
$reflectionClass = new \ReflectionClass($class);
} catch (\ReflectionException $e) {
return null;
}
$constructor = $reflectionClass->getConstructor();
if (!$constructor) {
return null;
}
foreach ($constructor->getParameters() as $parameter) {
if ($property !== $parameter->name) {
continue;
}
$reflectionType = $parameter->getType();
return $reflectionType ? $this->extractFromReflectionType($reflectionType, $constructor->getDeclaringClass()) : null;
}
if ($parentClass = $reflectionClass->getParentClass()) {
return $this->extractFromConstructor($parentClass->getName(), $property);
}
return null;
}
private function extractFromPropertyDeclaration(string $class, string $property): ?array
{
try {
$reflectionClass = new \ReflectionClass($class);
if (\PHP_VERSION_ID >= 70400) {
$reflectionProperty = $reflectionClass->getProperty($property);
$reflectionPropertyType = $reflectionProperty->getType();
if (null !== $reflectionPropertyType && $types = $this->extractFromReflectionType($reflectionPropertyType, $reflectionProperty->getDeclaringClass())) {
return $types;
}
}
} catch (\ReflectionException $e) {
return null;
}
$defaultValue = $reflectionClass->getDefaultProperties()[$property] ?? null;
if (null === $defaultValue) {
return null;
}
$type = \gettype($defaultValue);
$type = static::MAP_TYPES[$type] ?? $type;
return [new Type($type, $this->isNullableProperty($class, $property), null, Type::BUILTIN_TYPE_ARRAY === $type)];
}
private function extractFromReflectionType(\ReflectionType $reflectionType, \ReflectionClass $declaringClass): array
{
$types = [];
$nullable = $reflectionType->allowsNull();
foreach (($reflectionType instanceof \ReflectionUnionType || $reflectionType instanceof \ReflectionIntersectionType) ? $reflectionType->getTypes() : [$reflectionType] as $type) {
$phpTypeOrClass = $type->getName();
if ('null' === $phpTypeOrClass || 'mixed' === $phpTypeOrClass || 'never' === $phpTypeOrClass) {
continue;
}
if (Type::BUILTIN_TYPE_ARRAY === $phpTypeOrClass) {
$types[] = new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true);
} elseif ('void' === $phpTypeOrClass) {
$types[] = new Type(Type::BUILTIN_TYPE_NULL, $nullable);
} elseif ($type->isBuiltin()) {
$types[] = new Type($phpTypeOrClass, $nullable);
} else {
$types[] = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $this->resolveTypeName($phpTypeOrClass, $declaringClass));
}
}
return $types;
}
private function resolveTypeName(string $name, \ReflectionClass $declaringClass): string
{
if ('self' === $lcName = strtolower($name)) {
return $declaringClass->name;
}
if ('parent' === $lcName && $parent = $declaringClass->getParentClass()) {
return $parent->name;
}
return $name;
}
private function isNullableProperty(string $class, string $property): bool
{
try {
$reflectionProperty = new \ReflectionProperty($class, $property);
if (\PHP_VERSION_ID >= 70400) {
$reflectionPropertyType = $reflectionProperty->getType();
return null !== $reflectionPropertyType && $reflectionPropertyType->allowsNull();
}
return false;
} catch (\ReflectionException $e) {
// Return false if the property doesn't exist
}
return false;
}
private function isAllowedProperty(string $class, string $property): bool
{
try {
$reflectionProperty = new \ReflectionProperty($class, $property);
return (bool) ($reflectionProperty->getModifiers() & $this->propertyReflectionFlags);
} catch (\ReflectionException $e) {
// Return false if the property doesn't exist
}
return false;
}
/**
* Gets the accessor method.
*
* Returns an array with a the instance of \ReflectionMethod as first key
* and the prefix of the method as second or null if not found.
*/
private function getAccessorMethod(string $class, string $property): ?array
{
$ucProperty = ucfirst($property);
foreach ($this->accessorPrefixes as $prefix) {
try {
$reflectionMethod = new \ReflectionMethod($class, $prefix.$ucProperty);
if ($reflectionMethod->isStatic()) {
continue;
}
if (0 === $reflectionMethod->getNumberOfRequiredParameters()) {
return [$reflectionMethod, $prefix];
}
} catch (\ReflectionException $e) {
// Return null if the property doesn't exist
}
}
return null;
}
/**
* Returns an array with a the instance of \ReflectionMethod as first key
* and the prefix of the method as second or null if not found.
*/
private function getMutatorMethod(string $class, string $property): ?array
{
$ucProperty = ucfirst($property);
$ucSingulars = $this->inflector->singularize($ucProperty);
$mutatorPrefixes = \in_array($ucProperty, $ucSingulars, true) ? $this->arrayMutatorPrefixesLast : $this->arrayMutatorPrefixesFirst;
foreach ($mutatorPrefixes as $prefix) {
$names = [$ucProperty];
if (\in_array($prefix, $this->arrayMutatorPrefixes)) {
$names = array_merge($names, $ucSingulars);
}
foreach ($names as $name) {
try {
$reflectionMethod = new \ReflectionMethod($class, $prefix.$name);
if ($reflectionMethod->isStatic()) {
continue;
}
// Parameter can be optional to allow things like: method(array $foo = null)
if ($reflectionMethod->getNumberOfParameters() >= 1) {
return [$reflectionMethod, $prefix];
}
} catch (\ReflectionException $e) {
// Try the next prefix if the method doesn't exist
}
}
}
return null;
}
private function getPropertyName(string $methodName, array $reflectionProperties): ?string
{
$pattern = implode('|', array_merge($this->accessorPrefixes, $this->mutatorPrefixes));
if ('' !== $pattern && preg_match('/^('.$pattern.')(.+)$/i', $methodName, $matches)) {
if (!\in_array($matches[1], $this->arrayMutatorPrefixes)) {
return $matches[2];
}
foreach ($reflectionProperties as $reflectionProperty) {
foreach ($this->inflector->singularize($reflectionProperty->name) as $name) {
if (strtolower($name) === strtolower($matches[2])) {
return $reflectionProperty->name;
}
}
}
return $matches[2];
}
return null;
}
/**
* Searches for add and remove methods.
*
* @param \ReflectionClass $reflClass The reflection class for the given object
* @param array $singulars The singular form of the property name or null
*
* @return array An array containing the adder and remover when found and errors
*/
private function findAdderAndRemover(\ReflectionClass $reflClass, array $singulars): array
{
if (!\is_array($this->arrayMutatorPrefixes) && 2 !== \count($this->arrayMutatorPrefixes)) {
return [null, null, []];
}
[$addPrefix, $removePrefix] = $this->arrayMutatorPrefixes;
$errors = [];
foreach ($singulars as $singular) {
$addMethod = $addPrefix.$singular;
$removeMethod = $removePrefix.$singular;
[$addMethodFound, $addMethodAccessibleErrors] = $this->isMethodAccessible($reflClass, $addMethod, 1);
[$removeMethodFound, $removeMethodAccessibleErrors] = $this->isMethodAccessible($reflClass, $removeMethod, 1);
$errors[] = $addMethodAccessibleErrors;
$errors[] = $removeMethodAccessibleErrors;
if ($addMethodFound && $removeMethodFound) {
return [$addMethod, $removeMethod, []];
}
if ($addMethodFound && !$removeMethodFound) {
$errors[] = [sprintf('The add method "%s" in class "%s" was found, but the corresponding remove method "%s" was not found', $addMethod, $reflClass->getName(), $removeMethod)];
} elseif (!$addMethodFound && $removeMethodFound) {
$errors[] = [sprintf('The remove method "%s" in class "%s" was found, but the corresponding add method "%s" was not found', $removeMethod, $reflClass->getName(), $addMethod)];
}
}
return [null, null, array_merge([], ...$errors)];
}
/**
* Returns whether a method is public and has the number of required parameters and errors.
*/
private function isMethodAccessible(\ReflectionClass $class, string $methodName, int $parameters): array
{
$errors = [];
if ($class->hasMethod($methodName)) {
$method = $class->getMethod($methodName);
if (\ReflectionMethod::IS_PUBLIC === $this->methodReflectionFlags && !$method->isPublic()) {
$errors[] = sprintf('The method "%s" in class "%s" was found but does not have public access.', $methodName, $class->getName());
} elseif ($method->getNumberOfRequiredParameters() > $parameters || $method->getNumberOfParameters() < $parameters) {
$errors[] = sprintf('The method "%s" in class "%s" requires %d arguments, but should accept only %d.', $methodName, $class->getName(), $method->getNumberOfRequiredParameters(), $parameters);
} else {
return [true, $errors];
}
}
return [false, $errors];
}
/**
* Camelizes a given string.
*/
private function camelize(string $string): string
{
return str_replace(' ', '', ucwords(str_replace('_', ' ', $string)));
}
/**
* Return allowed reflection method flags.
*/
private function getMethodsFlags(int $accessFlags): int
{
$methodFlags = 0;
if ($accessFlags & self::ALLOW_PUBLIC) {
$methodFlags |= \ReflectionMethod::IS_PUBLIC;
}
if ($accessFlags & self::ALLOW_PRIVATE) {
$methodFlags |= \ReflectionMethod::IS_PRIVATE;
}
if ($accessFlags & self::ALLOW_PROTECTED) {
$methodFlags |= \ReflectionMethod::IS_PROTECTED;
}
return $methodFlags;
}
/**
* Return allowed reflection property flags.
*/
private function getPropertyFlags(int $accessFlags): int
{
$propertyFlags = 0;
if ($accessFlags & self::ALLOW_PUBLIC) {
$propertyFlags |= \ReflectionProperty::IS_PUBLIC;
}
if ($accessFlags & self::ALLOW_PRIVATE) {
$propertyFlags |= \ReflectionProperty::IS_PRIVATE;
}
if ($accessFlags & self::ALLOW_PROTECTED) {
$propertyFlags |= \ReflectionProperty::IS_PROTECTED;
}
return $propertyFlags;
}
private function getReadVisiblityForProperty(\ReflectionProperty $reflectionProperty): string
{
if ($reflectionProperty->isPrivate()) {
return PropertyReadInfo::VISIBILITY_PRIVATE;
}
if ($reflectionProperty->isProtected()) {
return PropertyReadInfo::VISIBILITY_PROTECTED;
}
return PropertyReadInfo::VISIBILITY_PUBLIC;
}
private function getReadVisiblityForMethod(\ReflectionMethod $reflectionMethod): string
{
if ($reflectionMethod->isPrivate()) {
return PropertyReadInfo::VISIBILITY_PRIVATE;
}
if ($reflectionMethod->isProtected()) {
return PropertyReadInfo::VISIBILITY_PROTECTED;
}
return PropertyReadInfo::VISIBILITY_PUBLIC;
}
private function getWriteVisiblityForProperty(\ReflectionProperty $reflectionProperty): string
{
if ($reflectionProperty->isPrivate()) {
return PropertyWriteInfo::VISIBILITY_PRIVATE;
}
if ($reflectionProperty->isProtected()) {
return PropertyWriteInfo::VISIBILITY_PROTECTED;
}
return PropertyWriteInfo::VISIBILITY_PUBLIC;
}
private function getWriteVisiblityForMethod(\ReflectionMethod $reflectionMethod): string
{
if ($reflectionMethod->isPrivate()) {
return PropertyWriteInfo::VISIBILITY_PRIVATE;
}
if ($reflectionMethod->isProtected()) {
return PropertyWriteInfo::VISIBILITY_PROTECTED;
}
return PropertyWriteInfo::VISIBILITY_PUBLIC;
}
}

View File

@ -0,0 +1,58 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyInfo\Extractor;
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
/**
* Lists available properties using Symfony Serializer Component metadata.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*
* @final
*/
class SerializerExtractor implements PropertyListExtractorInterface
{
private $classMetadataFactory;
public function __construct(ClassMetadataFactoryInterface $classMetadataFactory)
{
$this->classMetadataFactory = $classMetadataFactory;
}
/**
* {@inheritdoc}
*/
public function getProperties(string $class, array $context = []): ?array
{
if (!\array_key_exists('serializer_groups', $context) || (null !== $context['serializer_groups'] && !\is_array($context['serializer_groups']))) {
return null;
}
if (!$this->classMetadataFactory->getMetadataFor($class)) {
return null;
}
$properties = [];
$serializerClassMetadata = $this->classMetadataFactory->getMetadataFor($class);
foreach ($serializerClassMetadata->getAttributesMetadata() as $serializerAttributeMetadata) {
$ignored = method_exists($serializerAttributeMetadata, 'isIgnored') && $serializerAttributeMetadata->isIgnored();
if (!$ignored && (null === $context['serializer_groups'] || array_intersect($context['serializer_groups'], $serializerAttributeMetadata->getGroups()))) {
$properties[] = $serializerAttributeMetadata->getName();
}
}
return $properties;
}
}

19
vendor/symfony/property-info/LICENSE vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2015-2022 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,65 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyInfo\PhpStan;
/**
* NameScope class adapted from PHPStan code.
*
* @copyright Copyright (c) 2016, PHPStan https://github.com/phpstan/phpstan-src
* @copyright Copyright (c) 2016, Ondřej Mirtes
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*
* @internal
*/
final class NameScope
{
private $calledClassName;
private $namespace;
/** @var array<string, string> alias(string) => fullName(string) */
private $uses;
public function __construct(string $calledClassName, string $namespace, array $uses = [])
{
$this->calledClassName = $calledClassName;
$this->namespace = $namespace;
$this->uses = $uses;
}
public function resolveStringName(string $name): string
{
if (0 === strpos($name, '\\')) {
return ltrim($name, '\\');
}
$nameParts = explode('\\', $name);
$firstNamePart = $nameParts[0];
if (isset($this->uses[$firstNamePart])) {
if (1 === \count($nameParts)) {
return $this->uses[$firstNamePart];
}
array_shift($nameParts);
return sprintf('%s\\%s', $this->uses[$firstNamePart], implode('\\', $nameParts));
}
if (null !== $this->namespace) {
return sprintf('%s\\%s', $this->namespace, $name);
}
return $name;
}
public function resolveRootClass(): string
{
return $this->resolveStringName($this->calledClassName);
}
}

View File

@ -0,0 +1,70 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyInfo\PhpStan;
use phpDocumentor\Reflection\Types\ContextFactory;
/**
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*
* @internal
*/
final class NameScopeFactory
{
public function create(string $calledClassName, string $declaringClassName = null): NameScope
{
$declaringClassName = $declaringClassName ?? $calledClassName;
$path = explode('\\', $calledClassName);
$calledClassName = array_pop($path);
$declaringReflection = new \ReflectionClass($declaringClassName);
[$declaringNamespace, $declaringUses] = $this->extractFromFullClassName($declaringReflection);
$declaringUses = array_merge($declaringUses, $this->collectUses($declaringReflection));
return new NameScope($calledClassName, $declaringNamespace, $declaringUses);
}
private function collectUses(\ReflectionClass $reflection): array
{
$uses = [$this->extractFromFullClassName($reflection)[1]];
foreach ($reflection->getTraits() as $traitReflection) {
$uses[] = $this->extractFromFullClassName($traitReflection)[1];
}
if (false !== $parentClass = $reflection->getParentClass()) {
$uses[] = $this->collectUses($parentClass);
}
return $uses ? array_merge(...$uses) : [];
}
private function extractFromFullClassName(\ReflectionClass $reflection): array
{
$namespace = trim($reflection->getNamespaceName(), '\\');
$fileName = $reflection->getFileName();
if (\is_string($fileName) && is_file($fileName)) {
if (false === $contents = file_get_contents($fileName)) {
throw new \RuntimeException(sprintf('Unable to read file "%s".', $fileName));
}
$factory = new ContextFactory();
$context = $factory->createForNamespace($namespace, $contents);
return [$namespace, $context->getNamespaceAliases()];
}
return [$namespace, []];
}
}

View File

@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyInfo;
/**
* Guesses if the property can be accessed or mutated.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
interface PropertyAccessExtractorInterface
{
/**
* Is the property readable?
*
* @return bool|null
*/
public function isReadable(string $class, string $property, array $context = []);
/**
* Is the property writable?
*
* @return bool|null
*/
public function isWritable(string $class, string $property, array $context = []);
}

View File

@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyInfo;
/**
* Guesses the property's human readable description.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
interface PropertyDescriptionExtractorInterface
{
/**
* Gets the short description of the property.
*
* @return string|null
*/
public function getShortDescription(string $class, string $property, array $context = []);
/**
* Gets the long description of the property.
*
* @return string|null
*/
public function getLongDescription(string $class, string $property, array $context = []);
}

View File

@ -0,0 +1,124 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyInfo;
use Psr\Cache\CacheItemPoolInterface;
/**
* Adds a PSR-6 cache layer on top of an extractor.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*
* @final
*/
class PropertyInfoCacheExtractor implements PropertyInfoExtractorInterface, PropertyInitializableExtractorInterface
{
private $propertyInfoExtractor;
private $cacheItemPool;
private $arrayCache = [];
public function __construct(PropertyInfoExtractorInterface $propertyInfoExtractor, CacheItemPoolInterface $cacheItemPool)
{
$this->propertyInfoExtractor = $propertyInfoExtractor;
$this->cacheItemPool = $cacheItemPool;
}
/**
* {@inheritdoc}
*/
public function isReadable(string $class, string $property, array $context = []): ?bool
{
return $this->extract('isReadable', [$class, $property, $context]);
}
/**
* {@inheritdoc}
*/
public function isWritable(string $class, string $property, array $context = []): ?bool
{
return $this->extract('isWritable', [$class, $property, $context]);
}
/**
* {@inheritdoc}
*/
public function getShortDescription(string $class, string $property, array $context = []): ?string
{
return $this->extract('getShortDescription', [$class, $property, $context]);
}
/**
* {@inheritdoc}
*/
public function getLongDescription(string $class, string $property, array $context = []): ?string
{
return $this->extract('getLongDescription', [$class, $property, $context]);
}
/**
* {@inheritdoc}
*/
public function getProperties(string $class, array $context = []): ?array
{
return $this->extract('getProperties', [$class, $context]);
}
/**
* {@inheritdoc}
*/
public function getTypes(string $class, string $property, array $context = []): ?array
{
return $this->extract('getTypes', [$class, $property, $context]);
}
/**
* {@inheritdoc}
*/
public function isInitializable(string $class, string $property, array $context = []): ?bool
{
return $this->extract('isInitializable', [$class, $property, $context]);
}
/**
* Retrieves the cached data if applicable or delegates to the decorated extractor.
*
* @return mixed
*/
private function extract(string $method, array $arguments)
{
try {
$serializedArguments = serialize($arguments);
} catch (\Exception $exception) {
// If arguments are not serializable, skip the cache
return $this->propertyInfoExtractor->{$method}(...$arguments);
}
// Calling rawurlencode escapes special characters not allowed in PSR-6's keys
$key = rawurlencode($method.'.'.$serializedArguments);
if (\array_key_exists($key, $this->arrayCache)) {
return $this->arrayCache[$key];
}
$item = $this->cacheItemPool->getItem($key);
if ($item->isHit()) {
return $this->arrayCache[$key] = $item->get();
}
$value = $this->propertyInfoExtractor->{$method}(...$arguments);
$item->set($value);
$this->cacheItemPool->save($item);
return $this->arrayCache[$key] = $value;
}
}

View File

@ -0,0 +1,119 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyInfo;
/**
* Default {@see PropertyInfoExtractorInterface} implementation.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*
* @final
*/
class PropertyInfoExtractor implements PropertyInfoExtractorInterface, PropertyInitializableExtractorInterface
{
private $listExtractors;
private $typeExtractors;
private $descriptionExtractors;
private $accessExtractors;
private $initializableExtractors;
/**
* @param iterable<mixed, PropertyListExtractorInterface> $listExtractors
* @param iterable<mixed, PropertyTypeExtractorInterface> $typeExtractors
* @param iterable<mixed, PropertyDescriptionExtractorInterface> $descriptionExtractors
* @param iterable<mixed, PropertyAccessExtractorInterface> $accessExtractors
* @param iterable<mixed, PropertyInitializableExtractorInterface> $initializableExtractors
*/
public function __construct(iterable $listExtractors = [], iterable $typeExtractors = [], iterable $descriptionExtractors = [], iterable $accessExtractors = [], iterable $initializableExtractors = [])
{
$this->listExtractors = $listExtractors;
$this->typeExtractors = $typeExtractors;
$this->descriptionExtractors = $descriptionExtractors;
$this->accessExtractors = $accessExtractors;
$this->initializableExtractors = $initializableExtractors;
}
/**
* {@inheritdoc}
*/
public function getProperties(string $class, array $context = []): ?array
{
return $this->extract($this->listExtractors, 'getProperties', [$class, $context]);
}
/**
* {@inheritdoc}
*/
public function getShortDescription(string $class, string $property, array $context = []): ?string
{
return $this->extract($this->descriptionExtractors, 'getShortDescription', [$class, $property, $context]);
}
/**
* {@inheritdoc}
*/
public function getLongDescription(string $class, string $property, array $context = []): ?string
{
return $this->extract($this->descriptionExtractors, 'getLongDescription', [$class, $property, $context]);
}
/**
* {@inheritdoc}
*/
public function getTypes(string $class, string $property, array $context = []): ?array
{
return $this->extract($this->typeExtractors, 'getTypes', [$class, $property, $context]);
}
/**
* {@inheritdoc}
*/
public function isReadable(string $class, string $property, array $context = []): ?bool
{
return $this->extract($this->accessExtractors, 'isReadable', [$class, $property, $context]);
}
/**
* {@inheritdoc}
*/
public function isWritable(string $class, string $property, array $context = []): ?bool
{
return $this->extract($this->accessExtractors, 'isWritable', [$class, $property, $context]);
}
/**
* {@inheritdoc}
*/
public function isInitializable(string $class, string $property, array $context = []): ?bool
{
return $this->extract($this->initializableExtractors, 'isInitializable', [$class, $property, $context]);
}
/**
* Iterates over registered extractors and return the first value found.
*
* @param iterable<mixed, object> $extractors
* @param list<mixed> $arguments
*
* @return mixed
*/
private function extract(iterable $extractors, string $method, array $arguments)
{
foreach ($extractors as $extractor) {
if (null !== $value = $extractor->{$method}(...$arguments)) {
return $value;
}
}
return null;
}
}

View File

@ -0,0 +1,23 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyInfo;
/**
* Gets info about PHP class properties.
*
* A convenient interface inheriting all specific info interfaces.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
interface PropertyInfoExtractorInterface extends PropertyTypeExtractorInterface, PropertyDescriptionExtractorInterface, PropertyAccessExtractorInterface, PropertyListExtractorInterface
{
}

View File

@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyInfo;
/**
* Guesses if the property can be initialized through the constructor.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
interface PropertyInitializableExtractorInterface
{
/**
* Is the property initializable? Returns true if a constructor's parameter matches the given property name.
*/
public function isInitializable(string $class, string $property, array $context = []): ?bool;
}

View File

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyInfo;
/**
* Extracts the list of properties available for the given class.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
interface PropertyListExtractorInterface
{
/**
* Gets the list of properties available for the given class.
*
* @return string[]|null
*/
public function getProperties(string $class, array $context = []);
}

View File

@ -0,0 +1,82 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyInfo;
/**
* The property read info tells how a property can be read.
*
* @author Joel Wurtz <jwurtz@jolicode.com>
*
* @internal
*/
final class PropertyReadInfo
{
public const TYPE_METHOD = 'method';
public const TYPE_PROPERTY = 'property';
public const VISIBILITY_PUBLIC = 'public';
public const VISIBILITY_PROTECTED = 'protected';
public const VISIBILITY_PRIVATE = 'private';
private $type;
private $name;
private $visibility;
private $static;
private $byRef;
public function __construct(string $type, string $name, string $visibility, bool $static, bool $byRef)
{
$this->type = $type;
$this->name = $name;
$this->visibility = $visibility;
$this->static = $static;
$this->byRef = $byRef;
}
/**
* Get type of access.
*/
public function getType(): string
{
return $this->type;
}
/**
* Get name of the access, which can be a method name or a property name, depending on the type.
*/
public function getName(): string
{
return $this->name;
}
public function getVisibility(): string
{
return $this->visibility;
}
public function isStatic(): bool
{
return $this->static;
}
/**
* Whether this accessor can be accessed by reference.
*/
public function canBeReference(): bool
{
return $this->byRef;
}
}

View File

@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyInfo;
/**
* Extract read information for the property of a class.
*
* @author Joel Wurtz <jwurtz@jolicode.com>
*/
interface PropertyReadInfoExtractorInterface
{
/**
* Get read information object for a given property of a class.
*/
public function getReadInfo(string $class, string $property, array $context = []): ?PropertyReadInfo;
}

View File

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyInfo;
/**
* Type Extractor Interface.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
interface PropertyTypeExtractorInterface
{
/**
* Gets types of a property.
*
* @return Type[]|null
*/
public function getTypes(string $class, string $property, array $context = []);
}

View File

@ -0,0 +1,123 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyInfo;
/**
* The write mutator defines how a property can be written.
*
* @author Joel Wurtz <jwurtz@jolicode.com>
*
* @internal
*/
final class PropertyWriteInfo
{
public const TYPE_NONE = 'none';
public const TYPE_METHOD = 'method';
public const TYPE_PROPERTY = 'property';
public const TYPE_ADDER_AND_REMOVER = 'adder_and_remover';
public const TYPE_CONSTRUCTOR = 'constructor';
public const VISIBILITY_PUBLIC = 'public';
public const VISIBILITY_PROTECTED = 'protected';
public const VISIBILITY_PRIVATE = 'private';
private $type;
private $name;
private $visibility;
private $static;
private $adderInfo;
private $removerInfo;
private $errors = [];
public function __construct(string $type = self::TYPE_NONE, string $name = null, string $visibility = null, bool $static = null)
{
$this->type = $type;
$this->name = $name;
$this->visibility = $visibility;
$this->static = $static;
}
public function getType(): string
{
return $this->type;
}
public function getName(): string
{
if (null === $this->name) {
throw new \LogicException("Calling getName() when having a mutator of type {$this->type} is not tolerated.");
}
return $this->name;
}
public function setAdderInfo(self $adderInfo): void
{
$this->adderInfo = $adderInfo;
}
public function getAdderInfo(): self
{
if (null === $this->adderInfo) {
throw new \LogicException("Calling getAdderInfo() when having a mutator of type {$this->type} is not tolerated.");
}
return $this->adderInfo;
}
public function setRemoverInfo(self $removerInfo): void
{
$this->removerInfo = $removerInfo;
}
public function getRemoverInfo(): self
{
if (null === $this->removerInfo) {
throw new \LogicException("Calling getRemoverInfo() when having a mutator of type {$this->type} is not tolerated.");
}
return $this->removerInfo;
}
public function getVisibility(): string
{
if (null === $this->visibility) {
throw new \LogicException("Calling getVisibility() when having a mutator of type {$this->type} is not tolerated.");
}
return $this->visibility;
}
public function isStatic(): bool
{
if (null === $this->static) {
throw new \LogicException("Calling isStatic() when having a mutator of type {$this->type} is not tolerated.");
}
return $this->static;
}
public function setErrors(array $errors): void
{
$this->errors = $errors;
}
public function getErrors(): array
{
return $this->errors;
}
public function hasErrors(): bool
{
return (bool) \count($this->errors);
}
}

View File

@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyInfo;
/**
* Extract write information for the property of a class.
*
* @author Joel Wurtz <jwurtz@jolicode.com>
*/
interface PropertyWriteInfoExtractorInterface
{
/**
* Get write information object for a given property of a class.
*/
public function getWriteInfo(string $class, string $property, array $context = []): ?PropertyWriteInfo;
}

14
vendor/symfony/property-info/README.md vendored Normal file
View File

@ -0,0 +1,14 @@
PropertyInfo Component
======================
The PropertyInfo component extracts information about PHP class' properties
using metadata of popular sources.
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/property_info.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)

206
vendor/symfony/property-info/Type.php vendored Normal file
View File

@ -0,0 +1,206 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyInfo;
/**
* Type value object (immutable).
*
* @author Kévin Dunglas <dunglas@gmail.com>
*
* @final
*/
class Type
{
public const BUILTIN_TYPE_INT = 'int';
public const BUILTIN_TYPE_FLOAT = 'float';
public const BUILTIN_TYPE_STRING = 'string';
public const BUILTIN_TYPE_BOOL = 'bool';
public const BUILTIN_TYPE_TRUE = 'true';
public const BUILTIN_TYPE_FALSE = 'false';
public const BUILTIN_TYPE_RESOURCE = 'resource';
public const BUILTIN_TYPE_OBJECT = 'object';
public const BUILTIN_TYPE_ARRAY = 'array';
public const BUILTIN_TYPE_NULL = 'null';
public const BUILTIN_TYPE_CALLABLE = 'callable';
public const BUILTIN_TYPE_ITERABLE = 'iterable';
/**
* List of PHP builtin types.
*
* @var string[]
*/
public static $builtinTypes = [
self::BUILTIN_TYPE_INT,
self::BUILTIN_TYPE_FLOAT,
self::BUILTIN_TYPE_STRING,
self::BUILTIN_TYPE_BOOL,
self::BUILTIN_TYPE_TRUE,
self::BUILTIN_TYPE_FALSE,
self::BUILTIN_TYPE_RESOURCE,
self::BUILTIN_TYPE_OBJECT,
self::BUILTIN_TYPE_ARRAY,
self::BUILTIN_TYPE_CALLABLE,
self::BUILTIN_TYPE_NULL,
self::BUILTIN_TYPE_ITERABLE,
];
/**
* List of PHP builtin collection types.
*
* @var string[]
*/
public static $builtinCollectionTypes = [
self::BUILTIN_TYPE_ARRAY,
self::BUILTIN_TYPE_ITERABLE,
];
private $builtinType;
private $nullable;
private $class;
private $collection;
private $collectionKeyType;
private $collectionValueType;
/**
* @param Type[]|Type|null $collectionKeyType
* @param Type[]|Type|null $collectionValueType
*
* @throws \InvalidArgumentException
*/
public function __construct(string $builtinType, bool $nullable = false, string $class = null, bool $collection = false, $collectionKeyType = null, $collectionValueType = null)
{
if (!\in_array($builtinType, self::$builtinTypes)) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid PHP type.', $builtinType));
}
$this->builtinType = $builtinType;
$this->nullable = $nullable;
$this->class = $class;
$this->collection = $collection;
$this->collectionKeyType = $this->validateCollectionArgument($collectionKeyType, 5, '$collectionKeyType') ?? [];
$this->collectionValueType = $this->validateCollectionArgument($collectionValueType, 6, '$collectionValueType') ?? [];
}
private function validateCollectionArgument($collectionArgument, int $argumentIndex, string $argumentName): ?array
{
if (null === $collectionArgument) {
return null;
}
if (!\is_array($collectionArgument) && !$collectionArgument instanceof self) {
throw new \TypeError(sprintf('"%s()": Argument #%d (%s) must be of type "%s[]", "%s" or "null", "%s" given.', __METHOD__, $argumentIndex, $argumentName, self::class, self::class, get_debug_type($collectionArgument)));
}
if (\is_array($collectionArgument)) {
foreach ($collectionArgument as $type) {
if (!$type instanceof self) {
throw new \TypeError(sprintf('"%s()": Argument #%d (%s) must be of type "%s[]", "%s" or "null", array value "%s" given.', __METHOD__, $argumentIndex, $argumentName, self::class, self::class, get_debug_type($collectionArgument)));
}
}
return $collectionArgument;
}
return [$collectionArgument];
}
/**
* Gets built-in type.
*
* Can be bool, int, float, string, array, object, resource, null, callback or iterable.
*/
public function getBuiltinType(): string
{
return $this->builtinType;
}
public function isNullable(): bool
{
return $this->nullable;
}
/**
* Gets the class name.
*
* Only applicable if the built-in type is object.
*/
public function getClassName(): ?string
{
return $this->class;
}
public function isCollection(): bool
{
return $this->collection;
}
/**
* Gets collection key type.
*
* Only applicable for a collection type.
*
* @deprecated since Symfony 5.3, use "getCollectionKeyTypes()" instead
*/
public function getCollectionKeyType(): ?self
{
trigger_deprecation('symfony/property-info', '5.3', 'The "%s()" method is deprecated, use "getCollectionKeyTypes()" instead.', __METHOD__);
$type = $this->getCollectionKeyTypes();
if (0 === \count($type)) {
return null;
}
if (\is_array($type)) {
[$type] = $type;
}
return $type;
}
/**
* Gets collection key types.
*
* Only applicable for a collection type.
*
* @return Type[]
*/
public function getCollectionKeyTypes(): array
{
return $this->collectionKeyType;
}
/**
* Gets collection value type.
*
* Only applicable for a collection type.
*
* @deprecated since Symfony 5.3, use "getCollectionValueTypes()" instead
*/
public function getCollectionValueType(): ?self
{
trigger_deprecation('symfony/property-info', '5.3', 'The "%s()" method is deprecated, use "getCollectionValueTypes()" instead.', __METHOD__);
return $this->getCollectionValueTypes()[0] ?? null;
}
/**
* Gets collection value types.
*
* Only applicable for a collection type.
*
* @return Type[]
*/
public function getCollectionValueTypes(): array
{
return $this->collectionValueType;
}
}

View File

@ -0,0 +1,192 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyInfo\Util;
use phpDocumentor\Reflection\PseudoTypes\List_;
use phpDocumentor\Reflection\Type as DocType;
use phpDocumentor\Reflection\Types\Array_;
use phpDocumentor\Reflection\Types\Collection;
use phpDocumentor\Reflection\Types\Compound;
use phpDocumentor\Reflection\Types\Null_;
use phpDocumentor\Reflection\Types\Nullable;
use Symfony\Component\PropertyInfo\Type;
// Workaround for phpdocumentor/type-resolver < 1.6
// We trigger the autoloader here, so we don't need to trigger it inside the loop later.
class_exists(List_::class);
/**
* Transforms a php doc type to a {@link Type} instance.
*
* @author Kévin Dunglas <dunglas@gmail.com>
* @author Guilhem N. <egetick@gmail.com>
*/
final class PhpDocTypeHelper
{
/**
* Creates a {@see Type} from a PHPDoc type.
*
* @return Type[]
*/
public function getTypes(DocType $varType): array
{
$types = [];
$nullable = false;
if ($varType instanceof Nullable) {
$nullable = true;
$varType = $varType->getActualType();
}
if (!$varType instanceof Compound) {
if ($varType instanceof Null_) {
$nullable = true;
}
$type = $this->createType($varType, $nullable);
if (null !== $type) {
$types[] = $type;
}
return $types;
}
$varTypes = [];
for ($typeIndex = 0; $varType->has($typeIndex); ++$typeIndex) {
$type = $varType->get($typeIndex);
// If null is present, all types are nullable
if ($type instanceof Null_) {
$nullable = true;
continue;
}
if ($type instanceof Nullable) {
$nullable = true;
$type = $type->getActualType();
}
$varTypes[] = $type;
}
foreach ($varTypes as $varType) {
$type = $this->createType($varType, $nullable);
if (null !== $type) {
$types[] = $type;
}
}
return $types;
}
/**
* Creates a {@see Type} from a PHPDoc type.
*/
private function createType(DocType $type, bool $nullable, string $docType = null): ?Type
{
$docType = $docType ?? (string) $type;
if ($type instanceof Collection) {
$fqsen = $type->getFqsen();
if ($fqsen && 'list' === $fqsen->getName() && !class_exists(List_::class, false) && !class_exists((string) $fqsen)) {
// Workaround for phpdocumentor/type-resolver < 1.6
return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, new Type(Type::BUILTIN_TYPE_INT), $this->getTypes($type->getValueType()));
}
[$phpType, $class] = $this->getPhpTypeAndClass((string) $fqsen);
$key = $this->getTypes($type->getKeyType());
$value = $this->getTypes($type->getValueType());
// More than 1 type returned means it is a Compound type, which is
// not handled by Type, so better use a null value.
$key = 1 === \count($key) ? $key[0] : null;
$value = 1 === \count($value) ? $value[0] : null;
return new Type($phpType, $nullable, $class, true, $key, $value);
}
// Cannot guess
if (!$docType || 'mixed' === $docType) {
return null;
}
if (str_ends_with($docType, '[]')) {
$collectionKeyType = new Type(Type::BUILTIN_TYPE_INT);
$collectionValueType = $this->createType($type, false, substr($docType, 0, -2));
return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, $collectionKeyType, $collectionValueType);
}
if ((str_starts_with($docType, 'list<') || str_starts_with($docType, 'array<')) && $type instanceof Array_) {
// array<value> is converted to x[] which is handled above
// so it's only necessary to handle array<key, value> here
$collectionKeyType = $this->getTypes($type->getKeyType())[0];
$collectionValueTypes = $this->getTypes($type->getValueType());
if (1 != \count($collectionValueTypes)) {
// the Type class does not support union types yet, so assume that no type was defined
$collectionValueType = null;
} else {
$collectionValueType = $collectionValueTypes[0];
}
return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, $collectionKeyType, $collectionValueType);
}
$docType = $this->normalizeType($docType);
[$phpType, $class] = $this->getPhpTypeAndClass($docType);
if ('array' === $docType) {
return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, null, null);
}
return new Type($phpType, $nullable, $class);
}
private function normalizeType(string $docType): string
{
switch ($docType) {
case 'integer':
return 'int';
case 'boolean':
return 'bool';
// real is not part of the PHPDoc standard, so we ignore it
case 'double':
return 'float';
case 'callback':
return 'callable';
case 'void':
return 'null';
default:
return $docType;
}
}
private function getPhpTypeAndClass(string $docType): array
{
if (\in_array($docType, Type::$builtinTypes)) {
return [$docType, null];
}
if (\in_array($docType, ['parent', 'self', 'static'], true)) {
return ['object', $docType];
}
return ['object', ltrim($docType, '\\')];
}
}

View File

@ -0,0 +1,188 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyInfo\Util;
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode;
use PHPStan\PhpDocParser\Ast\Type\CallableTypeParameterNode;
use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode;
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode;
use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
use Symfony\Component\PropertyInfo\PhpStan\NameScope;
use Symfony\Component\PropertyInfo\Type;
/**
* Transforms a php doc tag value to a {@link Type} instance.
*
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*
* @internal
*/
final class PhpStanTypeHelper
{
/**
* Creates a {@see Type} from a PhpDocTagValueNode type.
*
* @return Type[]
*/
public function getTypes(PhpDocTagValueNode $node, NameScope $nameScope): array
{
if ($node instanceof ParamTagValueNode || $node instanceof ReturnTagValueNode || $node instanceof VarTagValueNode) {
return $this->compressNullableType($this->extractTypes($node->type, $nameScope));
}
return [];
}
/**
* Because PhpStan extract null as a separated type when Symfony / PHP compress it in the first available type we
* need this method to mimic how Symfony want null types.
*
* @param Type[] $types
*
* @return Type[]
*/
private function compressNullableType(array $types): array
{
$firstTypeIndex = null;
$nullableTypeIndex = null;
foreach ($types as $k => $type) {
if (null === $firstTypeIndex && Type::BUILTIN_TYPE_NULL !== $type->getBuiltinType() && !$type->isNullable()) {
$firstTypeIndex = $k;
}
if (null === $nullableTypeIndex && Type::BUILTIN_TYPE_NULL === $type->getBuiltinType()) {
$nullableTypeIndex = $k;
}
if (null !== $firstTypeIndex && null !== $nullableTypeIndex) {
break;
}
}
if (null !== $firstTypeIndex && null !== $nullableTypeIndex) {
$firstType = $types[$firstTypeIndex];
$types[$firstTypeIndex] = new Type(
$firstType->getBuiltinType(),
true,
$firstType->getClassName(),
$firstType->isCollection(),
$firstType->getCollectionKeyTypes(),
$firstType->getCollectionValueTypes()
);
unset($types[$nullableTypeIndex]);
}
return array_values($types);
}
/**
* @return Type[]
*/
private function extractTypes(TypeNode $node, NameScope $nameScope): array
{
if ($node instanceof UnionTypeNode) {
$types = [];
foreach ($node->types as $type) {
if ($type instanceof ConstTypeNode) {
// It's safer to fall back to other extractors here, as resolving const types correctly is not easy at the moment
return [];
}
foreach ($this->extractTypes($type, $nameScope) as $subType) {
$types[] = $subType;
}
}
return $this->compressNullableType($types);
}
if ($node instanceof GenericTypeNode) {
[$mainType] = $this->extractTypes($node->type, $nameScope);
$collectionKeyTypes = $mainType->getCollectionKeyTypes();
$collectionKeyValues = [];
if (1 === \count($node->genericTypes)) {
foreach ($this->extractTypes($node->genericTypes[0], $nameScope) as $subType) {
$collectionKeyValues[] = $subType;
}
} elseif (2 === \count($node->genericTypes)) {
foreach ($this->extractTypes($node->genericTypes[0], $nameScope) as $keySubType) {
$collectionKeyTypes[] = $keySubType;
}
foreach ($this->extractTypes($node->genericTypes[1], $nameScope) as $valueSubType) {
$collectionKeyValues[] = $valueSubType;
}
}
return [new Type($mainType->getBuiltinType(), $mainType->isNullable(), $mainType->getClassName(), true, $collectionKeyTypes, $collectionKeyValues)];
}
if ($node instanceof ArrayShapeNode) {
return [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true)];
}
if ($node instanceof ArrayTypeNode) {
return [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, [new Type(Type::BUILTIN_TYPE_INT)], $this->extractTypes($node->type, $nameScope))];
}
if ($node instanceof CallableTypeNode || $node instanceof CallableTypeParameterNode) {
return [new Type(Type::BUILTIN_TYPE_CALLABLE)];
}
if ($node instanceof NullableTypeNode) {
$subTypes = $this->extractTypes($node->type, $nameScope);
if (\count($subTypes) > 1) {
$subTypes[] = new Type(Type::BUILTIN_TYPE_NULL);
return $subTypes;
}
return [new Type($subTypes[0]->getBuiltinType(), true, $subTypes[0]->getClassName(), $subTypes[0]->isCollection(), $subTypes[0]->getCollectionKeyTypes(), $subTypes[0]->getCollectionValueTypes())];
}
if ($node instanceof ThisTypeNode) {
return [new Type(Type::BUILTIN_TYPE_OBJECT, false, $nameScope->resolveRootClass())];
}
if ($node instanceof IdentifierTypeNode) {
if (\in_array($node->name, Type::$builtinTypes)) {
return [new Type($node->name, false, null, \in_array($node->name, Type::$builtinCollectionTypes))];
}
switch ($node->name) {
case 'integer':
return [new Type(Type::BUILTIN_TYPE_INT)];
case 'list':
case 'non-empty-list':
return [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT))];
case 'non-empty-array':
return [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true)];
case 'mixed':
return []; // mixed seems to be ignored in all other extractors
case 'parent':
return [new Type(Type::BUILTIN_TYPE_OBJECT, false, $node->name)];
case 'static':
case 'self':
return [new Type(Type::BUILTIN_TYPE_OBJECT, false, $nameScope->resolveRootClass())];
case 'void':
return [new Type(Type::BUILTIN_TYPE_NULL)];
}
return [new Type(Type::BUILTIN_TYPE_OBJECT, false, $nameScope->resolveStringName($node->name))];
}
return [];
}
}

View File

@ -0,0 +1,57 @@
{
"name": "symfony/property-info",
"type": "library",
"description": "Extracts information about PHP class' properties using metadata of popular sources",
"keywords": [
"property",
"type",
"PHPDoc",
"symfony",
"validator",
"doctrine"
],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Kévin Dunglas",
"email": "dunglas@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.1|^3",
"symfony/polyfill-php80": "^1.16",
"symfony/string": "^5.1|^6.0"
},
"require-dev": {
"symfony/serializer": "^4.4|^5.0|^6.0",
"symfony/cache": "^4.4|^5.0|^6.0",
"symfony/dependency-injection": "^4.4|^5.0|^6.0",
"phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",
"phpstan/phpdoc-parser": "^1.0",
"doctrine/annotations": "^1.10.4"
},
"conflict": {
"phpdocumentor/reflection-docblock": "<3.2.2",
"phpdocumentor/type-resolver": "<1.4.0",
"symfony/dependency-injection": "<4.4"
},
"suggest": {
"psr/cache-implementation": "To cache results",
"symfony/doctrine-bridge": "To use Doctrine metadata",
"phpdocumentor/reflection-docblock": "To use the PHPDoc",
"symfony/serializer": "To use Serializer metadata"
},
"autoload": {
"psr-4": { "Symfony\\Component\\PropertyInfo\\": "" },
"exclude-from-classmap": [
"/Tests/"
]
},
"minimum-stability": "dev"
}