├── .phpdoc
└── template
│ └── base.html.twig
├── LICENSE
├── README.md
├── composer.json
├── docs
├── getting-started.rst
└── index.rst
├── phpdoc.dist.xml
└── src
├── FqsenResolver.php
├── PseudoType.php
├── PseudoTypes
├── ArrayShape.php
├── ArrayShapeItem.php
├── CallableString.php
├── ConstExpression.php
├── False_.php
├── FloatValue.php
├── HtmlEscapedString.php
├── IntegerRange.php
├── IntegerValue.php
├── ListShape.php
├── ListShapeItem.php
├── List_.php
├── LiteralString.php
├── LowercaseString.php
├── NegativeInteger.php
├── NonEmptyArray.php
├── NonEmptyList.php
├── NonEmptyLowercaseString.php
├── NonEmptyString.php
├── NumericString.php
├── Numeric_.php
├── ObjectShape.php
├── ObjectShapeItem.php
├── PositiveInteger.php
├── ShapeItem.php
├── StringValue.php
├── TraitString.php
└── True_.php
├── Type.php
├── TypeResolver.php
└── Types
├── AbstractList.php
├── AggregatedType.php
├── ArrayKey.php
├── Array_.php
├── Boolean.php
├── CallableParameter.php
├── Callable_.php
├── ClassString.php
├── Collection.php
├── Compound.php
├── Context.php
├── ContextFactory.php
├── Expression.php
├── Float_.php
├── Integer.php
├── InterfaceString.php
├── Intersection.php
├── Iterable_.php
├── Mixed_.php
├── Never_.php
├── Null_.php
├── Nullable.php
├── Object_.php
├── Parent_.php
├── Resource_.php
├── Scalar.php
├── Self_.php
├── Static_.php
├── String_.php
├── This.php
└── Void_.php
/.phpdoc/template/base.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'layout.html.twig' %}
2 |
3 | {% set topMenu = {
4 | "menu": [
5 | { "name": "About", "url": "https://phpdoc.org/"},
6 | { "name": "Components", "url": "https://phpdoc.org/components.html"},
7 | { "name": "Documentation", "url": "https://docs.phpdoc.org/"},
8 | ],
9 | "social": [
10 | { "iconClass": "fab fa-mastodon", "url": "https://phpc.social/@phpdoc"},
11 | { "iconClass": "fab fa-github", "url": "https://github.com/phpdocumentor/typeresolver"},
12 | { "iconClass": "fas fa-envelope-open-text", "url": "https://github.com/orgs/phpDocumentor/discussions"}
13 | ]
14 | }
15 | %}
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2010 Mike van Riel
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://opensource.org/licenses/MIT)
2 | 
3 | [](https://coveralls.io/github/phpDocumentor/TypeResolver?branch=1.x)
4 | [](https://scrutinizer-ci.com/g/phpDocumentor/TypeResolver/?branch=1.x)
5 | [](https://scrutinizer-ci.com/g/phpDocumentor/TypeResolver/?branch=1.x)
6 | 
7 | 
8 |
9 | TypeResolver and FqsenResolver
10 | ==============================
11 |
12 | The specification on types in DocBlocks (PSR-5) describes various keywords and special constructs
13 | but also how to statically resolve the partial name of a Class into a Fully Qualified Class Name (FQCN).
14 |
15 | PSR-5 also introduces an additional way to describe deeper elements than Classes, Interfaces and Traits
16 | called the Fully Qualified Structural Element Name (FQSEN). Using this it is possible to refer to methods,
17 | properties and class constants but also functions and global constants.
18 |
19 | This package provides two Resolvers that are capable of
20 |
21 | 1. Returning a series of Value Object for given expression while resolving any partial class names, and
22 | 2. Returning an FQSEN object after resolving any partial Structural Element Names into Fully Qualified Structural
23 | Element names.
24 |
25 | ## Installing
26 |
27 | The easiest way to install this library is with [Composer](https://getcomposer.org) using the following command:
28 |
29 | $ composer require phpdocumentor/type-resolver
30 |
31 | ## Examples
32 |
33 | Ready to dive in and don't want to read through all that text below? Just consult the [examples](examples) folder and check which type of action that your want to accomplish.
34 |
35 | ## On Types and Element Names
36 |
37 | This component can be used in one of two ways
38 |
39 | 1. To resolve a Type or
40 | 2. To resolve a Fully Qualified Structural Element Name
41 |
42 | The big difference between these two is in the number of things it can resolve.
43 |
44 | The TypeResolver can resolve:
45 |
46 | - a php primitive or pseudo-primitive such as a string or void (`@var string` or `@return void`).
47 | - a composite such as an array of string (`@var string[]`).
48 | - a compound such as a string or integer (`@var string|integer`).
49 | - an array expression (`@var (string|TypeResolver)[]`)
50 | - an object or interface such as the TypeResolver class (`@var TypeResolver`
51 | or `@var \phpDocumentor\Reflection\TypeResolver`)
52 |
53 | > please note that if you want to pass partial class names that additional steps are necessary, see the
54 | > chapter `Resolving partial classes and FQSENs` for more information.
55 |
56 | Where the FqsenResolver can resolve:
57 |
58 | - Constant expressions (i.e. `@see \MyNamespace\MY_CONSTANT`)
59 | - Function expressions (i.e. `@see \MyNamespace\myFunction()`)
60 | - Class expressions (i.e. `@see \MyNamespace\MyClass`)
61 | - Interface expressions (i.e. `@see \MyNamespace\MyInterface`)
62 | - Trait expressions (i.e. `@see \MyNamespace\MyTrait`)
63 | - Class constant expressions (i.e. `@see \MyNamespace\MyClass::MY_CONSTANT`)
64 | - Property expressions (i.e. `@see \MyNamespace\MyClass::$myProperty`)
65 | - Method expressions (i.e. `@see \MyNamespace\MyClass::myMethod()`)
66 |
67 | ## Resolving a type
68 |
69 | In order to resolve a type you will have to instantiate the class `\phpDocumentor\Reflection\TypeResolver` and call its `resolve` method like this:
70 |
71 | ```php
72 | $typeResolver = new \phpDocumentor\Reflection\TypeResolver();
73 | $type = $typeResolver->resolve('string|integer');
74 | ```
75 |
76 | In this example you will receive a Value Object of class `\phpDocumentor\Reflection\Types\Compound` that has two
77 | elements, one of type `\phpDocumentor\Reflection\Types\String_` and one of type
78 | `\phpDocumentor\Reflection\Types\Integer`.
79 |
80 | The real power of this resolver is in its capability to expand partial class names into fully qualified class names; but in order to do that we need an additional `\phpDocumentor\Reflection\Types\Context` class that will inform the resolver in which namespace the given expression occurs and which namespace aliases (or imports) apply.
81 |
82 | ### Resolving nullable types
83 |
84 | Php 7.1 introduced nullable types e.g. `?string`. Type resolver will resolve the original type without the nullable notation `?`
85 | just like it would do without the `?`. After that the type is wrapped in a `\phpDocumentor\Reflection\Types\Nullable` object.
86 | The `Nullable` type has a method to fetch the actual type.
87 |
88 | ## Resolving an FQSEN
89 |
90 | A Fully Qualified Structural Element Name is a reference to another element in your code bases and can be resolved using the `\phpDocumentor\Reflection\FqsenResolver` class' `resolve` method, like this:
91 |
92 | ```php
93 | $fqsenResolver = new \phpDocumentor\Reflection\FqsenResolver();
94 | $fqsen = $fqsenResolver->resolve('\phpDocumentor\Reflection\FqsenResolver::resolve()');
95 | ```
96 |
97 | In this example we resolve a Fully Qualified Structural Element Name (meaning that it includes the full namespace, class name and element name) and receive a Value Object of type `\phpDocumentor\Reflection\Fqsen`.
98 |
99 | The real power of this resolver is in its capability to expand partial element names into Fully Qualified Structural Element Names; but in order to do that we need an additional `\phpDocumentor\Reflection\Types\Context` class that will inform the resolver in which namespace the given expression occurs and which namespace aliases (or imports) apply.
100 |
101 | ## Resolving partial Classes and Structural Element Names
102 |
103 | Perhaps the best feature of this library is that it knows how to resolve partial class names into fully qualified class names.
104 |
105 | For example, you have this file:
106 |
107 | ```php
108 | namespace My\Example;
109 |
110 | use phpDocumentor\Reflection\Types;
111 |
112 | class Classy
113 | {
114 | /**
115 | * @var Types\Context
116 | * @see Classy::otherFunction()
117 | */
118 | public function __construct($context) {}
119 |
120 | public function otherFunction(){}
121 | }
122 | ```
123 |
124 | Suppose that you would want to resolve (and expand) the type in the `@var` tag and the element name in the `@see` tag.
125 |
126 | For the resolvers to know how to expand partial names you have to provide a bit of _Context_ for them by instantiating a new class named `\phpDocumentor\Reflection\Types\Context` with the name of the namespace and the aliases that are in play.
127 |
128 | ### Creating a Context
129 |
130 | You can do this by manually creating a Context like this:
131 |
132 | ```php
133 | $context = new \phpDocumentor\Reflection\Types\Context(
134 | '\My\Example',
135 | [ 'Types' => '\phpDocumentor\Reflection\Types']
136 | );
137 | ```
138 |
139 | Or by using the `\phpDocumentor\Reflection\Types\ContextFactory` to instantiate a new context based on a Reflector object or by providing the namespace that you'd like to extract and the source code of the file in which the given type expression occurs.
140 |
141 | ```php
142 | $contextFactory = new \phpDocumentor\Reflection\Types\ContextFactory();
143 | $context = $contextFactory->createFromReflector(new ReflectionMethod('\My\Example\Classy', '__construct'));
144 | ```
145 |
146 | or
147 |
148 | ```php
149 | $contextFactory = new \phpDocumentor\Reflection\Types\ContextFactory();
150 | $context = $contextFactory->createForNamespace('\My\Example', file_get_contents('My/Example/Classy.php'));
151 | ```
152 |
153 | ### Using the Context
154 |
155 | After you have obtained a Context it is just a matter of passing it along with the `resolve` method of either Resolver class as second argument and the Resolvers will take this into account when resolving partial names.
156 |
157 | To obtain the resolved class name for the `@var` tag in the example above you can do:
158 |
159 | ```php
160 | $typeResolver = new \phpDocumentor\Reflection\TypeResolver();
161 | $type = $typeResolver->resolve('Types\Context', $context);
162 | ```
163 |
164 | When you do this you will receive an object of class `\phpDocumentor\Reflection\Types\Object_` for which you can call the `getFqsen` method to receive a Value Object that represents the complete FQSEN. So that would be `phpDocumentor\Reflection\Types\Context`.
165 |
166 | > Why is the FQSEN wrapped in another object `Object_`?
167 | >
168 | > The resolve method of the TypeResolver only returns object with the interface `Type` and the FQSEN is a common type that does not represent a Type. Also: in some cases a type can represent an "Untyped Object", meaning that it is an object (signified by the `object` keyword) but does not refer to a specific element using an FQSEN.
169 |
170 | Another example is on how to resolve the FQSEN of a method as can be seen with the `@see` tag in the example above. To resolve that you can do the following:
171 |
172 | ```php
173 | $fqsenResolver = new \phpDocumentor\Reflection\FqsenResolver();
174 | $type = $fqsenResolver->resolve('Classy::otherFunction()', $context);
175 | ```
176 |
177 | Because Classy is a Class in the current namespace its FQSEN will have the `My\Example` namespace and by calling the `resolve` method of the FQSEN Resolver you will receive an `Fqsen` object that refers to `\My\Example\Classy::otherFunction()`.
178 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "phpdocumentor/type-resolver",
3 | "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
4 | "type": "library",
5 | "license": "MIT",
6 | "authors": [
7 | {
8 | "name": "Mike van Riel",
9 | "email": "me@mikevanriel.com"
10 | }
11 | ],
12 | "require": {
13 | "php": "^7.3 || ^8.0",
14 | "phpdocumentor/reflection-common": "^2.0",
15 | "phpstan/phpdoc-parser": "^1.18|^2.0",
16 | "doctrine/deprecations": "^1.0"
17 | },
18 | "require-dev": {
19 | "ext-tokenizer": "*",
20 | "phpunit/phpunit": "^9.5",
21 | "phpstan/phpstan": "^1.8",
22 | "phpstan/phpstan-phpunit": "^1.1",
23 | "phpstan/extension-installer": "^1.1",
24 | "vimeo/psalm": "^4.25",
25 | "rector/rector": "^0.13.9",
26 | "phpbench/phpbench": "^1.2"
27 | },
28 | "autoload": {
29 | "psr-4": {
30 | "phpDocumentor\\Reflection\\": "src"
31 | }
32 | },
33 | "autoload-dev": {
34 | "psr-4": {
35 | "phpDocumentor\\Reflection\\": ["tests/unit", "tests/benchmark"]
36 | }
37 | },
38 | "extra": {
39 | "branch-alias": {
40 | "dev-1.x": "1.x-dev"
41 | }
42 | },
43 | "config": {
44 | "platform": {
45 | "php": "7.3.0"
46 | },
47 | "allow-plugins": {
48 | "phpstan/extension-installer": true
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/docs/getting-started.rst:
--------------------------------------------------------------------------------
1 | ===============
2 | Getting started
3 | ===============
4 |
5 | On this page you will find a brief introduction on how to use the TypeResolver in your project.
6 |
7 | Installation
8 | ============
9 |
10 | The TypeResolver is available on Packagist and can be installed using Composer:
11 |
12 | .. code:: bash
13 | composer require phpdocumentor/type-resolver
14 |
15 |
16 | General usage
17 | ===========
18 |
19 | After you installed the TypeResolver you can use it in your project. This can be done by creating a new instance
20 | of the :php:class:`\phpDocumentor\Reflection\TypeResolver` class and calling
21 | :php:method:`\phpDocumentor\Reflection\TypeResolver::resolve()` with the type you want to resolve.
22 |
23 | .. code:: php
24 | $typeResolver = new \phpDocumentor\Reflection\TypeResolver();
25 | $type = $typeResolver->resolve('string');
26 | echo get_class($type); // phpDocumentor\Reflection\Types\String_
27 |
28 | The real power of this resolver is in its capability to expand partial class names into fully qualified class names;
29 | but in order to do that we need an additional :php:class:`\phpDocumentor\Reflection\Types\Context` class that
30 | will inform the resolver in which namespace the given expression occurs and which namespace aliases (or imports) apply.
31 |
32 | Read more about the Context class in the next section.
33 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | =============
2 | Type resolver
3 | =============
4 |
5 | This project part of the phpDocumentor project. It is capable of creating an object structure of the type
6 | specifications found in the PHPDoc blocks of a project. This can be useful for static analysis of a project
7 | or other behavior that requires knowledge of the types used in a project like automatically build forms.
8 |
9 | This project aims to cover all types that are available in PHPDoc and PHP itself. And is open for extension by
10 | third party developers.
11 |
12 | .. toctree::
13 | :maxdepth: 2
14 | :hidden:
15 |
16 | index
17 | getting-started
18 |
--------------------------------------------------------------------------------
/phpdoc.dist.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 | Type Resolver
9 |
10 |
11 |
12 |
13 | latest
14 |
15 |
16 | src/
17 |
18 |
19 |
20 | tests/**/*
21 | build/**/*
22 | var/**/*
23 | vendor/**/*
24 |
25 |
26 | php
27 |
28 |
29 | template
30 | template-extends
31 | template-implements
32 | extends
33 | implements
34 |
35 | phpDocumentor
36 |
37 |
38 |
39 | docs
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/FqsenResolver.php:
--------------------------------------------------------------------------------
1 | isFqsen($fqsen)) {
40 | return new Fqsen($fqsen);
41 | }
42 |
43 | return $this->resolvePartialStructuralElementName($fqsen, $context);
44 | }
45 |
46 | /**
47 | * Tests whether the given type is a Fully Qualified Structural Element Name.
48 | */
49 | private function isFqsen(string $type): bool
50 | {
51 | return strpos($type, self::OPERATOR_NAMESPACE) === 0;
52 | }
53 |
54 | /**
55 | * Resolves a partial Structural Element Name (i.e. `Reflection\DocBlock`) to its FQSEN representation
56 | * (i.e. `\phpDocumentor\Reflection\DocBlock`) based on the Namespace and aliases mentioned in the Context.
57 | *
58 | * @throws InvalidArgumentException When type is not a valid FQSEN.
59 | */
60 | private function resolvePartialStructuralElementName(string $type, Context $context): Fqsen
61 | {
62 | $typeParts = explode(self::OPERATOR_NAMESPACE, $type, 2);
63 |
64 | $namespaceAliases = $context->getNamespaceAliases();
65 |
66 | // if the first segment is not an alias; prepend namespace name and return
67 | if (!isset($namespaceAliases[$typeParts[0]])) {
68 | $namespace = $context->getNamespace();
69 | if ($namespace !== '') {
70 | $namespace .= self::OPERATOR_NAMESPACE;
71 | }
72 |
73 | return new Fqsen(self::OPERATOR_NAMESPACE . $namespace . $type);
74 | }
75 |
76 | $typeParts[0] = $namespaceAliases[$typeParts[0]];
77 |
78 | return new Fqsen(self::OPERATOR_NAMESPACE . implode(self::OPERATOR_NAMESPACE, $typeParts));
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/PseudoType.php:
--------------------------------------------------------------------------------
1 | items = $items;
33 | }
34 |
35 | /**
36 | * @return ArrayShapeItem[]
37 | */
38 | public function getItems(): array
39 | {
40 | return $this->items;
41 | }
42 |
43 | public function underlyingType(): Type
44 | {
45 | return new Array_(new Mixed_(), new ArrayKey());
46 | }
47 |
48 | public function __toString(): string
49 | {
50 | return 'array{' . implode(', ', $this->items) . '}';
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/PseudoTypes/ArrayShapeItem.php:
--------------------------------------------------------------------------------
1 | owner = $owner;
33 | $this->expression = $expression;
34 | }
35 |
36 | public function getOwner(): Type
37 | {
38 | return $this->owner;
39 | }
40 |
41 | public function getExpression(): string
42 | {
43 | return $this->expression;
44 | }
45 |
46 | public function underlyingType(): Type
47 | {
48 | return new Mixed_();
49 | }
50 |
51 | public function __toString(): string
52 | {
53 | return sprintf('%s::%s', (string) $this->owner, $this->expression);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/PseudoTypes/False_.php:
--------------------------------------------------------------------------------
1 | value = $value;
29 | }
30 |
31 | public function getValue(): float
32 | {
33 | return $this->value;
34 | }
35 |
36 | public function underlyingType(): Type
37 | {
38 | return new Float_();
39 | }
40 |
41 | public function __toString(): string
42 | {
43 | return (string) $this->value;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/PseudoTypes/HtmlEscapedString.php:
--------------------------------------------------------------------------------
1 | minValue = $minValue;
36 | $this->maxValue = $maxValue;
37 | }
38 |
39 | public function underlyingType(): Type
40 | {
41 | return new Integer();
42 | }
43 |
44 | public function getMinValue(): string
45 | {
46 | return $this->minValue;
47 | }
48 |
49 | public function getMaxValue(): string
50 | {
51 | return $this->maxValue;
52 | }
53 |
54 | /**
55 | * Returns a rendered output of the Type as it would be used in a DocBlock.
56 | */
57 | public function __toString(): string
58 | {
59 | return 'int<' . $this->minValue . ', ' . $this->maxValue . '>';
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/PseudoTypes/IntegerValue.php:
--------------------------------------------------------------------------------
1 | value = $value;
29 | }
30 |
31 | public function getValue(): int
32 | {
33 | return $this->value;
34 | }
35 |
36 | public function underlyingType(): Type
37 | {
38 | return new Integer();
39 | }
40 |
41 | public function __toString(): string
42 | {
43 | return (string) $this->value;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/PseudoTypes/ListShape.php:
--------------------------------------------------------------------------------
1 | getItems()) . '}';
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/PseudoTypes/ListShapeItem.php:
--------------------------------------------------------------------------------
1 | valueType instanceof Mixed_) {
45 | return 'list';
46 | }
47 |
48 | return 'list<' . $this->valueType . '>';
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/PseudoTypes/LiteralString.php:
--------------------------------------------------------------------------------
1 | valueType, $this->keyType);
31 | }
32 |
33 | /**
34 | * Returns a rendered output of the Type as it would be used in a DocBlock.
35 | */
36 | public function __toString(): string
37 | {
38 | if ($this->keyType) {
39 | return 'non-empty-array<' . $this->keyType . ',' . $this->valueType . '>';
40 | }
41 |
42 | if ($this->valueType instanceof Mixed_) {
43 | return 'non-empty-array';
44 | }
45 |
46 | return 'non-empty-array<' . $this->valueType . '>';
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/PseudoTypes/NonEmptyList.php:
--------------------------------------------------------------------------------
1 | valueType, $this->keyType);
32 | }
33 |
34 | public function __construct(?Type $valueType = null)
35 | {
36 | parent::__construct($valueType, new Integer());
37 | }
38 |
39 | /**
40 | * Returns a rendered output of the Type as it would be used in a DocBlock.
41 | */
42 | public function __toString(): string
43 | {
44 | if ($this->valueType instanceof Mixed_) {
45 | return 'non-empty-list';
46 | }
47 |
48 | return 'non-empty-list<' . $this->valueType . '>';
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/PseudoTypes/NonEmptyLowercaseString.php:
--------------------------------------------------------------------------------
1 | items = $items;
22 | }
23 |
24 | /**
25 | * @return ObjectShapeItem[]
26 | */
27 | public function getItems(): array
28 | {
29 | return $this->items;
30 | }
31 |
32 | public function underlyingType(): Type
33 | {
34 | return new Object_();
35 | }
36 |
37 | public function __toString(): string
38 | {
39 | return 'object{' . implode(', ', $this->items) . '}';
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/PseudoTypes/ObjectShapeItem.php:
--------------------------------------------------------------------------------
1 | key = $key;
24 | $this->value = $value ?? new Mixed_();
25 | $this->optional = $optional;
26 | }
27 |
28 | public function getKey(): ?string
29 | {
30 | return $this->key;
31 | }
32 |
33 | public function getValue(): Type
34 | {
35 | return $this->value;
36 | }
37 |
38 | public function isOptional(): bool
39 | {
40 | return $this->optional;
41 | }
42 |
43 | public function __toString(): string
44 | {
45 | if ($this->key !== null) {
46 | return sprintf(
47 | '%s%s: %s',
48 | $this->key,
49 | $this->optional ? '?' : '',
50 | (string) $this->value
51 | );
52 | }
53 |
54 | return (string) $this->value;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/PseudoTypes/StringValue.php:
--------------------------------------------------------------------------------
1 | value = $value;
31 | }
32 |
33 | public function getValue(): string
34 | {
35 | return $this->value;
36 | }
37 |
38 | public function underlyingType(): Type
39 | {
40 | return new String_();
41 | }
42 |
43 | public function __toString(): string
44 | {
45 | return sprintf('"%s"', $this->value);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/PseudoTypes/TraitString.php:
--------------------------------------------------------------------------------
1 | List of recognized keywords and unto which Value Object they map
124 | * @psalm-var array>
125 | */
126 | private $keywords = [
127 | 'string' => String_::class,
128 | 'class-string' => ClassString::class,
129 | 'interface-string' => InterfaceString::class,
130 | 'html-escaped-string' => HtmlEscapedString::class,
131 | 'lowercase-string' => LowercaseString::class,
132 | 'non-empty-lowercase-string' => NonEmptyLowercaseString::class,
133 | 'non-empty-string' => NonEmptyString::class,
134 | 'numeric-string' => NumericString::class,
135 | 'numeric' => Numeric_::class,
136 | 'trait-string' => TraitString::class,
137 | 'int' => Integer::class,
138 | 'integer' => Integer::class,
139 | 'positive-int' => PositiveInteger::class,
140 | 'negative-int' => NegativeInteger::class,
141 | 'bool' => Boolean::class,
142 | 'boolean' => Boolean::class,
143 | 'real' => Float_::class,
144 | 'float' => Float_::class,
145 | 'double' => Float_::class,
146 | 'object' => Object_::class,
147 | 'mixed' => Mixed_::class,
148 | 'array' => Array_::class,
149 | 'array-key' => ArrayKey::class,
150 | 'non-empty-array' => NonEmptyArray::class,
151 | 'resource' => Resource_::class,
152 | 'void' => Void_::class,
153 | 'null' => Null_::class,
154 | 'scalar' => Scalar::class,
155 | 'callback' => Callable_::class,
156 | 'callable' => Callable_::class,
157 | 'callable-string' => CallableString::class,
158 | 'false' => False_::class,
159 | 'true' => True_::class,
160 | 'literal-string' => LiteralString::class,
161 | 'self' => Self_::class,
162 | '$this' => This::class,
163 | 'static' => Static_::class,
164 | 'parent' => Parent_::class,
165 | 'iterable' => Iterable_::class,
166 | 'never' => Never_::class,
167 | 'list' => List_::class,
168 | 'non-empty-list' => NonEmptyList::class,
169 | ];
170 |
171 | /**
172 | * @psalm-readonly
173 | * @var FqsenResolver
174 | */
175 | private $fqsenResolver;
176 | /**
177 | * @psalm-readonly
178 | * @var TypeParser
179 | */
180 | private $typeParser;
181 | /**
182 | * @psalm-readonly
183 | * @var Lexer
184 | */
185 | private $lexer;
186 |
187 | /**
188 | * Initializes this TypeResolver with the means to create and resolve Fqsen objects.
189 | */
190 | public function __construct(?FqsenResolver $fqsenResolver = null)
191 | {
192 | $this->fqsenResolver = $fqsenResolver ?: new FqsenResolver();
193 |
194 | if (class_exists(ParserConfig::class)) {
195 | $this->typeParser = new TypeParser(new ParserConfig([]), new ConstExprParser(new ParserConfig([])));
196 | $this->lexer = new Lexer(new ParserConfig([]));
197 | } else {
198 | $this->typeParser = new TypeParser(new ConstExprParser());
199 | $this->lexer = new Lexer();
200 | }
201 | }
202 |
203 | /**
204 | * Analyzes the given type and returns the FQCN variant.
205 | *
206 | * When a type is provided this method checks whether it is not a keyword or
207 | * Fully Qualified Class Name. If so it will use the given namespace and
208 | * aliases to expand the type to a FQCN representation.
209 | *
210 | * This method only works as expected if the namespace and aliases are set;
211 | * no dynamic reflection is being performed here.
212 | *
213 | * @uses Context::getNamespace() to determine with what to prefix the type name.
214 | * @uses Context::getNamespaceAliases() to check whether the first part of the relative type name should not be
215 | * replaced with another namespace.
216 | *
217 | * @param string $type The relative or absolute type.
218 | */
219 | public function resolve(string $type, ?Context $context = null): Type
220 | {
221 | $type = trim($type);
222 | if (!$type) {
223 | throw new InvalidArgumentException('Attempted to resolve "' . $type . '" but it appears to be empty');
224 | }
225 |
226 | if ($context === null) {
227 | $context = new Context('');
228 | }
229 |
230 | $tokens = $this->lexer->tokenize($type);
231 | $tokenIterator = new TokenIterator($tokens);
232 |
233 | $ast = $this->parse($tokenIterator);
234 | $type = $this->createType($ast, $context);
235 |
236 | return $this->tryParseRemainingCompoundTypes($tokenIterator, $context, $type);
237 | }
238 |
239 | public function createType(?TypeNode $type, Context $context): Type
240 | {
241 | if ($type === null) {
242 | return new Mixed_();
243 | }
244 |
245 | switch (get_class($type)) {
246 | case ArrayTypeNode::class:
247 | return new Array_(
248 | $this->createType($type->type, $context)
249 | );
250 |
251 | case ArrayShapeNode::class:
252 | switch ($type->kind) {
253 | case ArrayShapeNode::KIND_ARRAY:
254 | return new ArrayShape(
255 | ...array_map(
256 | function (ArrayShapeItemNode $item) use ($context): ArrayShapeItem {
257 | return new ArrayShapeItem(
258 | (string) $item->keyName,
259 | $this->createType($item->valueType, $context),
260 | $item->optional
261 | );
262 | },
263 | $type->items
264 | )
265 | );
266 |
267 | case ArrayShapeNode::KIND_LIST:
268 | return new ListShape(
269 | ...array_map(
270 | function (ArrayShapeItemNode $item) use ($context): ListShapeItem {
271 | return new ListShapeItem(
272 | null,
273 | $this->createType($item->valueType, $context),
274 | $item->optional
275 | );
276 | },
277 | $type->items
278 | )
279 | );
280 |
281 | default:
282 | throw new RuntimeException('Unsupported array shape kind');
283 | }
284 | case ObjectShapeNode::class:
285 | return new ObjectShape(
286 | ...array_map(
287 | function (ObjectShapeItemNode $item) use ($context): ObjectShapeItem {
288 | return new ObjectShapeItem(
289 | (string) $item->keyName,
290 | $this->createType($item->valueType, $context),
291 | $item->optional
292 | );
293 | },
294 | $type->items
295 | )
296 | );
297 |
298 | case CallableTypeNode::class:
299 | return $this->createFromCallable($type, $context);
300 |
301 | case ConstTypeNode::class:
302 | return $this->createFromConst($type, $context);
303 |
304 | case GenericTypeNode::class:
305 | return $this->createFromGeneric($type, $context);
306 |
307 | case IdentifierTypeNode::class:
308 | return $this->resolveSingleType($type->name, $context);
309 |
310 | case IntersectionTypeNode::class:
311 | return new Intersection(
312 | array_filter(
313 | array_map(
314 | function (TypeNode $nestedType) use ($context): Type {
315 | $type = $this->createType($nestedType, $context);
316 | if ($type instanceof AggregatedType) {
317 | return new Expression($type);
318 | }
319 |
320 | return $type;
321 | },
322 | $type->types
323 | )
324 | )
325 | );
326 |
327 | case NullableTypeNode::class:
328 | $nestedType = $this->createType($type->type, $context);
329 |
330 | return new Nullable($nestedType);
331 |
332 | case UnionTypeNode::class:
333 | return new Compound(
334 | array_filter(
335 | array_map(
336 | function (TypeNode $nestedType) use ($context): Type {
337 | $type = $this->createType($nestedType, $context);
338 | if ($type instanceof AggregatedType) {
339 | return new Expression($type);
340 | }
341 |
342 | return $type;
343 | },
344 | $type->types
345 | )
346 | )
347 | );
348 |
349 | case ThisTypeNode::class:
350 | return new This();
351 |
352 | case ConditionalTypeNode::class:
353 | case ConditionalTypeForParameterNode::class:
354 | case OffsetAccessTypeNode::class:
355 | default:
356 | return new Mixed_();
357 | }
358 | }
359 |
360 | private function createFromGeneric(GenericTypeNode $type, Context $context): Type
361 | {
362 | switch (strtolower($type->type->name)) {
363 | case 'array':
364 | return $this->createArray($type->genericTypes, $context);
365 |
366 | case 'class-string':
367 | $subType = $this->createType($type->genericTypes[0], $context);
368 | if (!$subType instanceof Object_ || $subType->getFqsen() === null) {
369 | throw new RuntimeException(
370 | $subType . ' is not a class string'
371 | );
372 | }
373 |
374 | return new ClassString(
375 | $subType->getFqsen()
376 | );
377 |
378 | case 'interface-string':
379 | $subType = $this->createType($type->genericTypes[0], $context);
380 | if (!$subType instanceof Object_ || $subType->getFqsen() === null) {
381 | throw new RuntimeException(
382 | $subType . ' is not a class string'
383 | );
384 | }
385 |
386 | return new InterfaceString(
387 | $subType->getFqsen()
388 | );
389 |
390 | case 'list':
391 | return new List_(
392 | $this->createType($type->genericTypes[0], $context)
393 | );
394 |
395 | case 'non-empty-list':
396 | return new NonEmptyList(
397 | $this->createType($type->genericTypes[0], $context)
398 | );
399 |
400 | case 'int':
401 | if (isset($type->genericTypes[1]) === false) {
402 | throw new RuntimeException('int has not the correct format');
403 | }
404 |
405 | return new IntegerRange((string) $type->genericTypes[0], (string) $type->genericTypes[1]);
406 |
407 | case 'iterable':
408 | return new Iterable_(
409 | ...array_reverse(
410 | array_map(
411 | function (TypeNode $genericType) use ($context): Type {
412 | return $this->createType($genericType, $context);
413 | },
414 | $type->genericTypes
415 | )
416 | )
417 | );
418 |
419 | default:
420 | $collectionType = $this->createType($type->type, $context);
421 | if ($collectionType instanceof Object_ === false) {
422 | throw new RuntimeException(sprintf('%s is not a collection', (string) $collectionType));
423 | }
424 |
425 | return new Collection(
426 | $collectionType->getFqsen(),
427 | ...array_reverse(
428 | array_map(
429 | function (TypeNode $genericType) use ($context): Type {
430 | return $this->createType($genericType, $context);
431 | },
432 | $type->genericTypes
433 | )
434 | )
435 | );
436 | }
437 | }
438 |
439 | private function createFromCallable(CallableTypeNode $type, Context $context): Callable_
440 | {
441 | return new Callable_(array_map(
442 | function (CallableTypeParameterNode $param) use ($context): CallableParameter {
443 | return new CallableParameter(
444 | $this->createType($param->type, $context),
445 | $param->parameterName !== '' ? trim($param->parameterName, '$') : null,
446 | $param->isReference,
447 | $param->isVariadic,
448 | $param->isOptional
449 | );
450 | },
451 | $type->parameters
452 | ), $this->createType($type->returnType, $context));
453 | }
454 |
455 | private function createFromConst(ConstTypeNode $type, Context $context): Type
456 | {
457 | switch (true) {
458 | case $type->constExpr instanceof ConstExprIntegerNode:
459 | return new IntegerValue((int) $type->constExpr->value);
460 |
461 | case $type->constExpr instanceof ConstExprFloatNode:
462 | return new FloatValue((float) $type->constExpr->value);
463 |
464 | case $type->constExpr instanceof ConstExprStringNode:
465 | return new StringValue($type->constExpr->value);
466 |
467 | case $type->constExpr instanceof ConstFetchNode:
468 | return new ConstExpression(
469 | $this->resolve($type->constExpr->className, $context),
470 | $type->constExpr->name
471 | );
472 |
473 | default:
474 | throw new RuntimeException(sprintf('Unsupported constant type %s', get_class($type)));
475 | }
476 | }
477 |
478 | /**
479 | * resolve the given type into a type object
480 | *
481 | * @param string $type the type string, representing a single type
482 | *
483 | * @return Type|Array_|Object_
484 | *
485 | * @psalm-mutation-free
486 | */
487 | private function resolveSingleType(string $type, Context $context): object
488 | {
489 | switch (true) {
490 | case $this->isKeyword($type):
491 | return $this->resolveKeyword($type);
492 |
493 | case $this->isFqsen($type):
494 | return $this->resolveTypedObject($type);
495 |
496 | case $this->isPartialStructuralElementName($type):
497 | return $this->resolveTypedObject($type, $context);
498 |
499 | // @codeCoverageIgnoreStart
500 | default:
501 | // I haven't got the foggiest how the logic would come here but added this as a defense.
502 | throw new RuntimeException(
503 | 'Unable to resolve type "' . $type . '", there is no known method to resolve it'
504 | );
505 | }
506 |
507 | // @codeCoverageIgnoreEnd
508 | }
509 |
510 | /**
511 | * Adds a keyword to the list of Keywords and associates it with a specific Value Object.
512 | *
513 | * @psalm-param class-string $typeClassName
514 | */
515 | public function addKeyword(string $keyword, string $typeClassName): void
516 | {
517 | if (!class_exists($typeClassName)) {
518 | throw new InvalidArgumentException(
519 | 'The Value Object that needs to be created with a keyword "' . $keyword . '" must be an existing class'
520 | . ' but we could not find the class ' . $typeClassName
521 | );
522 | }
523 |
524 | $interfaces = class_implements($typeClassName);
525 | if ($interfaces === false) {
526 | throw new InvalidArgumentException(
527 | 'The Value Object that needs to be created with a keyword "' . $keyword . '" must be an existing class'
528 | . ' but we could not find the class ' . $typeClassName
529 | );
530 | }
531 |
532 | if (!in_array(Type::class, $interfaces, true)) {
533 | throw new InvalidArgumentException(
534 | 'The class "' . $typeClassName . '" must implement the interface "phpDocumentor\Reflection\Type"'
535 | );
536 | }
537 |
538 | $this->keywords[$keyword] = $typeClassName;
539 | }
540 |
541 | /**
542 | * Detects whether the given type represents a PHPDoc keyword.
543 | *
544 | * @param string $type A relative or absolute type as defined in the phpDocumentor documentation.
545 | *
546 | * @psalm-mutation-free
547 | */
548 | private function isKeyword(string $type): bool
549 | {
550 | return array_key_exists(strtolower($type), $this->keywords);
551 | }
552 |
553 | /**
554 | * Detects whether the given type represents a relative structural element name.
555 | *
556 | * @param string $type A relative or absolute type as defined in the phpDocumentor documentation.
557 | *
558 | * @psalm-mutation-free
559 | */
560 | private function isPartialStructuralElementName(string $type): bool
561 | {
562 | return (isset($type[0]) && $type[0] !== self::OPERATOR_NAMESPACE) && !$this->isKeyword($type);
563 | }
564 |
565 | /**
566 | * Tests whether the given type is a Fully Qualified Structural Element Name.
567 | *
568 | * @psalm-mutation-free
569 | */
570 | private function isFqsen(string $type): bool
571 | {
572 | return strpos($type, self::OPERATOR_NAMESPACE) === 0;
573 | }
574 |
575 | /**
576 | * Resolves the given keyword (such as `string`) into a Type object representing that keyword.
577 | *
578 | * @psalm-mutation-free
579 | */
580 | private function resolveKeyword(string $type): Type
581 | {
582 | $className = $this->keywords[strtolower($type)];
583 |
584 | return new $className();
585 | }
586 |
587 | /**
588 | * Resolves the given FQSEN string into an FQSEN object.
589 | *
590 | * @psalm-mutation-free
591 | */
592 | private function resolveTypedObject(string $type, ?Context $context = null): Object_
593 | {
594 | return new Object_($this->fqsenResolver->resolve($type, $context));
595 | }
596 |
597 | /** @param TypeNode[] $typeNodes */
598 | private function createArray(array $typeNodes, Context $context): Array_
599 | {
600 | $types = array_reverse(
601 | array_map(
602 | function (TypeNode $node) use ($context): Type {
603 | return $this->createType($node, $context);
604 | },
605 | $typeNodes
606 | )
607 | );
608 |
609 | if (isset($types[1]) === false) {
610 | return new Array_(...$types);
611 | }
612 |
613 | if ($this->validArrayKeyType($types[1]) || $types[1] instanceof ArrayKey) {
614 | return new Array_(...$types);
615 | }
616 |
617 | if ($types[1] instanceof Compound && $types[1]->getIterator()->count() === 2) {
618 | if ($this->validArrayKeyType($types[1]->get(0)) && $this->validArrayKeyType($types[1]->get(1))) {
619 | return new Array_(...$types);
620 | }
621 | }
622 |
623 | throw new RuntimeException('An array can have only integers or strings as keys');
624 | }
625 |
626 | private function validArrayKeyType(?Type $type): bool
627 | {
628 | return $type instanceof String_ || $type instanceof Integer;
629 | }
630 |
631 | private function parse(TokenIterator $tokenIterator): TypeNode
632 | {
633 | try {
634 | $ast = $this->typeParser->parse($tokenIterator);
635 | } catch (ParserException $e) {
636 | throw new RuntimeException($e->getMessage(), 0, $e);
637 | }
638 |
639 | return $ast;
640 | }
641 |
642 | /**
643 | * Will try to parse unsupported type notations by phpstan
644 | *
645 | * The phpstan parser doesn't support the illegal nullable combinations like this library does.
646 | * This method will warn the user about those notations but for bc purposes we will still have it here.
647 | */
648 | private function tryParseRemainingCompoundTypes(TokenIterator $tokenIterator, Context $context, Type $type): Type
649 | {
650 | if (
651 | $tokenIterator->isCurrentTokenType(Lexer::TOKEN_UNION) ||
652 | $tokenIterator->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)
653 | ) {
654 | Deprecation::trigger(
655 | 'phpdocumentor/type-resolver',
656 | 'https://github.com/phpDocumentor/TypeResolver/issues/184',
657 | 'Legacy nullable type detected, please update your code as
658 | you are using nullable types in a docblock. support will be removed in v2.0.0'
659 | );
660 | }
661 |
662 | $continue = true;
663 | while ($continue) {
664 | $continue = false;
665 | while ($tokenIterator->tryConsumeTokenType(Lexer::TOKEN_UNION)) {
666 | $ast = $this->parse($tokenIterator);
667 | $type2 = $this->createType($ast, $context);
668 | $type = new Compound([$type, $type2]);
669 | $continue = true;
670 | }
671 |
672 | while ($tokenIterator->tryConsumeTokenType(Lexer::TOKEN_INTERSECTION)) {
673 | $ast = $this->typeParser->parse($tokenIterator);
674 | $type2 = $this->createType($ast, $context);
675 | $type = new Intersection([$type, $type2]);
676 | $continue = true;
677 | }
678 | }
679 |
680 | return $type;
681 | }
682 | }
683 |
--------------------------------------------------------------------------------
/src/Types/AbstractList.php:
--------------------------------------------------------------------------------
1 | valueType = $valueType;
44 | $this->defaultKeyType = new Compound([new String_(), new Integer()]);
45 | $this->keyType = $keyType;
46 | }
47 |
48 | /**
49 | * Returns the type for the keys of this array.
50 | */
51 | public function getKeyType(): Type
52 | {
53 | return $this->keyType ?? $this->defaultKeyType;
54 | }
55 |
56 | /**
57 | * Returns the type for the values of this array.
58 | */
59 | public function getValueType(): Type
60 | {
61 | return $this->valueType;
62 | }
63 |
64 | /**
65 | * Returns a rendered output of the Type as it would be used in a DocBlock.
66 | */
67 | public function __toString(): string
68 | {
69 | if ($this->keyType) {
70 | return 'array<' . $this->keyType . ',' . $this->valueType . '>';
71 | }
72 |
73 | if ($this->valueType instanceof Mixed_) {
74 | return 'array';
75 | }
76 |
77 | if ($this->valueType instanceof Compound) {
78 | return '(' . $this->valueType . ')[]';
79 | }
80 |
81 | return $this->valueType . '[]';
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Types/AggregatedType.php:
--------------------------------------------------------------------------------
1 |
30 | */
31 | abstract class AggregatedType implements Type, IteratorAggregate
32 | {
33 | /**
34 | * @psalm-allow-private-mutation
35 | * @var array
36 | */
37 | private $types = [];
38 |
39 | /** @var string */
40 | private $token;
41 |
42 | /**
43 | * @param array $types
44 | */
45 | public function __construct(array $types, string $token)
46 | {
47 | foreach ($types as $type) {
48 | $this->add($type);
49 | }
50 |
51 | $this->token = $token;
52 | }
53 |
54 | /**
55 | * Returns the type at the given index.
56 | */
57 | public function get(int $index): ?Type
58 | {
59 | if (!$this->has($index)) {
60 | return null;
61 | }
62 |
63 | return $this->types[$index];
64 | }
65 |
66 | /**
67 | * Tests if this compound type has a type with the given index.
68 | */
69 | public function has(int $index): bool
70 | {
71 | return array_key_exists($index, $this->types);
72 | }
73 |
74 | /**
75 | * Tests if this compound type contains the given type.
76 | */
77 | public function contains(Type $type): bool
78 | {
79 | foreach ($this->types as $typePart) {
80 | // if the type is duplicate; do not add it
81 | if ((string) $typePart === (string) $type) {
82 | return true;
83 | }
84 | }
85 |
86 | return false;
87 | }
88 |
89 | /**
90 | * Returns a rendered output of the Type as it would be used in a DocBlock.
91 | */
92 | public function __toString(): string
93 | {
94 | return implode($this->token, $this->types);
95 | }
96 |
97 | /**
98 | * @return ArrayIterator
99 | */
100 | public function getIterator(): ArrayIterator
101 | {
102 | return new ArrayIterator($this->types);
103 | }
104 |
105 | /**
106 | * @psalm-suppress ImpureMethodCall
107 | */
108 | private function add(Type $type): void
109 | {
110 | if ($type instanceof static) {
111 | foreach ($type->getIterator() as $subType) {
112 | $this->add($subType);
113 | }
114 |
115 | return;
116 | }
117 |
118 | // if the type is duplicate; do not add it
119 | if ($this->contains($type)) {
120 | return;
121 | }
122 |
123 | $this->types[] = $type;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/Types/ArrayKey.php:
--------------------------------------------------------------------------------
1 | type = $type;
47 | $this->isReference = $isReference;
48 | $this->isVariadic = $isVariadic;
49 | $this->isOptional = $isOptional;
50 | $this->name = $name;
51 | }
52 |
53 | public function getName(): ?string
54 | {
55 | return $this->name;
56 | }
57 |
58 | public function getType(): Type
59 | {
60 | return $this->type;
61 | }
62 |
63 | public function isReference(): bool
64 | {
65 | return $this->isReference;
66 | }
67 |
68 | public function isVariadic(): bool
69 | {
70 | return $this->isVariadic;
71 | }
72 |
73 | public function isOptional(): bool
74 | {
75 | return $this->isOptional;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Types/Callable_.php:
--------------------------------------------------------------------------------
1 | parameters = $parameters;
36 | $this->returnType = $returnType;
37 | }
38 |
39 | /** @return CallableParameter[] */
40 | public function getParameters(): array
41 | {
42 | return $this->parameters;
43 | }
44 |
45 | public function getReturnType(): ?Type
46 | {
47 | return $this->returnType;
48 | }
49 |
50 | /**
51 | * Returns a rendered output of the Type as it would be used in a DocBlock.
52 | */
53 | public function __toString(): string
54 | {
55 | return 'callable';
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Types/ClassString.php:
--------------------------------------------------------------------------------
1 | fqsen = $fqsen;
36 | }
37 |
38 | public function underlyingType(): Type
39 | {
40 | return new String_();
41 | }
42 |
43 | /**
44 | * Returns the FQSEN associated with this object.
45 | */
46 | public function getFqsen(): ?Fqsen
47 | {
48 | return $this->fqsen;
49 | }
50 |
51 | /**
52 | * Returns a rendered output of the Type as it would be used in a DocBlock.
53 | */
54 | public function __toString(): string
55 | {
56 | if ($this->fqsen === null) {
57 | return 'class-string';
58 | }
59 |
60 | return 'class-string<' . (string) $this->fqsen . '>';
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Types/Collection.php:
--------------------------------------------------------------------------------
1 | `
25 | * 2. `ACollectionObject`
26 | *
27 | * - ACollectionObject can be 'array' or an object that can act as an array
28 | * - aValueType and aKeyType can be any type expression
29 | *
30 | * @psalm-immutable
31 | */
32 | final class Collection extends AbstractList
33 | {
34 | /** @var Fqsen|null */
35 | private $fqsen;
36 |
37 | /**
38 | * Initializes this representation of an array with the given Type or Fqsen.
39 | */
40 | public function __construct(?Fqsen $fqsen, Type $valueType, ?Type $keyType = null)
41 | {
42 | parent::__construct($valueType, $keyType);
43 |
44 | $this->fqsen = $fqsen;
45 | }
46 |
47 | /**
48 | * Returns the FQSEN associated with this object.
49 | */
50 | public function getFqsen(): ?Fqsen
51 | {
52 | return $this->fqsen;
53 | }
54 |
55 | /**
56 | * Returns a rendered output of the Type as it would be used in a DocBlock.
57 | */
58 | public function __toString(): string
59 | {
60 | $objectType = (string) ($this->fqsen ?? 'object');
61 |
62 | if ($this->keyType === null) {
63 | return $objectType . '<' . $this->valueType . '>';
64 | }
65 |
66 | return $objectType . '<' . $this->keyType . ',' . $this->valueType . '>';
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Types/Compound.php:
--------------------------------------------------------------------------------
1 | $types
33 | */
34 | public function __construct(array $types)
35 | {
36 | parent::__construct($types, '|');
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Types/Context.php:
--------------------------------------------------------------------------------
1 | Fully Qualified Namespace.
43 | * @psalm-var array
44 | */
45 | private $namespaceAliases;
46 |
47 | /**
48 | * Initializes the new context and normalizes all passed namespaces to be in Qualified Namespace Name (QNN)
49 | * format (without a preceding `\`).
50 | *
51 | * @param string $namespace The namespace where this DocBlock resides in.
52 | * @param string[] $namespaceAliases List of namespace aliases => Fully Qualified Namespace.
53 | * @psalm-param array $namespaceAliases
54 | */
55 | public function __construct(string $namespace, array $namespaceAliases = [])
56 | {
57 | $this->namespace = $namespace !== 'global' && $namespace !== 'default'
58 | ? trim($namespace, '\\')
59 | : '';
60 |
61 | foreach ($namespaceAliases as $alias => $fqnn) {
62 | if ($fqnn[0] === '\\') {
63 | $fqnn = substr($fqnn, 1);
64 | }
65 |
66 | if ($fqnn[strlen($fqnn) - 1] === '\\') {
67 | $fqnn = substr($fqnn, 0, -1);
68 | }
69 |
70 | $namespaceAliases[$alias] = $fqnn;
71 | }
72 |
73 | $this->namespaceAliases = $namespaceAliases;
74 | }
75 |
76 | /**
77 | * Returns the Qualified Namespace Name (thus without `\` in front) where the associated element is in.
78 | */
79 | public function getNamespace(): string
80 | {
81 | return $this->namespace;
82 | }
83 |
84 | /**
85 | * Returns a list of Qualified Namespace Names (thus without `\` in front) that are imported, the keys represent
86 | * the alias for the imported Namespace.
87 | *
88 | * @return string[]
89 | * @psalm-return array
90 | */
91 | public function getNamespaceAliases(): array
92 | {
93 | return $this->namespaceAliases;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/Types/ContextFactory.php:
--------------------------------------------------------------------------------
1 | $reflector */
86 |
87 | return $this->createFromReflectionClass($reflector);
88 | }
89 |
90 | if ($reflector instanceof ReflectionParameter) {
91 | return $this->createFromReflectionParameter($reflector);
92 | }
93 |
94 | if ($reflector instanceof ReflectionMethod) {
95 | return $this->createFromReflectionMethod($reflector);
96 | }
97 |
98 | if ($reflector instanceof ReflectionProperty) {
99 | return $this->createFromReflectionProperty($reflector);
100 | }
101 |
102 | if ($reflector instanceof ReflectionClassConstant) {
103 | return $this->createFromReflectionClassConstant($reflector);
104 | }
105 |
106 | throw new UnexpectedValueException('Unhandled \Reflector instance given: ' . get_class($reflector));
107 | }
108 |
109 | private function createFromReflectionParameter(ReflectionParameter $parameter): Context
110 | {
111 | $class = $parameter->getDeclaringClass();
112 | if (!$class) {
113 | throw new InvalidArgumentException('Unable to get class of ' . $parameter->getName());
114 | }
115 |
116 | return $this->createFromReflectionClass($class);
117 | }
118 |
119 | private function createFromReflectionMethod(ReflectionMethod $method): Context
120 | {
121 | $class = $method->getDeclaringClass();
122 |
123 | return $this->createFromReflectionClass($class);
124 | }
125 |
126 | private function createFromReflectionProperty(ReflectionProperty $property): Context
127 | {
128 | $class = $property->getDeclaringClass();
129 |
130 | return $this->createFromReflectionClass($class);
131 | }
132 |
133 | private function createFromReflectionClassConstant(ReflectionClassConstant $constant): Context
134 | {
135 | //phpcs:ignore SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.MissingVariable
136 | /** @phpstan-var ReflectionClass