Ubah dari Swagger ke Dedoc lalu buat API Ticket dan TicketType
This commit is contained in:
-40
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Vendored
-19
@@ -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.
|
||||
Vendored
-54
@@ -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)
|
||||
Vendored
-108
@@ -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
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.'>';
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user