1
0

Ubah dari Swagger ke Dedoc lalu buat API Ticket dan TicketType

This commit is contained in:
Wian Drs
2026-06-20 00:12:19 +07:00
parent 2f805a233d
commit 2dc461ae89
1283 changed files with 36405 additions and 89470 deletions
-40
View File
@@ -1,40 +0,0 @@
CHANGELOG
=========
7.4
---
* Add extra type alias support in `TypeContext` and `StringTypeResolver`
7.3
---
* Add `Type::accepts()` method
* Add the `TypeFactoryTrait::fromValue()`, `TypeFactoryTrait::arrayShape()`, and `TypeFactoryTrait::arrayKey()` methods
* Deprecate constructing a `CollectionType` instance as a list that is not an array
* Deprecate the third `$asList` argument of `TypeFactoryTrait::iterable()`, use `TypeFactoryTrait::list()` instead
* Add type alias support in `TypeContext` and `StringTypeResolver`
* Add `CollectionType::mergeCollectionValueTypes()` method
* Add `ArrayShapeType` to represent the exact shape of an array
* Add `Type::traverse()` method
7.2
---
* Add construction validation for `BackedEnumType`, `CollectionType`, `GenericType`, `IntersectionType`, and `UnionType`
* Add `TypeIdentifier::isStandalone()`, `TypeIdentifier::isScalar()`, and `TypeIdentifier::isBool()` methods
* Add `WrappingTypeInterface` and `CompositeTypeInterface` type interfaces
* Add `NullableType` type class
* Rename `Type::isA()` to `Type::isIdentifiedBy()` and `Type::is()` to `Type::isSatisfiedBy()`
* Remove `Type::__call()`
* Remove `Type::getBaseType()`, use `WrappingTypeInterface::getWrappedType()` instead
* Remove `Type::asNonNullable()`, use `NullableType::getWrappedType()` instead
* Remove `CompositeTypeTrait`
* Add `PhpDocAwareReflectionTypeResolver` resolver
* The type resolvers are not marked as `@internal` anymore
* The component is not marked as `@experimental` anymore
7.1
---
* Add the component as experimental
@@ -1,20 +0,0 @@
<?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\TypeInfo\Exception;
/**
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*/
interface ExceptionInterface extends \Throwable
{
}
@@ -1,20 +0,0 @@
<?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\TypeInfo\Exception;
/**
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*/
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
-20
View File
@@ -1,20 +0,0 @@
<?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\TypeInfo\Exception;
/**
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*/
class LogicException extends \LogicException implements ExceptionInterface
{
}
-20
View File
@@ -1,20 +0,0 @@
<?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\TypeInfo\Exception;
/**
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*/
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
@@ -1,28 +0,0 @@
<?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\TypeInfo\Exception;
/**
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*/
class UnsupportedException extends \LogicException implements ExceptionInterface
{
public function __construct(
string $message,
public readonly mixed $subject,
int $code = 0,
?\Throwable $previous = null,
) {
parent::__construct($message, $code, $previous);
}
}
-19
View File
@@ -1,19 +0,0 @@
Copyright (c) 2024-present 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.
-54
View File
@@ -1,54 +0,0 @@
TypeInfo Component
==================
The TypeInfo component extracts PHP types information.
Getting Started
---------------
```bash
composer require symfony/type-info
composer require phpstan/phpdoc-parser # to support raw string resolving
```
```php
<?php
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\TypeIdentifier;
use Symfony\Component\TypeInfo\TypeResolver\TypeResolver;
// Instantiate a new resolver
$typeResolver = TypeResolver::create();
// Then resolve types for any subject
$typeResolver->resolve(new \ReflectionProperty(Dummy::class, 'id')); // returns an "int" Type instance
$typeResolver->resolve('bool'); // returns a "bool" Type instance
// Types can be instantiated thanks to static factories
$type = Type::list(Type::nullable(Type::bool()));
// Type classes have their specific methods
Type::object(FooClass::class)->getClassName();
Type::enum(FooEnum::class, Type::int())->getBackingType();
Type::list(Type::int())->isList();
// Every type can be cast to string
(string) Type::generic(Type::object(Collection::class), Type::int()) // returns "Collection<int>"
// You can check that a type (or one of its wrapped/composed parts) is identified by one of some identifiers.
$type->isIdentifiedBy(Foo::class, Bar::class);
$type->isIdentifiedBy(TypeIdentifier::OBJECT);
$type->isIdentifiedBy('float');
// You can also check that a type satisfies specific conditions
$type->isSatisfiedBy(fn (Type $type): bool => !$type->isNullable() && $type->isIdentifiedBy(TypeIdentifier::INT));
```
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/type_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)
-108
View File
@@ -1,108 +0,0 @@
<?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\TypeInfo;
use Symfony\Component\TypeInfo\Type\CompositeTypeInterface;
use Symfony\Component\TypeInfo\Type\WrappingTypeInterface;
/**
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*/
abstract class Type implements \Stringable
{
use TypeFactoryTrait;
/**
* Tells if the type is satisfied by the $specification callable.
*
* @param callable(self): bool $specification
*/
public function isSatisfiedBy(callable $specification): bool
{
if ($this instanceof WrappingTypeInterface && $this->wrappedTypeIsSatisfiedBy($specification)) {
return true;
}
if ($this instanceof CompositeTypeInterface && $this->composedTypesAreSatisfiedBy($specification)) {
return true;
}
return $specification($this);
}
/**
* Tells if the type (or one of its wrapped/composed parts) is identified by one of the $identifiers.
*/
public function isIdentifiedBy(TypeIdentifier|string ...$identifiers): bool
{
$specification = static fn (Type $type): bool => $type->isIdentifiedBy(...$identifiers);
if ($this instanceof WrappingTypeInterface && $this->wrappedTypeIsSatisfiedBy($specification)) {
return true;
}
if ($this instanceof CompositeTypeInterface && $this->composedTypesAreSatisfiedBy($specification)) {
return true;
}
return false;
}
public function isNullable(): bool
{
return false;
}
/**
* Tells if the type (or one of its wrapped/composed parts) accepts the given $value.
*/
public function accepts(mixed $value): bool
{
$specification = static function (Type $type) use (&$specification, $value): bool {
if ($type instanceof WrappingTypeInterface) {
return $type->wrappedTypeIsSatisfiedBy($specification);
}
if ($type instanceof CompositeTypeInterface) {
return $type->composedTypesAreSatisfiedBy($specification);
}
return $type->accepts($value);
};
return $this->isSatisfiedBy($specification);
}
/**
* Traverses the whole type tree.
*
* @return iterable<self>
*/
public function traverse(bool $traverseComposite = true, bool $traverseWrapped = true): iterable
{
yield $this;
if ($this instanceof CompositeTypeInterface && $traverseComposite) {
foreach ($this->getTypes() as $type) {
yield $type;
}
// prevent yielding twice when having a type that is both composite and wrapped
return;
}
if ($this instanceof WrappingTypeInterface && $traverseWrapped) {
yield $this->getWrappedType();
}
}
}
-144
View File
@@ -1,144 +0,0 @@
<?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\TypeInfo\Type;
use Symfony\Component\TypeInfo\Exception\InvalidArgumentException;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\TypeIdentifier;
/**
* Represents the exact shape of an array.
*
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
*
* @extends CollectionType<GenericType<BuiltinType<TypeIdentifier::ARRAY>>>
*/
final class ArrayShapeType extends CollectionType
{
/**
* @var array<array{type: Type, optional: bool}>
*/
private readonly array $shape;
/**
* @param array<array{type: Type, optional: bool}> $shape
*/
public function __construct(
array $shape,
private readonly ?Type $extraKeyType = null,
private readonly ?Type $extraValueType = null,
) {
$keyTypes = [];
$valueTypes = [];
foreach ($shape as $k => $v) {
$keyTypes[] = \is_int($k) ? Type::int() : Type::string();
$valueTypes[] = $v['type'];
}
if ($keyTypes) {
$keyTypes = array_values(array_unique($keyTypes));
$keyType = \count($keyTypes) > 1 ? self::union(...$keyTypes) : $keyTypes[0];
} else {
$keyType = Type::arrayKey();
}
$valueType = $valueTypes ? CollectionType::mergeCollectionValueTypes($valueTypes) : Type::mixed();
parent::__construct(self::generic(self::builtin(TypeIdentifier::ARRAY), $keyType, $valueType));
$sortedShape = $shape;
ksort($sortedShape);
$this->shape = $sortedShape;
if ($this->extraKeyType xor $this->extraValueType) {
throw new InvalidArgumentException(\sprintf('You must provide a value for "$%s" when "$%s" is provided.', $this->extraKeyType ? 'extraValueType' : 'extraKeyType', $this->extraKeyType ? 'extraKeyType' : 'extraValueType'));
}
if ($extraKeyType && !$extraKeyType->isIdentifiedBy(TypeIdentifier::INT, TypeIdentifier::STRING)) {
throw new InvalidArgumentException(\sprintf('"%s" is not a valid array key type.', (string) $extraKeyType));
}
}
/**
* @return array<array{type: Type, optional: bool}>
*/
public function getShape(): array
{
return $this->shape;
}
public function isSealed(): bool
{
return null === $this->extraValueType;
}
public function getExtraKeyType(): ?Type
{
return $this->extraKeyType;
}
public function getExtraValueType(): ?Type
{
return $this->extraValueType;
}
public function accepts(mixed $value): bool
{
if (!\is_array($value)) {
return false;
}
foreach ($this->shape as $key => $shapeValue) {
if (!($shapeValue['optional'] ?? false) && !\array_key_exists($key, $value)) {
return false;
}
}
foreach ($value as $key => $itemValue) {
$valueType = $this->shape[$key]['type'] ?? false;
if ($valueType && !$valueType->accepts($itemValue)) {
return false;
}
if (!$valueType && ($this->isSealed() || !$this->extraKeyType->accepts($key) || !$this->extraValueType->accepts($itemValue))) {
return false;
}
}
return true;
}
public function __toString(): string
{
$items = [];
foreach ($this->shape as $key => $value) {
$itemKey = \is_int($key) ? (string) $key : \sprintf("'%s'", $key);
if ($value['optional'] ?? false) {
$itemKey = \sprintf('%s?', $itemKey);
}
$items[] = \sprintf('%s: %s', $itemKey, $value['type']);
}
if (!$this->isSealed()) {
$items[] = $this->extraKeyType->isIdentifiedBy(TypeIdentifier::INT) && $this->extraKeyType->isIdentifiedBy(TypeIdentifier::STRING) && $this->extraValueType->isIdentifiedBy(TypeIdentifier::MIXED)
? '...'
: \sprintf('...<%s, %s>', $this->extraKeyType, $this->extraValueType);
}
return \sprintf('array{%s}', implode(', ', $items));
}
}
-50
View File
@@ -1,50 +0,0 @@
<?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\TypeInfo\Type;
use Symfony\Component\TypeInfo\Exception\InvalidArgumentException;
use Symfony\Component\TypeInfo\TypeIdentifier;
/**
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*
* @template T of class-string<\BackedEnum>
* @template U of BuiltinType<TypeIdentifier::INT>|BuiltinType<TypeIdentifier::STRING>
*
* @extends EnumType<T>
*/
final class BackedEnumType extends EnumType
{
/**
* @param T $className
* @param U $backingType
*/
public function __construct(
string $className,
private readonly BuiltinType $backingType,
) {
if (TypeIdentifier::INT !== $backingType->getTypeIdentifier() && TypeIdentifier::STRING !== $backingType->getTypeIdentifier()) {
throw new InvalidArgumentException(\sprintf('Cannot create "%s" with "%s" backing type.', self::class, $backingType));
}
parent::__construct($className);
}
/**
* @return U
*/
public function getBackingType(): BuiltinType
{
return $this->backingType;
}
}
-81
View File
@@ -1,81 +0,0 @@
<?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\TypeInfo\Type;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\TypeIdentifier;
/**
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*
* @template T of TypeIdentifier
*/
final class BuiltinType extends Type
{
/**
* @param T $typeIdentifier
*/
public function __construct(
private readonly TypeIdentifier $typeIdentifier,
) {
}
/**
* @return T
*/
public function getTypeIdentifier(): TypeIdentifier
{
return $this->typeIdentifier;
}
public function isIdentifiedBy(TypeIdentifier|string ...$identifiers): bool
{
foreach ($identifiers as $identifier) {
if ($identifier === $this->typeIdentifier || $identifier === $this->typeIdentifier->value) {
return true;
}
}
return false;
}
public function isNullable(): bool
{
return \in_array($this->typeIdentifier, [TypeIdentifier::NULL, TypeIdentifier::MIXED], true);
}
public function accepts(mixed $value): bool
{
return match ($this->typeIdentifier) {
TypeIdentifier::ARRAY => \is_array($value),
TypeIdentifier::BOOL => \is_bool($value),
TypeIdentifier::CALLABLE => \is_callable($value),
TypeIdentifier::FALSE => false === $value,
TypeIdentifier::FLOAT => \is_float($value),
TypeIdentifier::INT => \is_int($value),
TypeIdentifier::ITERABLE => is_iterable($value),
TypeIdentifier::MIXED => true,
TypeIdentifier::NULL => null === $value,
TypeIdentifier::OBJECT => \is_object($value),
TypeIdentifier::RESOURCE => \is_resource($value),
TypeIdentifier::STRING => \is_string($value),
TypeIdentifier::TRUE => true === $value,
default => false,
};
}
public function __toString(): string
{
return $this->typeIdentifier->value;
}
}
-217
View File
@@ -1,217 +0,0 @@
<?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\TypeInfo\Type;
use Symfony\Component\TypeInfo\Exception\InvalidArgumentException;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\TypeIdentifier;
/**
* Represents a key/value collection type.
*
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*
* @template T of BuiltinType<TypeIdentifier::ARRAY>|BuiltinType<TypeIdentifier::ITERABLE>|ObjectType|GenericType
*
* @implements WrappingTypeInterface<T>
*/
class CollectionType extends Type implements WrappingTypeInterface
{
/**
* @param T $type
*/
public function __construct(
private readonly BuiltinType|ObjectType|GenericType $type,
private readonly bool $isList = false,
) {
if ($type instanceof BuiltinType && TypeIdentifier::ARRAY !== $type->getTypeIdentifier() && TypeIdentifier::ITERABLE !== $type->getTypeIdentifier()) {
throw new InvalidArgumentException(\sprintf('Cannot create "%s" with "%s" type.', self::class, $type));
}
if ($this->isList()) {
if (!$type->isIdentifiedBy(TypeIdentifier::ARRAY)) {
trigger_deprecation('symfony/type-info', '7.3', 'Creating a "%s" that is a list and not an array is deprecated and will throw a "%s" in 8.0.', self::class, InvalidArgumentException::class);
// throw new InvalidArgumentException(\sprintf('Cannot create a "%s" as list when type is not "array".', self::class));
}
$keyType = $this->getCollectionKeyType();
if (!$keyType instanceof BuiltinType || TypeIdentifier::INT !== $keyType->getTypeIdentifier()) {
throw new InvalidArgumentException(\sprintf('"%s" is not a valid list key type.', (string) $keyType));
}
} elseif ($type instanceof GenericType && $type->getWrappedType() instanceof BuiltinType && TypeIdentifier::ARRAY === $type->getWrappedType()->getTypeIdentifier()) {
$keyType = $this->getCollectionKeyType();
$this->assertValidArrayKeyType($keyType);
}
}
private function assertValidArrayKeyType(Type $keyType, ?Type $rootType = null): void
{
$rootType ??= $keyType;
if ($keyType instanceof UnionType) {
foreach ($keyType->getTypes() as $type) {
$this->assertValidArrayKeyType($type, $rootType);
}
return;
}
if ($keyType instanceof TemplateType) {
$this->assertValidArrayKeyType($keyType->getBound(), $rootType);
return;
}
if ($keyType instanceof BuiltinType && \in_array($keyType->getTypeIdentifier(), [TypeIdentifier::INT, TypeIdentifier::STRING], true)) {
return;
}
throw new InvalidArgumentException(\sprintf('"%s" is not a valid array key type.', (string) $rootType));
}
/**
* @param array<Type> $types
*/
public static function mergeCollectionValueTypes(array $types): Type
{
if (!$types) {
throw new InvalidArgumentException('The $types cannot be empty.');
}
$normalizedTypes = [];
$boolTypes = [];
$objectTypes = [];
foreach ($types as $type) {
foreach (($type instanceof UnionType ? $type->getTypes() : [$type]) as $t) {
// cannot create an union with a standalone type
if ($t->isIdentifiedBy(TypeIdentifier::MIXED)) {
return Type::mixed();
}
if ($t->isIdentifiedBy(TypeIdentifier::TRUE, TypeIdentifier::FALSE, TypeIdentifier::BOOL)) {
$boolTypes[] = $t;
continue;
}
if ($t->isIdentifiedBy(TypeIdentifier::OBJECT)) {
$objectTypes[] = $t;
continue;
}
$normalizedTypes[] = $t;
}
}
$boolTypes = array_unique($boolTypes);
$objectTypes = array_unique($objectTypes);
// cannot create an union with either "true" and "false", "bool" must be used instead
if ($boolTypes) {
$normalizedTypes[] = \count($boolTypes) > 1 ? Type::bool() : $boolTypes[0];
}
// cannot create a union with either "object" and a class name, "object" must be used instead
if ($objectTypes) {
$hasBuiltinObjectType = array_filter($objectTypes, static fn (Type $t): bool => $t->isSatisfiedBy(static fn (Type $t): bool => $t instanceof BuiltinType));
$normalizedTypes = [...$normalizedTypes, ...($hasBuiltinObjectType ? [Type::object()] : $objectTypes)];
}
$normalizedTypes = array_values(array_unique($normalizedTypes));
return \count($normalizedTypes) > 1 ? self::union(...$normalizedTypes) : $normalizedTypes[0];
}
public function getWrappedType(): Type
{
return $this->type;
}
public function isList(): bool
{
return $this->isList;
}
public function getCollectionKeyType(): Type
{
$defaultCollectionKeyType = self::arrayKey();
if ($this->type instanceof GenericType) {
return match (\count($this->type->getVariableTypes())) {
2 => $this->type->getVariableTypes()[0],
1 => self::int(),
default => $defaultCollectionKeyType,
};
}
return $defaultCollectionKeyType;
}
public function getCollectionValueType(): Type
{
$defaultCollectionValueType = self::mixed();
if ($this->type instanceof GenericType) {
return match (\count($this->type->getVariableTypes())) {
2 => $this->type->getVariableTypes()[1],
1 => $this->type->getVariableTypes()[0],
default => $defaultCollectionValueType,
};
}
return $defaultCollectionValueType;
}
public function wrappedTypeIsSatisfiedBy(callable $specification): bool
{
return $this->getWrappedType()->isSatisfiedBy($specification);
}
public function accepts(mixed $value): bool
{
if (!parent::accepts($value)) {
return false;
}
if ($this->isList() && (!\is_array($value) || !array_is_list($value))) {
return false;
}
$keyType = $this->getCollectionKeyType();
$valueType = $this->getCollectionValueType();
if (is_iterable($value)) {
foreach ($value as $k => $v) {
// key or value do not match
if (!$keyType->accepts($k) || !$valueType->accepts($v)) {
return false;
}
}
}
return true;
}
public function __toString(): string
{
if ($this->isList && $this->type->isIdentifiedBy(TypeIdentifier::ARRAY)) {
return 'list<'.$this->getCollectionValueType().'>';
}
return (string) $this->type;
}
}
@@ -1,34 +0,0 @@
<?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\TypeInfo\Type;
use Symfony\Component\TypeInfo\Type;
/**
* Represents a type composed of several other types.
*
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
*
* @template T of Type
*/
interface CompositeTypeInterface
{
/**
* @return list<T>
*/
public function getTypes(): array;
/**
* @param callable(Type): bool $specification
*/
public function composedTypesAreSatisfiedBy(callable $specification): bool;
}
-24
View File
@@ -1,24 +0,0 @@
<?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\TypeInfo\Type;
/**
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*
* @template T of class-string<\UnitEnum>
*
* @extends ObjectType<T>
*/
class EnumType extends ObjectType
{
}
-80
View File
@@ -1,80 +0,0 @@
<?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\TypeInfo\Type;
use Symfony\Component\TypeInfo\Exception\InvalidArgumentException;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\TypeIdentifier;
/**
* Represents a generic type, which is a type that holds variable parts.
*
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*
* @template T of BuiltinType<TypeIdentifier::ARRAY>|BuiltinType<TypeIdentifier::ITERABLE>|ObjectType
*
* @implements WrappingTypeInterface<T>
*/
final class GenericType extends Type implements WrappingTypeInterface
{
/**
* @var list<Type>
*/
private readonly array $variableTypes;
/**
* @param T $type
*/
public function __construct(
private readonly BuiltinType|ObjectType $type,
Type ...$variableTypes,
) {
if ($type instanceof BuiltinType && TypeIdentifier::ARRAY !== $type->getTypeIdentifier() && TypeIdentifier::ITERABLE !== $type->getTypeIdentifier()) {
throw new InvalidArgumentException(\sprintf('Cannot create "%s" with "%s" type.', self::class, $type));
}
$this->variableTypes = $variableTypes;
}
public function getWrappedType(): Type
{
return $this->type;
}
/**
* @return list<Type>
*/
public function getVariableTypes(): array
{
return $this->variableTypes;
}
public function wrappedTypeIsSatisfiedBy(callable $specification): bool
{
return $this->getWrappedType()->isSatisfiedBy($specification);
}
public function __toString(): string
{
$typeString = (string) $this->type;
$variableTypesString = '';
$glue = '';
foreach ($this->variableTypes as $t) {
$variableTypesString .= $glue.$t;
$glue = ', ';
}
return $typeString.'<'.$variableTypesString.'>';
}
}
-90
View File
@@ -1,90 +0,0 @@
<?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\TypeInfo\Type;
use Symfony\Component\TypeInfo\Exception\InvalidArgumentException;
use Symfony\Component\TypeInfo\Type;
/**
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*
* @template T of ObjectType|GenericType<ObjectType>|CollectionType<GenericType<ObjectType>>
*
* @implements CompositeTypeInterface<T>
*/
final class IntersectionType extends Type implements CompositeTypeInterface
{
/**
* @var list<T>
*/
private readonly array $types;
/**
* @param list<T> $types
*/
public function __construct(Type ...$types)
{
if (\count($types) < 2) {
throw new InvalidArgumentException(\sprintf('"%s" expects at least 2 types.', self::class));
}
foreach ($types as $type) {
if ($type instanceof CompositeTypeInterface || $type instanceof NullableType) {
throw new InvalidArgumentException(\sprintf('Cannot set "%s" as a "%s" part.', $type, self::class));
}
while ($type instanceof WrappingTypeInterface) {
$type = $type->getWrappedType();
}
if (!$type instanceof ObjectType) {
throw new InvalidArgumentException(\sprintf('Cannot set "%s" as a "%s" part.', $type, self::class));
}
}
usort($types, static fn (Type $a, Type $b): int => (string) $a <=> (string) $b);
$this->types = array_values(array_unique($types));
}
/**
* @return list<T>
*/
public function getTypes(): array
{
return $this->types;
}
public function composedTypesAreSatisfiedBy(callable $specification): bool
{
foreach ($this->types as $type) {
if (!$type->isSatisfiedBy($specification)) {
return false;
}
}
return true;
}
public function __toString(): string
{
$string = '';
$glue = '';
foreach ($this->types as $t) {
$string .= $glue.($t instanceof CompositeTypeInterface ? '('.$t.')' : $t);
$glue = '&';
}
return $string;
}
}
-67
View File
@@ -1,67 +0,0 @@
<?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\TypeInfo\Type;
use Symfony\Component\TypeInfo\Exception\InvalidArgumentException;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\TypeIdentifier;
/**
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
*
* @template T of Type
*
* @extends UnionType<T|BuiltinType<TypeIdentifier::NULL>>
*
* @implements WrappingTypeInterface<T>
*/
final class NullableType extends UnionType implements WrappingTypeInterface
{
/**
* @param T $type
*/
public function __construct(
private readonly Type $type,
) {
if ($type->isNullable()) {
throw new InvalidArgumentException(\sprintf('Cannot create a "%s" with "%s" because it is already nullable.', self::class, $type));
}
if ($type instanceof UnionType) {
parent::__construct(Type::null(), ...$type->getTypes());
return;
}
parent::__construct(Type::null(), $type);
}
public function getWrappedType(): Type
{
return $this->type;
}
public function wrappedTypeIsSatisfiedBy(callable $specification): bool
{
return $this->getWrappedType()->isSatisfiedBy($specification);
}
public function isNullable(): bool
{
return true;
}
public function accepts(mixed $value): bool
{
return null === $value || parent::accepts($value);
}
}
-78
View File
@@ -1,78 +0,0 @@
<?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\TypeInfo\Type;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\TypeIdentifier;
/**
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*
* @template T of class-string
*/
class ObjectType extends Type
{
/**
* @param T $className
*/
public function __construct(
private readonly string $className,
) {
}
public function getTypeIdentifier(): TypeIdentifier
{
return TypeIdentifier::OBJECT;
}
/**
* @return T
*/
public function getClassName(): string
{
return $this->className;
}
public function isIdentifiedBy(TypeIdentifier|string ...$identifiers): bool
{
foreach ($identifiers as $identifier) {
if ($identifier instanceof TypeIdentifier) {
if (TypeIdentifier::OBJECT === $identifier) {
return true;
}
continue;
}
if (TypeIdentifier::OBJECT->value === $identifier) {
return true;
}
if (is_a($this->className, $identifier, allow_string: true)) {
return true;
}
}
return false;
}
public function accepts(mixed $value): bool
{
return $value instanceof $this->className;
}
public function __toString(): string
{
return $this->className;
}
}
-64
View File
@@ -1,64 +0,0 @@
<?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\TypeInfo\Type;
use Symfony\Component\TypeInfo\Type;
/**
* Represents a template placeholder, such as "T" in "Collection<T>".
*
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*
* @template T of Type
*
* @implements WrappingTypeInterface<T>
*/
final class TemplateType extends Type implements WrappingTypeInterface
{
/**
* @param T $bound
*/
public function __construct(
private readonly string $name,
private readonly Type $bound,
) {
}
public function getName(): string
{
return $this->name;
}
/**
* @return T
*/
public function getBound(): Type
{
return $this->bound;
}
public function getWrappedType(): Type
{
return $this->bound;
}
public function wrappedTypeIsSatisfiedBy(callable $specification): bool
{
return $this->getWrappedType()->isSatisfiedBy($specification);
}
public function __toString(): string
{
return $this->name;
}
}
-110
View File
@@ -1,110 +0,0 @@
<?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\TypeInfo\Type;
use Symfony\Component\TypeInfo\Exception\InvalidArgumentException;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\TypeIdentifier;
/**
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*
* @template T of Type
*
* @implements CompositeTypeInterface<T>
*/
class UnionType extends Type implements CompositeTypeInterface
{
/**
* @var list<T>
*/
private readonly array $types;
/**
* @param list<T> $types
*/
public function __construct(Type ...$types)
{
if (\count($types) < 2) {
throw new InvalidArgumentException(\sprintf('"%s" expects at least 2 types.', self::class));
}
foreach ($types as $type) {
if ($type instanceof self) {
throw new InvalidArgumentException(\sprintf('Cannot set "%s" as a "%1$s" part.', self::class));
}
if ($type instanceof BuiltinType) {
if (TypeIdentifier::NULL === $type->getTypeIdentifier() && !is_a(static::class, NullableType::class, allow_string: true)) {
throw new InvalidArgumentException(\sprintf('Cannot create union with "null", please use "%s" instead.', NullableType::class));
}
if ($type->getTypeIdentifier()->isStandalone()) {
throw new InvalidArgumentException(\sprintf('Cannot create union with "%s" standalone type.', $type));
}
}
}
usort($types, static fn (Type $a, Type $b): int => (string) $a <=> (string) $b);
$this->types = array_values(array_unique($types));
$builtinTypesIdentifiers = array_map(
static fn (BuiltinType $t): TypeIdentifier => $t->getTypeIdentifier(),
array_filter($this->types, static fn (Type $t): bool => $t instanceof BuiltinType),
);
if ((\in_array(TypeIdentifier::TRUE, $builtinTypesIdentifiers, true) || \in_array(TypeIdentifier::FALSE, $builtinTypesIdentifiers, true)) && \in_array(TypeIdentifier::BOOL, $builtinTypesIdentifiers, true)) {
throw new InvalidArgumentException('Cannot create union with redundant boolean type.');
}
if (\in_array(TypeIdentifier::TRUE, $builtinTypesIdentifiers, true) && \in_array(TypeIdentifier::FALSE, $builtinTypesIdentifiers, true)) {
throw new InvalidArgumentException('Cannot create union with both "true" and "false", "bool" should be used instead.');
}
if (\in_array(TypeIdentifier::OBJECT, $builtinTypesIdentifiers, true) && \count(array_filter($this->types, static fn (Type $t): bool => $t instanceof ObjectType))) {
throw new InvalidArgumentException('Cannot create union with both "object" and class type.');
}
}
/**
* @return list<T>
*/
public function getTypes(): array
{
return $this->types;
}
public function composedTypesAreSatisfiedBy(callable $specification): bool
{
foreach ($this->types as $type) {
if ($type->isSatisfiedBy($specification)) {
return true;
}
}
return false;
}
public function __toString(): string
{
$string = '';
$glue = '';
foreach ($this->types as $t) {
$string .= $glue.($t instanceof CompositeTypeInterface ? '('.$t.')' : $t);
$glue = '|';
}
return $string;
}
}
-34
View File
@@ -1,34 +0,0 @@
<?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\TypeInfo\Type;
use Symfony\Component\TypeInfo\Type;
/**
* Represents a type wrapping another type.
*
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
*
* @template T of Type
*/
interface WrappingTypeInterface
{
/**
* @return T
*/
public function getWrappedType(): Type;
/**
* @param callable(Type): bool $specification
*/
public function wrappedTypeIsSatisfiedBy(callable $specification): bool;
}
-118
View File
@@ -1,118 +0,0 @@
<?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\TypeInfo\TypeContext;
use Symfony\Component\TypeInfo\Exception\LogicException;
use Symfony\Component\TypeInfo\Type;
/**
* Type resolving context.
*
* Helps to retrieve declaring class, called class, parent class, templates
* and normalize classes according to the current namespace and uses.
*
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*/
final class TypeContext
{
/**
* @var array<string, bool>
*/
private static array $classExistCache = [];
/**
* @param array<string, string> $uses
* @param array<string, Type> $templates
* @param array<string, Type> $typeAliases
*/
public function __construct(
public readonly string $calledClassName,
public readonly string $declaringClassName,
public readonly ?string $namespace = null,
public readonly array $uses = [],
public readonly array $templates = [],
public readonly array $typeAliases = [],
) {
}
/**
* Normalize class name according to current namespace and uses.
*/
public function normalize(string $name): string
{
if (str_starts_with($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;
}
/**
* @return class-string
*/
public function getDeclaringClass(): string
{
return $this->declaringClassName;
}
/**
* @return class-string
*/
public function getCalledClass(): string
{
return $this->calledClassName;
}
/**
* @return class-string
*/
public function getParentClass(): string
{
$declaringClassName = $this->getDeclaringClass();
if (false === $parentClass = get_parent_class($declaringClassName)) {
throw new LogicException(\sprintf('"%s" do not extend any class.', $declaringClassName));
}
if (!isset(self::$classExistCache[$parentClass])) {
self::$classExistCache[$parentClass] = false;
if (class_exists($parentClass)) {
self::$classExistCache[$parentClass] = true;
} else {
try {
new \ReflectionClass($parentClass);
self::$classExistCache[$parentClass] = true;
} catch (\Throwable) {
}
}
}
return self::$classExistCache[$parentClass] ? $parentClass : $this->normalize(str_replace($this->namespace.'\\', '', $parentClass));
}
}
@@ -1,374 +0,0 @@
<?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\TypeInfo\TypeContext;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\TypeAliasImportTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\TypeAliasTagValueNode;
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 PHPStan\PhpDocParser\ParserConfig;
use Symfony\Component\TypeInfo\Exception\LogicException;
use Symfony\Component\TypeInfo\Exception\RuntimeException;
use Symfony\Component\TypeInfo\Exception\UnsupportedException;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\Type\ObjectType;
use Symfony\Component\TypeInfo\TypeResolver\StringTypeResolver;
/**
* Creates a type resolving context.
*
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
* @author Pierre-Yves Landuré <landure@gmail.com>
*/
final class TypeContextFactory
{
/**
* @var array<class-string, \ReflectionClass>
*/
private static array $reflectionClassCache = [];
/**
* @var array<string,array<string,TypeContext>>
*/
private array $intermediateTypeContextCache = [];
/**
* @var array<string,array<string,TypeContext>>
*/
private array $typeContextCache = [];
private ?Lexer $phpstanLexer = null;
private ?PhpDocParser $phpstanParser = null;
/**
* @param array<string, string> $extraTypeAliases
*/
public function __construct(
private readonly ?StringTypeResolver $stringTypeResolver = null,
private readonly array $extraTypeAliases = [],
) {
}
public function createFromClassName(string $calledClassName, ?string $declaringClassName = null): TypeContext
{
$declaringClassName ??= $calledClassName;
return $this->typeContextCache[$declaringClassName][$calledClassName] ??= $this->createNewInstanceFromClassName($calledClassName, $declaringClassName);
}
public function createFromReflection(\Reflector $reflection): ?TypeContext
{
$declaringClassReflection = match (true) {
$reflection instanceof \ReflectionClass => $reflection,
$reflection instanceof \ReflectionMethod => $reflection->getDeclaringClass(),
$reflection instanceof \ReflectionProperty => $reflection->getDeclaringClass(),
$reflection instanceof \ReflectionParameter => $reflection->getDeclaringClass(),
$reflection instanceof \ReflectionFunctionAbstract => $reflection->getClosureScopeClass(),
default => null,
};
if (null === $declaringClassReflection) {
return null;
}
$typeContext = $this->createIntermediateTypeContext($declaringClassReflection->getName(), $declaringClassReflection);
$templates = match (true) {
$reflection instanceof \ReflectionFunctionAbstract => $this->collectTemplates($reflection, $typeContext) + $this->collectTemplates($declaringClassReflection, $typeContext),
$reflection instanceof \ReflectionParameter => $this->collectTemplates($reflection->getDeclaringFunction(), $typeContext) + $this->collectTemplates($declaringClassReflection, $typeContext),
default => $this->collectTemplates($declaringClassReflection, $typeContext),
};
$typeContext = new TypeContext(
$typeContext->calledClassName,
$typeContext->declaringClassName,
$typeContext->namespace,
$typeContext->uses,
$templates,
);
return new TypeContext(
$typeContext->calledClassName,
$typeContext->declaringClassName,
$typeContext->namespace,
$typeContext->uses,
$typeContext->templates,
$this->collectTypeAliases($declaringClassReflection, $typeContext),
);
}
private function createNewInstanceFromClassName(string $calledClassName, string $declaringClassName): TypeContext
{
$calledClassNameReflection = self::$reflectionClassCache[$calledClassName] ??= new \ReflectionClass($calledClassName);
$declaringClassReflection = self::$reflectionClassCache[$declaringClassName] ??= new \ReflectionClass($declaringClassName);
$calledClassTypeContext = $this->createIntermediateTypeContext($calledClassNameReflection->getName(), $calledClassNameReflection);
$typeContext = $this->createIntermediateTypeContext($calledClassNameReflection->getName(), $declaringClassReflection);
$typeContext = new TypeContext(
$typeContext->calledClassName,
$typeContext->declaringClassName,
$typeContext->namespace,
$typeContext->uses,
$this->collectTemplates($calledClassNameReflection, $calledClassTypeContext) + $this->collectTemplates($declaringClassReflection, $typeContext),
);
return new TypeContext(
$typeContext->calledClassName,
$typeContext->declaringClassName,
$typeContext->namespace,
$typeContext->uses,
$typeContext->templates,
$this->collectTypeAliases($declaringClassReflection, $typeContext),
);
}
private function createIntermediateTypeContext(string $calledClassName, \ReflectionClass $declaringClassReflection): TypeContext
{
$declaringClassName = $declaringClassReflection->getName();
return $this->intermediateTypeContextCache[$declaringClassName][$calledClassName] ??= new TypeContext(
$calledClassName,
$declaringClassReflection->getName(),
trim($declaringClassReflection->getNamespaceName(), '\\'),
$this->collectUses($declaringClassReflection),
);
}
/**
* @return array<string, string>
*/
private function collectUses(\ReflectionClass $reflection): array
{
$fileName = $reflection->getFileName();
if (!\is_string($fileName) || !is_file($fileName)) {
return [];
}
if (false === $lines = @file($fileName, \FILE_IGNORE_NEW_LINES | \FILE_SKIP_EMPTY_LINES)) {
throw new RuntimeException(\sprintf('Unable to read file "%s".', $fileName));
}
$uses = [];
$inUseSection = false;
$inGroupedUse = false;
$groupPrefix = '';
foreach ($lines as $line) {
$trimmed = trim($line, " \t");
if ($inGroupedUse) {
$this->parseGroupedUseMembers($trimmed, $groupPrefix, $uses);
if (str_contains($trimmed, '}')) {
$inGroupedUse = false;
}
continue;
}
if (str_starts_with($line, 'use ')) {
$inUseSection = true;
$body = substr($trimmed, 4);
if (str_contains($body, '{')) {
$groupPrefix = substr($body, 0, strpos($body, '{'));
$inGroupedUse = !str_contains($body, '}');
$segment = trim(substr($body, strpos($body, '{')), " \t\r\n{};");
$this->parseGroupedUseMembers($segment, $groupPrefix, $uses);
} else {
$use = preg_split('/\s+as\s+/', rtrim($body, ';'), 2);
$fqcn = ltrim($use[0], '\\');
$alias = $use[1] ?? (false !== ($p = strrpos($fqcn, '\\')) ? substr($fqcn, 1 + $p) : $fqcn);
$uses[$alias] = $fqcn;
}
} elseif ($inUseSection) {
break;
}
}
$traitUses = [];
foreach ($reflection->getTraits() as $traitReflection) {
$traitUses[] = $this->collectUses($traitReflection);
}
return array_merge($uses, ...$traitUses);
}
/**
* @param array<string, string> $uses
*/
private function parseGroupedUseMembers(string $segment, string $prefix, array &$uses): void
{
foreach (explode(',', $segment) as $member) {
if ('' === $member = trim($member, " \t\r\n};")) {
continue;
}
$parts = preg_split('/\s+as\s+/', $member, 2);
$fqcn = ltrim($prefix.$parts[0], '\\');
$alias = $parts[1] ?? (false !== ($p = strrpos($fqcn, '\\')) ? substr($fqcn, 1 + $p) : $fqcn);
$uses[$alias] = $fqcn;
}
}
/**
* @return array<string, Type>
*/
private function collectTemplates(\ReflectionClass|\ReflectionFunctionAbstract $reflection, TypeContext $typeContext): array
{
if (!$this->stringTypeResolver || !class_exists(PhpDocParser::class)) {
return [];
}
if (!$rawDocNode = $reflection->getDocComment()) {
return [];
}
$templates = [];
foreach ($this->getPhpDocNode($rawDocNode)->getTagsByName('@template') + $this->getPhpDocNode($rawDocNode)->getTagsByName('@phpstan-template') + $this->getPhpDocNode($rawDocNode)->getTagsByName('@psalm-template') as $tag) {
if (!$tag->value instanceof TemplateTagValueNode) {
continue;
}
$type = Type::mixed();
$typeString = ((string) $tag->value->bound) ?: null;
try {
if (null !== $typeString) {
$type = $this->stringTypeResolver->resolve($typeString, $typeContext);
}
} catch (UnsupportedException) {
}
$templates[$tag->value->name] = $type;
}
return $templates;
}
/**
* @return array<string, Type>
*/
private function collectTypeAliases(\ReflectionClass $reflection, TypeContext $typeContext): array
{
if (!$this->stringTypeResolver || !class_exists(PhpDocParser::class)) {
return [];
}
$extraAliases = array_map($this->stringTypeResolver->resolve(...), $this->extraTypeAliases);
if (!$rawDocNode = $reflection->getDocComment()) {
return $extraAliases;
}
$aliases = [];
$resolvedAliases = [];
foreach ($this->getPhpDocNode($rawDocNode)->getTagsByName('@psalm-import-type') + $this->getPhpDocNode($rawDocNode)->getTagsByName('@phpstan-import-type') as $tag) {
if (!$tag->value instanceof TypeAliasImportTagValueNode) {
continue;
}
$importedFromType = $this->stringTypeResolver->resolve((string) $tag->value->importedFrom, $typeContext);
if (!$importedFromType instanceof ObjectType) {
throw new LogicException(\sprintf('Type alias "%s" is not imported from a valid class name.', $tag->value->importedAlias));
}
$importedFromContext = $this->createFromClassName($importedFromType->getClassName());
$typeAlias = $importedFromContext->typeAliases[$tag->value->importedAlias] ?? null;
if (!$typeAlias) {
throw new LogicException(\sprintf('Cannot find any "%s" type alias in "%s".', $tag->value->importedAlias, $importedFromType->getClassName()));
}
$resolvedAliases[$tag->value->importedAs ?? $tag->value->importedAlias] = $typeAlias;
}
foreach ($this->getPhpDocNode($rawDocNode)->getTagsByName('@psalm-type') + $this->getPhpDocNode($rawDocNode)->getTagsByName('@phpstan-type') as $tag) {
if (!$tag->value instanceof TypeAliasTagValueNode) {
continue;
}
$aliases[$tag->value->alias] = (string) $tag->value->type;
}
return $this->resolveTypeAliases($aliases, $resolvedAliases, $typeContext) + $extraAliases;
}
/**
* @param array<string, string> $toResolve
* @param array<string, Type> $resolved
*
* @return array<string, Type>
*/
private function resolveTypeAliases(array $toResolve, array $resolved, TypeContext $typeContext): array
{
if (!$toResolve) {
return $resolved;
}
$typeContext = new TypeContext(
$typeContext->calledClassName,
$typeContext->declaringClassName,
$typeContext->namespace,
$typeContext->uses,
$typeContext->templates,
$typeContext->typeAliases + $resolved,
);
$succeeded = false;
$lastFailure = null;
$lastFailingAlias = null;
foreach ($toResolve as $alias => $type) {
try {
$resolved[$alias] = $this->stringTypeResolver->resolve($type, $typeContext);
unset($toResolve[$alias]);
$succeeded = true;
} catch (UnsupportedException $lastFailure) {
$lastFailingAlias = $alias;
}
}
// nothing has succeeded, the result won't be different from the
// previous one, we can stop here.
if (!$succeeded) {
throw new LogicException(\sprintf('Cannot resolve "%s" type alias.', $lastFailingAlias), 0, $lastFailure);
}
if ($toResolve) {
return $this->resolveTypeAliases($toResolve, $resolved, $typeContext);
}
return $resolved;
}
private function getPhpDocNode(string $rawDocNode): PhpDocNode
{
if (class_exists(ParserConfig::class)) {
$this->phpstanLexer ??= new Lexer($config = new ParserConfig([]));
$this->phpstanParser ??= new PhpDocParser($config, new TypeParser($config, new ConstExprParser($config)), new ConstExprParser($config));
} else {
$this->phpstanLexer ??= new Lexer();
$this->phpstanParser ??= new PhpDocParser(new TypeParser(new ConstExprParser()), new ConstExprParser());
}
return $this->phpstanParser->parse(new TokenIterator($this->phpstanLexer->tokenize($rawDocNode)));
}
}
-451
View File
@@ -1,451 +0,0 @@
<?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\TypeInfo;
use Symfony\Component\TypeInfo\Type\ArrayShapeType;
use Symfony\Component\TypeInfo\Type\BackedEnumType;
use Symfony\Component\TypeInfo\Type\BuiltinType;
use Symfony\Component\TypeInfo\Type\CollectionType;
use Symfony\Component\TypeInfo\Type\EnumType;
use Symfony\Component\TypeInfo\Type\GenericType;
use Symfony\Component\TypeInfo\Type\IntersectionType;
use Symfony\Component\TypeInfo\Type\NullableType;
use Symfony\Component\TypeInfo\Type\ObjectType;
use Symfony\Component\TypeInfo\Type\TemplateType;
use Symfony\Component\TypeInfo\Type\UnionType;
/**
* Helper trait to create any type easily.
*
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*/
trait TypeFactoryTrait
{
/**
* @template T of TypeIdentifier
* @template U value-of<T>
*
* @param T|U $identifier
*
* @return BuiltinType<T>
*/
public static function builtin(TypeIdentifier|string $identifier): BuiltinType
{
/** @var T $identifier */
$identifier = \is_string($identifier) ? TypeIdentifier::from($identifier) : $identifier;
return new BuiltinType($identifier);
}
/**
* @return BuiltinType<TypeIdentifier::INT>
*/
public static function int(): BuiltinType
{
return self::builtin(TypeIdentifier::INT);
}
/**
* @return BuiltinType<TypeIdentifier::FLOAT>
*/
public static function float(): BuiltinType
{
return self::builtin(TypeIdentifier::FLOAT);
}
/**
* @return BuiltinType<TypeIdentifier::STRING>
*/
public static function string(): BuiltinType
{
return self::builtin(TypeIdentifier::STRING);
}
/**
* @return BuiltinType<TypeIdentifier::BOOL>
*/
public static function bool(): BuiltinType
{
return self::builtin(TypeIdentifier::BOOL);
}
/**
* @return BuiltinType<TypeIdentifier::RESOURCE>
*/
public static function resource(): BuiltinType
{
return self::builtin(TypeIdentifier::RESOURCE);
}
/**
* @return BuiltinType<TypeIdentifier::FALSE>
*/
public static function false(): BuiltinType
{
return self::builtin(TypeIdentifier::FALSE);
}
/**
* @return BuiltinType<TypeIdentifier::TRUE>
*/
public static function true(): BuiltinType
{
return self::builtin(TypeIdentifier::TRUE);
}
/**
* @return BuiltinType<TypeIdentifier::CALLABLE>
*/
public static function callable(): BuiltinType
{
return self::builtin(TypeIdentifier::CALLABLE);
}
/**
* @return BuiltinType<TypeIdentifier::MIXED>
*/
public static function mixed(): BuiltinType
{
return self::builtin(TypeIdentifier::MIXED);
}
/**
* @return BuiltinType<TypeIdentifier::NULL>
*/
public static function null(): BuiltinType
{
return self::builtin(TypeIdentifier::NULL);
}
/**
* @return BuiltinType<TypeIdentifier::VOID>
*/
public static function void(): BuiltinType
{
return self::builtin(TypeIdentifier::VOID);
}
/**
* @return BuiltinType<TypeIdentifier::NEVER>
*/
public static function never(): BuiltinType
{
return self::builtin(TypeIdentifier::NEVER);
}
/**
* @template T of BuiltinType<TypeIdentifier::ARRAY>|BuiltinType<TypeIdentifier::ITERABLE>|ObjectType|GenericType
*
* @param T $type
*
* @return CollectionType<T>
*/
public static function collection(BuiltinType|ObjectType|GenericType $type, ?Type $value = null, ?Type $key = null, bool $asList = false): CollectionType
{
if (!$type instanceof GenericType && (null !== $value || null !== $key)) {
$type = self::generic($type, $key ?? self::arrayKey(), $value ?? self::mixed());
}
return new CollectionType($type, $asList);
}
/**
* @return CollectionType<BuiltinType<TypeIdentifier::ARRAY>>
*/
public static function array(?Type $value = null, ?Type $key = null, bool $asList = false): CollectionType
{
return self::collection(self::builtin(TypeIdentifier::ARRAY), $value, $key, $asList);
}
/**
* @return CollectionType<BuiltinType<TypeIdentifier::ITERABLE>>
*/
public static function iterable(?Type $value = null, ?Type $key = null, bool $asList = false): CollectionType
{
if ($asList) {
trigger_deprecation('symfony/type-info', '7.3', 'The third argument of "%s()" is deprecated. Use the "%s::list()" method to create a list instead.', __METHOD__, self::class);
}
return self::collection(self::builtin(TypeIdentifier::ITERABLE), $value, $key, $asList);
}
/**
* @return CollectionType<BuiltinType<TypeIdentifier::ARRAY>>
*/
public static function list(?Type $value = null): CollectionType
{
return self::array($value, self::int(), asList: true);
}
/**
* @return CollectionType<BuiltinType<TypeIdentifier::ARRAY>>
*/
public static function dict(?Type $value = null): CollectionType
{
return self::array($value, self::string());
}
/**
* @param array<array{type: Type, optional?: bool}|Type> $shape
*/
public static function arrayShape(array $shape, bool $sealed = true, ?Type $extraKeyType = null, ?Type $extraValueType = null): ArrayShapeType
{
$shape = array_map(static fn (array|Type $item): array => $item instanceof Type
? ['type' => $item, 'optional' => false]
: ['type' => $item['type'], 'optional' => $item['optional'] ?? false], $shape);
if ($extraKeyType || $extraValueType) {
$sealed = false;
}
$extraKeyType ??= !$sealed ? Type::arrayKey() : null;
$extraValueType ??= !$sealed ? Type::mixed() : null;
return new ArrayShapeType($shape, $extraKeyType, $extraValueType);
}
public static function arrayKey(): UnionType
{
return self::union(self::int(), self::string());
}
/**
* @template T of class-string
*
* @param T|null $className
*
* @return ($className is class-string ? ObjectType<T> : BuiltinType<TypeIdentifier::OBJECT>)
*/
public static function object(?string $className = null): BuiltinType|ObjectType
{
return null !== $className ? new ObjectType($className) : new BuiltinType(TypeIdentifier::OBJECT);
}
/**
* @template T of class-string<\UnitEnum>|class-string<\BackedEnum>
* @template U of BuiltinType<TypeIdentifier::INT>|BuiltinType<TypeIdentifier::STRING>
*
* @param T $className
* @param U|null $backingType
*
* @return ($className is class-string<\BackedEnum> ? ($backingType is U ? BackedEnumType<T, U> : BackedEnumType<T, BuiltinType<TypeIdentifier::INT>|BuiltinType<TypeIdentifier::STRING>>) : EnumType<T>))
*/
public static function enum(string $className, ?BuiltinType $backingType = null): EnumType
{
if (is_subclass_of($className, \BackedEnum::class)) {
if (null === $backingType) {
$reflectionBackingType = (new \ReflectionEnum($className))->getBackingType();
$typeIdentifier = TypeIdentifier::INT->value === (string) $reflectionBackingType ? TypeIdentifier::INT : TypeIdentifier::STRING;
$backingType = new BuiltinType($typeIdentifier);
}
return new BackedEnumType($className, $backingType);
}
return new EnumType($className);
}
/**
* @template T of BuiltinType<TypeIdentifier::ARRAY>|BuiltinType<TypeIdentifier::ITERABLE>|ObjectType
*
* @param T $mainType
*
* @return GenericType<T>
*/
public static function generic(BuiltinType|ObjectType $mainType, Type ...$variableTypes): GenericType
{
return new GenericType($mainType, ...$variableTypes);
}
/**
* @template T of Type
*
* @param T|null $bound
*
* @return ($bound is null ? TemplateType<BuiltinType<TypeIdentifier::MIXED>> : TemplateType<T>)
*/
public static function template(string $name, ?Type $bound = null): TemplateType
{
return new TemplateType($name, $bound ?? Type::mixed());
}
/**
* @template T of Type
*
* @param list<T> $types
*
* @return UnionType<T>|NullableType<T>
*/
public static function union(Type ...$types): UnionType
{
/** @var list<T> $unionTypes */
$unionTypes = [];
$nullableUnion = false;
$isNullable = static fn (Type $type): bool => $type instanceof BuiltinType && TypeIdentifier::NULL === $type->getTypeIdentifier();
foreach ($types as $type) {
if ($type instanceof NullableType) {
$nullableUnion = true;
$type = $type->getWrappedType();
}
if ($type instanceof UnionType) {
foreach ($type->getTypes() as $unionType) {
if ($isNullable($unionType)) {
$nullableUnion = true;
continue;
}
$unionTypes[] = $unionType;
}
continue;
}
if ($isNullable($type)) {
$nullableUnion = true;
continue;
}
$unionTypes[] = $type;
}
if (1 === \count($unionTypes)) {
return self::nullable($unionTypes[0]);
}
$unionType = new UnionType(...$unionTypes);
return $nullableUnion ? self::nullable($unionType) : $unionType;
}
/**
* @template T of ObjectType|GenericType<ObjectType>|CollectionType<GenericType<ObjectType>>
*
* @param list<T|IntersectionType<T>> $types
*
* @return IntersectionType<T>
*/
public static function intersection(Type ...$types): IntersectionType
{
/** @var list<T> $intersectionTypes */
$intersectionTypes = [];
foreach ($types as $type) {
if (!$type instanceof IntersectionType) {
$intersectionTypes[] = $type;
continue;
}
foreach ($type->getTypes() as $intersectionType) {
$intersectionTypes[] = $intersectionType;
}
}
return new IntersectionType(...$intersectionTypes);
}
/**
* @template T of Type
*
* @param T $type
*
* @return T|NullableType<T>
*/
public static function nullable(Type $type): Type
{
if ($type->isNullable()) {
return $type;
}
return new NullableType($type);
}
public static function fromValue(mixed $value): Type
{
$type = match ($value) {
null => self::null(),
true => self::true(),
false => self::false(),
default => null,
};
if (null !== $type) {
return $type;
}
if (\is_callable($value)) {
return Type::callable();
}
if (\is_resource($value)) {
return Type::resource();
}
$type = match (get_debug_type($value)) {
TypeIdentifier::INT->value => self::int(),
TypeIdentifier::FLOAT->value => self::float(),
TypeIdentifier::STRING->value => self::string(),
default => null,
};
if (null !== $type) {
return $type;
}
$type = match (true) {
$value instanceof \UnitEnum => Type::enum($value::class),
\is_object($value) => \stdClass::class === $value::class ? self::object() : self::object($value::class),
\is_array($value) => self::builtin(TypeIdentifier::ARRAY),
default => null,
};
if (null === $type) {
return Type::mixed();
}
if (is_iterable($value)) {
/** @var list<BuiltinType<TypeIdentifier::INT>|BuiltinType<TypeIdentifier::STRING>> $keyTypes */
$keyTypes = [];
/** @var list<Type> $valueTypes */
$valueTypes = [];
foreach ($value as $k => $v) {
$keyTypes[] = self::fromValue($k);
$valueTypes[] = self::fromValue($v);
}
if ($keyTypes) {
$keyTypes = array_values(array_unique($keyTypes));
$keyType = \count($keyTypes) > 1 ? self::union(...$keyTypes) : $keyTypes[0];
} else {
$keyType = Type::arrayKey();
}
$valueType = $valueTypes ? CollectionType::mergeCollectionValueTypes($valueTypes) : Type::mixed();
return self::collection($type, $valueType, $keyType, \is_array($value) && [] !== $value && array_is_list($value));
}
if ($value instanceof \ArrayAccess) {
return self::collection($type);
}
return $type;
}
}
-60
View File
@@ -1,60 +0,0 @@
<?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\TypeInfo;
/**
* Identifier of a PHP native type.
*
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*/
enum TypeIdentifier: string
{
case ARRAY = 'array';
case BOOL = 'bool';
case CALLABLE = 'callable';
case FALSE = 'false';
case FLOAT = 'float';
case INT = 'int';
case ITERABLE = 'iterable';
case MIXED = 'mixed';
case NULL = 'null';
case OBJECT = 'object';
case RESOURCE = 'resource';
case STRING = 'string';
case TRUE = 'true';
case NEVER = 'never';
case VOID = 'void';
/**
* @return list<string>
*/
public static function values(): array
{
return array_column(self::cases(), 'value');
}
public function isStandalone(): bool
{
return \in_array($this, [self::MIXED, self::NEVER, self::VOID], true);
}
public function isScalar(): bool
{
return \in_array($this, [self::STRING, self::FLOAT, self::INT, self::BOOL, self::FALSE, self::TRUE], true);
}
public function isBool(): bool
{
return \in_array($this, [self::BOOL, self::FALSE, self::TRUE], true);
}
}
@@ -1,104 +0,0 @@
<?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\TypeInfo\TypeResolver;
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
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 PHPStan\PhpDocParser\ParserConfig;
use Symfony\Component\TypeInfo\Exception\UnsupportedException;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\TypeContext\TypeContext;
use Symfony\Component\TypeInfo\TypeContext\TypeContextFactory;
/**
* Resolves type on reflection prioriziting PHP documentation.
*
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
*/
final class PhpDocAwareReflectionTypeResolver implements TypeResolverInterface
{
private readonly PhpDocParser $phpDocParser;
private readonly Lexer $lexer;
public function __construct(
private readonly TypeResolverInterface $reflectionTypeResolver,
private readonly TypeResolverInterface $stringTypeResolver,
private readonly TypeContextFactory $typeContextFactory,
?PhpDocParser $phpDocParser = null,
?Lexer $lexer = null,
) {
if (class_exists(ParserConfig::class)) {
$this->lexer = $lexer ?? new Lexer(new ParserConfig([]));
$this->phpDocParser = $phpDocParser ?? new PhpDocParser(
$config = new ParserConfig([]),
new TypeParser($config, $constExprParser = new ConstExprParser($config)),
$constExprParser,
);
} else {
$this->lexer = $lexer ?? new Lexer();
$this->phpDocParser = $phpDocParser ?? new PhpDocParser(
new TypeParser($constExprParser = new ConstExprParser()),
$constExprParser,
);
}
}
public function resolve(mixed $subject, ?TypeContext $typeContext = null): Type
{
if (!$subject instanceof \ReflectionProperty && !$subject instanceof \ReflectionParameter && !$subject instanceof \ReflectionFunctionAbstract) {
throw new UnsupportedException(\sprintf('Expected subject to be a "ReflectionProperty", a "ReflectionParameter" or a "ReflectionFunctionAbstract", "%s" given.', get_debug_type($subject)), $subject);
}
$typeContext ??= $this->typeContextFactory->createFromReflection($subject);
$docComments = match (true) {
$subject instanceof \ReflectionProperty => $subject->isPromoted()
? ['@var' => $subject->getDocComment(), '@param' => $subject->getDeclaringClass()?->getConstructor()?->getDocComment()]
: ['@var' => $subject->getDocComment()],
$subject instanceof \ReflectionParameter => ['@param' => $subject->getDeclaringFunction()->getDocComment()],
$subject instanceof \ReflectionFunctionAbstract => ['@return' => $subject->getDocComment()],
};
foreach ($docComments as $tagName => $docComment) {
if (!$docComment) {
continue;
}
$tokens = new TokenIterator($this->lexer->tokenize($docComment));
$docNode = $this->phpDocParser->parse($tokens);
foreach ($docNode->getTagsByName($tagName) as $tag) {
$tagValue = $tag->value;
if ('@var' === $tagName && $tagValue instanceof VarTagValueNode) {
return $this->stringTypeResolver->resolve((string) $tagValue, $typeContext);
}
if ('@param' === $tagName && $tagValue instanceof ParamTagValueNode && '$'.$subject->getName() === $tagValue->parameterName) {
return $this->stringTypeResolver->resolve((string) $tagValue, $typeContext);
}
if ('@return' === $tagName && $tagValue instanceof ReturnTagValueNode) {
return $this->stringTypeResolver->resolve((string) $tagValue, $typeContext);
}
}
}
return $this->reflectionTypeResolver->resolve($subject);
}
}
@@ -1,51 +0,0 @@
<?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\TypeInfo\TypeResolver;
use Symfony\Component\TypeInfo\Exception\UnsupportedException;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\TypeContext\TypeContext;
use Symfony\Component\TypeInfo\TypeContext\TypeContextFactory;
/**
* Resolves type for a given parameter reflection.
*
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*/
final class ReflectionParameterTypeResolver implements TypeResolverInterface
{
public function __construct(
private readonly ReflectionTypeResolver $reflectionTypeResolver,
private readonly TypeContextFactory $typeContextFactory,
) {
}
public function resolve(mixed $subject, ?TypeContext $typeContext = null): Type
{
if (!$subject instanceof \ReflectionParameter) {
throw new UnsupportedException(\sprintf('Expected subject to be a "ReflectionParameter", "%s" given.', get_debug_type($subject)), $subject);
}
$typeContext ??= $this->typeContextFactory->createFromReflection($subject);
try {
return $this->reflectionTypeResolver->resolve($subject->getType(), $typeContext);
} catch (UnsupportedException $e) {
$path = null !== $typeContext
? \sprintf('%s::%s($%s)', $typeContext->calledClassName, $subject->getDeclaringFunction()->getName(), $subject->getName())
: \sprintf('%s($%s)', $subject->getDeclaringFunction()->getName(), $subject->getName());
throw new UnsupportedException(\sprintf('Cannot resolve type for "%s".', $path), $subject, previous: $e);
}
}
}
@@ -1,49 +0,0 @@
<?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\TypeInfo\TypeResolver;
use Symfony\Component\TypeInfo\Exception\UnsupportedException;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\TypeContext\TypeContext;
use Symfony\Component\TypeInfo\TypeContext\TypeContextFactory;
/**
* Resolves type for a given property reflection.
*
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*/
final class ReflectionPropertyTypeResolver implements TypeResolverInterface
{
public function __construct(
private readonly ReflectionTypeResolver $reflectionTypeResolver,
private readonly TypeContextFactory $typeContextFactory,
) {
}
public function resolve(mixed $subject, ?TypeContext $typeContext = null): Type
{
if (!$subject instanceof \ReflectionProperty) {
throw new UnsupportedException(\sprintf('Expected subject to be a "ReflectionProperty", "%s" given.', get_debug_type($subject)), $subject);
}
$typeContext ??= $this->typeContextFactory->createFromReflection($subject);
try {
return $this->reflectionTypeResolver->resolve($subject->getType(), $typeContext);
} catch (UnsupportedException $e) {
$path = \sprintf('%s::$%s', $subject->getDeclaringClass()->getName(), $subject->getName());
throw new UnsupportedException(\sprintf('Cannot resolve type for "%s".', $path), $subject, previous: $e);
}
}
}
@@ -1,51 +0,0 @@
<?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\TypeInfo\TypeResolver;
use Symfony\Component\TypeInfo\Exception\UnsupportedException;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\TypeContext\TypeContext;
use Symfony\Component\TypeInfo\TypeContext\TypeContextFactory;
/**
* Resolves return type for a given function reflection.
*
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*/
final class ReflectionReturnTypeResolver implements TypeResolverInterface
{
public function __construct(
private readonly ReflectionTypeResolver $reflectionTypeResolver,
private readonly TypeContextFactory $typeContextFactory,
) {
}
public function resolve(mixed $subject, ?TypeContext $typeContext = null): Type
{
if (!$subject instanceof \ReflectionFunctionAbstract) {
throw new UnsupportedException(\sprintf('Expected subject to be a "ReflectionFunctionAbstract", "%s" given.', get_debug_type($subject)), $subject);
}
$typeContext ??= $this->typeContextFactory->createFromReflection($subject);
try {
return $this->reflectionTypeResolver->resolve($subject->getReturnType(), $typeContext);
} catch (UnsupportedException $e) {
$path = null !== $typeContext
? \sprintf('%s::%s()', $typeContext->calledClassName, $subject->getName())
: \sprintf('%s()', $subject->getName());
throw new UnsupportedException(\sprintf('Cannot resolve type for "%s".', $path), $subject, previous: $e);
}
}
}
@@ -1,87 +0,0 @@
<?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\TypeInfo\TypeResolver;
use Symfony\Component\TypeInfo\Exception\InvalidArgumentException;
use Symfony\Component\TypeInfo\Exception\UnsupportedException;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\TypeContext\TypeContext;
use Symfony\Component\TypeInfo\TypeIdentifier;
/**
* Resolves type for a given type reflection.
*
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*/
final class ReflectionTypeResolver implements TypeResolverInterface
{
public function resolve(mixed $subject, ?TypeContext $typeContext = null): Type
{
if ($subject instanceof \ReflectionUnionType) {
return Type::union(...array_map(fn (mixed $t): Type => $this->resolve($t, $typeContext), $subject->getTypes()));
}
if ($subject instanceof \ReflectionIntersectionType) {
return Type::intersection(...array_map(fn (mixed $t): Type => $this->resolve($t, $typeContext), $subject->getTypes()));
}
if (!$subject instanceof \ReflectionNamedType) {
throw new UnsupportedException(\sprintf('Expected subject to be a "ReflectionNamedType", a "ReflectionUnionType" or a "ReflectionIntersectionType", "%s" given.', get_debug_type($subject)), $subject);
}
$identifier = $subject->getName();
$nullable = $subject->allowsNull();
if (TypeIdentifier::ARRAY->value === $identifier) {
$type = Type::array();
return $nullable ? Type::nullable($type) : $type;
}
if (TypeIdentifier::ITERABLE->value === $identifier) {
$type = Type::iterable();
return $nullable ? Type::nullable($type) : $type;
}
if (TypeIdentifier::NULL->value === $identifier || TypeIdentifier::MIXED->value === $identifier) {
return Type::builtin($identifier);
}
if ($subject->isBuiltin()) {
$type = Type::builtin(TypeIdentifier::from($identifier));
return $nullable ? Type::nullable($type) : $type;
}
if (\in_array(strtolower($identifier), ['self', 'static', 'parent'], true) && !$typeContext) {
throw new InvalidArgumentException(\sprintf('A "%s" must be provided to resolve "%s".', TypeContext::class, strtolower($identifier)));
}
/** @var class-string $className */
$className = match (true) {
'self' === strtolower($identifier) => $typeContext->getDeclaringClass(),
'static' === strtolower($identifier) => $typeContext->getCalledClass(),
'parent' === strtolower($identifier) => $typeContext->getParentClass(),
default => $identifier,
};
if (is_subclass_of($className, \UnitEnum::class) && !interface_exists($className)) {
$type = Type::enum($className);
} else {
$type = Type::object($className);
}
return $nullable ? Type::nullable($type) : $type;
}
}
@@ -1,368 +0,0 @@
<?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\TypeInfo\TypeResolver;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFalseNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFloatNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprNullNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprTrueNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode;
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode;
use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode;
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode;
use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode;
use PHPStan\PhpDocParser\Ast\Type\ObjectShapeNode;
use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
use PHPStan\PhpDocParser\Lexer\Lexer;
use PHPStan\PhpDocParser\Parser\ConstExprParser;
use PHPStan\PhpDocParser\Parser\TokenIterator;
use PHPStan\PhpDocParser\Parser\TypeParser;
use PHPStan\PhpDocParser\ParserConfig;
use Symfony\Component\TypeInfo\Exception\InvalidArgumentException;
use Symfony\Component\TypeInfo\Exception\UnsupportedException;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\Type\BackedEnumType;
use Symfony\Component\TypeInfo\Type\BuiltinType;
use Symfony\Component\TypeInfo\Type\CollectionType;
use Symfony\Component\TypeInfo\Type\GenericType;
use Symfony\Component\TypeInfo\Type\ObjectType;
use Symfony\Component\TypeInfo\TypeContext\TypeContext;
use Symfony\Component\TypeInfo\TypeIdentifier;
/**
* Resolves type for a given string.
*
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*/
final class StringTypeResolver implements TypeResolverInterface
{
/**
* @var array<string, bool>
*/
private static array $classExistCache = [];
private readonly Lexer $lexer;
private readonly TypeParser $parser;
/**
* @param array<string, string> $extraTypeAliases
*/
public function __construct(
?Lexer $lexer = null,
?TypeParser $parser = null,
private readonly array $extraTypeAliases = [],
) {
if (class_exists(ParserConfig::class)) {
$this->lexer = $lexer ?? new Lexer(new ParserConfig([]));
$this->parser = $parser ?? new TypeParser($config = new ParserConfig([]), new ConstExprParser($config));
} else {
$this->lexer = $lexer ?? new Lexer();
$this->parser = $parser ?? new TypeParser(new ConstExprParser());
}
}
public function resolve(mixed $subject, ?TypeContext $typeContext = null): Type
{
if ($subject instanceof \Stringable) {
$subject = (string) $subject;
} elseif (!\is_string($subject)) {
throw new UnsupportedException(\sprintf('Expected subject to be a "string", "%s" given.', get_debug_type($subject)), $subject);
}
try {
$tokens = new TokenIterator($this->lexer->tokenize($subject));
$node = $this->parser->parse($tokens);
return $this->getTypeFromNode($node, $typeContext);
} catch (\DomainException $e) {
throw new UnsupportedException(\sprintf('Cannot resolve "%s".', $subject), $subject, previous: $e);
}
}
private function getTypeFromNode(TypeNode $node, ?TypeContext $typeContext): Type
{
$typeIsCollectionObject = static fn (Type $type): bool => $type->isIdentifiedBy(\Traversable::class) || $type->isIdentifiedBy(\ArrayAccess::class);
if ($node instanceof CallableTypeNode) {
return Type::callable();
}
if ($node instanceof ArrayTypeNode) {
return Type::array($this->getTypeFromNode($node->type, $typeContext));
}
if ($node instanceof ArrayShapeNode) {
$shape = [];
foreach ($node->items as $item) {
$shape[(string) $item->keyName] = [
'type' => $this->getTypeFromNode($item->valueType, $typeContext),
'optional' => $item->optional,
];
}
return Type::arrayShape(
$shape,
$node->sealed,
$node->unsealedType?->keyType ? $this->getTypeFromNode($node->unsealedType->keyType, $typeContext) : null,
$node->unsealedType?->valueType ? $this->getTypeFromNode($node->unsealedType->valueType, $typeContext) : null,
);
}
if ($node instanceof ObjectShapeNode) {
return Type::object();
}
if ($node instanceof ThisTypeNode) {
if (null === $typeContext) {
throw new InvalidArgumentException(\sprintf('A "%s" must be provided to resolve "$this".', TypeContext::class));
}
return Type::object($typeContext->getCalledClass());
}
if ($node instanceof ConstTypeNode) {
if ($node->constExpr instanceof ConstFetchNode) {
$className = match (strtolower($node->constExpr->className)) {
'self' => $typeContext->getDeclaringClass(),
'static' => $typeContext->getCalledClass(),
'parent' => $typeContext->getParentClass(),
default => null,
};
if (null === $className) {
$classType = $this->resolveCustomIdentifier($node->constExpr->className, $typeContext);
if (!$classType instanceof ObjectType) {
return Type::mixed();
}
$className = $classType->getClassName();
}
if (!class_exists($className)) {
return Type::mixed();
}
$types = [];
foreach ((new \ReflectionClass($className))->getReflectionConstants() as $const) {
if (preg_match('/^'.str_replace('\*', '.*', preg_quote($node->constExpr->name, '/')).'$/', $const->getName())) {
$types[] = Type::fromValue($const->getValue());
}
}
return CollectionType::mergeCollectionValueTypes($types);
}
return match ($node->constExpr::class) {
ConstExprArrayNode::class => Type::array(),
ConstExprFalseNode::class => Type::false(),
ConstExprFloatNode::class => Type::float(),
ConstExprIntegerNode::class => Type::int(),
ConstExprNullNode::class => Type::null(),
ConstExprStringNode::class => Type::string(),
ConstExprTrueNode::class => Type::true(),
default => throw new \DomainException(\sprintf('Unhandled "%s" constant expression.', $node->constExpr::class)),
};
}
if ($node instanceof IdentifierTypeNode) {
$type = match ($node->name) {
'bool', 'boolean' => Type::bool(),
'true' => Type::true(),
'false' => Type::false(),
'int', 'integer', 'positive-int', 'negative-int', 'non-positive-int', 'non-negative-int', 'non-zero-int' => Type::int(),
'float', 'double' => Type::float(),
'string',
'class-string',
'trait-string',
'interface-string',
'callable-string',
'numeric-string',
'lowercase-string',
'non-empty-lowercase-string',
'non-empty-string',
'non-falsy-string',
'truthy-string',
'literal-string',
'html-escaped-string' => Type::string(),
'resource' => Type::resource(),
'object' => Type::object(),
'callable' => Type::callable(),
'array', 'non-empty-array' => Type::array(),
'list', 'non-empty-list' => Type::list(),
'iterable' => Type::iterable(),
'mixed' => Type::mixed(),
'null' => Type::null(),
'array-key' => Type::arrayKey(),
'scalar' => Type::union(Type::int(), Type::float(), Type::string(), Type::bool()),
'number' => Type::union(Type::int(), Type::float()),
'numeric' => Type::union(Type::int(), Type::float(), Type::string()),
'self' => $typeContext ? Type::object($typeContext->getDeclaringClass()) : throw new InvalidArgumentException(\sprintf('A "%s" must be provided to resolve "self".', TypeContext::class)),
'static' => $typeContext ? Type::object($typeContext->getCalledClass()) : throw new InvalidArgumentException(\sprintf('A "%s" must be provided to resolve "static".', TypeContext::class)),
'parent' => $typeContext ? Type::object($typeContext->getParentClass()) : throw new InvalidArgumentException(\sprintf('A "%s" must be provided to resolve "parent".', TypeContext::class)),
'void' => Type::void(),
'never', 'never-return', 'never-returns', 'no-return' => Type::never(),
default => $this->resolveCustomIdentifier($node->name, $typeContext),
};
if ($typeIsCollectionObject($type)) {
return Type::collection($type);
}
return $type;
}
if ($node instanceof NullableTypeNode) {
return Type::nullable($this->getTypeFromNode($node->type, $typeContext));
}
if ($node instanceof GenericTypeNode) {
if ($node->type instanceof IdentifierTypeNode && 'value-of' === $node->type->name) {
$type = $this->getTypeFromNode($node->genericTypes[0], $typeContext);
if ($type instanceof BackedEnumType) {
return $type->getBackingType();
}
if ($type instanceof CollectionType) {
return $type->getCollectionValueType();
}
throw new \DomainException(\sprintf('"%s" is not a valid type for "value-of".', $node->genericTypes[0]));
}
if ($node->type instanceof IdentifierTypeNode && 'key-of' === $node->type->name) {
$type = $this->getTypeFromNode($node->genericTypes[0], $typeContext);
if ($type instanceof CollectionType) {
return $type->getCollectionKeyType();
}
throw new \DomainException(\sprintf('"%s" is not a valid type for "key-of".', $node->genericTypes[0]));
}
$type = $this->getTypeFromNode($node->type, $typeContext);
// handle integer ranges as simple integers
if ($type->isIdentifiedBy(TypeIdentifier::INT)) {
return $type;
}
$variableTypes = array_map(fn (TypeNode $t): Type => $this->getTypeFromNode($t, $typeContext), $node->genericTypes);
if ($type instanceof CollectionType) {
$asList = $type->isList();
$keyType = $type->getCollectionKeyType();
$type = $type->getWrappedType();
if ($type instanceof GenericType) {
$type = $type->getWrappedType();
}
if (1 === \count($variableTypes)) {
return new CollectionType(Type::generic($type, $keyType, $variableTypes[0]), $asList);
} elseif (2 === \count($variableTypes)) {
if ($asList) {
throw new \DomainException(\sprintf('"%s" type cannot have a key type defined.', $node->type));
}
return Type::collection($type, $variableTypes[1], $variableTypes[0], $asList);
}
}
if ($typeIsCollectionObject($type)) {
return match (\count($variableTypes)) {
1 => Type::collection($type, $variableTypes[0]),
2 => Type::collection($type, $variableTypes[1], $variableTypes[0]),
default => Type::collection($type),
};
}
if ($type instanceof BuiltinType && TypeIdentifier::ARRAY !== $type->getTypeIdentifier() && TypeIdentifier::ITERABLE !== $type->getTypeIdentifier()) {
return $type;
}
return Type::generic($type, ...$variableTypes);
}
if ($node instanceof UnionTypeNode) {
$types = [];
foreach ($node->types as $nodeType) {
$type = $this->getTypeFromNode($nodeType, $typeContext);
if ($type instanceof BuiltinType && TypeIdentifier::MIXED === $type->getTypeIdentifier()) {
return Type::mixed();
}
$types[] = $type;
}
return Type::union(...$types);
}
if ($node instanceof IntersectionTypeNode) {
return Type::intersection(...array_map(fn (TypeNode $t): Type => $this->getTypeFromNode($t, $typeContext), $node->types));
}
throw new \DomainException(\sprintf('Unhandled "%s" node.', $node::class));
}
private function resolveCustomIdentifier(string $identifier, ?TypeContext $typeContext): Type
{
$className = $typeContext ? $typeContext->normalize($identifier) : $identifier;
if (!isset(self::$classExistCache[$className])) {
self::$classExistCache[$className] = false;
if (class_exists($className) || interface_exists($className)) {
self::$classExistCache[$className] = true;
} else {
try {
new \ReflectionClass($className);
self::$classExistCache[$className] = true;
} catch (\Throwable) {
}
}
}
if (self::$classExistCache[$className]) {
if (is_subclass_of($className, \UnitEnum::class) && !interface_exists($className)) {
return Type::enum($className);
}
return Type::object($className);
}
if (isset($typeContext?->templates[$identifier])) {
return Type::template($identifier, $typeContext->templates[$identifier]);
}
if (isset($typeContext?->typeAliases[$identifier])) {
return $typeContext->typeAliases[$identifier];
}
if (isset($this->extraTypeAliases[$identifier])) {
return $this->resolve($this->extraTypeAliases[$identifier]);
}
throw new \DomainException(\sprintf('Unhandled "%s" identifier.', $identifier));
}
}
-106
View File
@@ -1,106 +0,0 @@
<?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\TypeInfo\TypeResolver;
use PHPStan\PhpDocParser\Parser\PhpDocParser;
use Psr\Container\ContainerInterface;
use Symfony\Component\TypeInfo\Exception\UnsupportedException;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\TypeContext\TypeContext;
use Symfony\Component\TypeInfo\TypeContext\TypeContextFactory;
/**
* Resolves type for a given subject by delegating resolving to nested type resolvers.
*
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*/
final class TypeResolver implements TypeResolverInterface
{
/**
* @param ContainerInterface $resolvers Locator of type resolvers, keyed by supported subject type
*/
public function __construct(
private readonly ContainerInterface $resolvers,
) {
}
public function resolve(mixed $subject, ?TypeContext $typeContext = null): Type
{
$subjectType = match (\is_object($subject)) {
true => match (true) {
is_subclass_of($subject::class, \ReflectionType::class) => \ReflectionType::class,
is_subclass_of($subject::class, \ReflectionFunctionAbstract::class) => \ReflectionFunctionAbstract::class,
default => $subject::class,
},
false => get_debug_type($subject),
};
if (!$this->resolvers->has($subjectType)) {
if ('string' === $subjectType) {
throw new UnsupportedException('Cannot find any resolver for "string" type. Try running "composer require phpstan/phpdoc-parser".', $subject);
}
throw new UnsupportedException(\sprintf('Cannot find any resolver for "%s" type.', $subjectType), $subject);
}
/** @param TypeResolverInterface $resolver */
$resolver = $this->resolvers->get($subjectType);
return $resolver->resolve($subject, $typeContext);
}
/**
* @param array<string, TypeResolverInterface>|null $resolvers
*/
public static function create(?array $resolvers = null): self
{
if (null === $resolvers) {
$stringTypeResolver = class_exists(PhpDocParser::class) ? new StringTypeResolver() : null;
$typeContextFactory = new TypeContextFactory($stringTypeResolver);
$reflectionTypeResolver = new ReflectionTypeResolver();
$resolvers = [
\ReflectionType::class => $reflectionTypeResolver,
\ReflectionParameter::class => new ReflectionParameterTypeResolver($reflectionTypeResolver, $typeContextFactory),
\ReflectionProperty::class => new ReflectionPropertyTypeResolver($reflectionTypeResolver, $typeContextFactory),
\ReflectionFunctionAbstract::class => new ReflectionReturnTypeResolver($reflectionTypeResolver, $typeContextFactory),
];
if (null !== $stringTypeResolver) {
$resolvers['string'] = $stringTypeResolver;
$resolvers[\ReflectionParameter::class] = new PhpDocAwareReflectionTypeResolver($resolvers[\ReflectionParameter::class], $stringTypeResolver, $typeContextFactory);
$resolvers[\ReflectionProperty::class] = new PhpDocAwareReflectionTypeResolver($resolvers[\ReflectionProperty::class], $stringTypeResolver, $typeContextFactory);
$resolvers[\ReflectionFunctionAbstract::class] = new PhpDocAwareReflectionTypeResolver($resolvers[\ReflectionFunctionAbstract::class], $stringTypeResolver, $typeContextFactory);
}
}
$resolversContainer = new class($resolvers) implements ContainerInterface {
public function __construct(
private readonly array $resolvers,
) {
}
public function has(string $id): bool
{
return isset($this->resolvers[$id]);
}
public function get(string $id): TypeResolverInterface
{
return $this->resolvers[$id];
}
};
return new self($resolversContainer);
}
}
@@ -1,33 +0,0 @@
<?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\TypeInfo\TypeResolver;
use Symfony\Component\TypeInfo\Exception\UnsupportedException;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\TypeContext\TypeContext;
/**
* Resolves type for a given subject.
*
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
* @author Baptiste Leduc <baptiste.leduc@gmail.com>
*/
interface TypeResolverInterface
{
/**
* Try to resolve a {@see Type} on a $subject.
* If the resolver cannot resolve the type, it will throw a {@see UnsupportedException}.
*
* @throws UnsupportedException
*/
public function resolve(mixed $subject, ?TypeContext $typeContext = null): Type;
}
-45
View File
@@ -1,45 +0,0 @@
{
"name": "symfony/type-info",
"type": "library",
"description": "Extracts PHP types information.",
"keywords": [
"type",
"phpdoc",
"phpstan",
"symfony"
],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Mathias Arlaud",
"email": "mathias.arlaud@gmail.com"
},
{
"name": "Baptiste LEDUC",
"email": "baptiste.leduc@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": ">=8.2",
"psr/container": "^1.1|^2.0",
"symfony/deprecation-contracts": "^2.5|^3"
},
"require-dev": {
"phpstan/phpdoc-parser": "^1.30|^2.0"
},
"conflict": {
"phpstan/phpdoc-parser": "<1.30"
},
"autoload": {
"psr-4": { "Symfony\\Component\\TypeInfo\\": "" },
"exclude-from-classmap": [
"/Tests/"
]
},
"minimum-stability": "dev"
}