Repository: phpDocumentor/TypeResolver
Branch: 2.x
Commit: b2abba83bb2d
Files: 195
Total size: 1.5 MB
Directory structure:
gitextract_9s3rpj8r/
├── .editorconfig
├── .git-blame-ignore-revs
├── .gitattributes
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ ├── documentation.yaml
│ └── integrate.yaml
├── .gitignore
├── .phpdoc/
│ └── template/
│ └── base.html.twig
├── .yamllint.yaml
├── LICENSE
├── Makefile
├── README.md
├── composer-require-checker.json
├── composer.json
├── docs/
│ ├── generics.rst
│ ├── getting-started.rst
│ ├── index.rst
│ └── upgrade-v1-to-v2.rst
├── examples/
│ ├── 01-resolving-simple-types.php
│ ├── 02-resolving-classes.php
│ ├── 03-resolving-all-elements.php
│ ├── 04-discovering-the-context-using-class-reflection.php
│ ├── 05-discovering-the-context-using-method-reflection.php
│ ├── 06-discovering-the-context-using-file-contents.php
│ └── Classy.php
├── phpbench.json
├── phpcs.xml.dist
├── phpdoc.dist.xml
├── phpstan.neon
├── phpunit.xml.dist
├── psalm.xml
├── src/
│ ├── FqsenResolver.php
│ ├── PseudoType.php
│ ├── PseudoTypes/
│ │ ├── ArrayKey.php
│ │ ├── ArrayShape.php
│ │ ├── ArrayShapeItem.php
│ │ ├── CallableArray.php
│ │ ├── CallableString.php
│ │ ├── ClassString.php
│ │ ├── ClosedResource.php
│ │ ├── Conditional.php
│ │ ├── ConditionalForParameter.php
│ │ ├── ConstExpression.php
│ │ ├── EnumString.php
│ │ ├── False_.php
│ │ ├── FloatValue.php
│ │ ├── Generic.php
│ │ ├── HtmlEscapedString.php
│ │ ├── IntMask.php
│ │ ├── IntMaskOf.php
│ │ ├── IntegerRange.php
│ │ ├── IntegerValue.php
│ │ ├── InterfaceString.php
│ │ ├── KeyOf.php
│ │ ├── ListShape.php
│ │ ├── ListShapeItem.php
│ │ ├── List_.php
│ │ ├── LiteralString.php
│ │ ├── LowercaseString.php
│ │ ├── NegativeInteger.php
│ │ ├── NeverReturn.php
│ │ ├── NeverReturns.php
│ │ ├── NoReturn.php
│ │ ├── NonEmptyArray.php
│ │ ├── NonEmptyList.php
│ │ ├── NonEmptyLowercaseString.php
│ │ ├── NonEmptyString.php
│ │ ├── NonFalsyString.php
│ │ ├── NonNegativeInteger.php
│ │ ├── NonPositiveInteger.php
│ │ ├── NonZeroInteger.php
│ │ ├── NumericString.php
│ │ ├── Numeric_.php
│ │ ├── ObjectShape.php
│ │ ├── ObjectShapeItem.php
│ │ ├── OffsetAccess.php
│ │ ├── OpenResource.php
│ │ ├── PositiveInteger.php
│ │ ├── PrivatePropertiesOf.php
│ │ ├── PropertiesOf.php
│ │ ├── ProtectedPropertiesOf.php
│ │ ├── PublicPropertiesOf.php
│ │ ├── Scalar.php
│ │ ├── ShapeItem.php
│ │ ├── StringValue.php
│ │ ├── TraitString.php
│ │ ├── True_.php
│ │ ├── TruthyString.php
│ │ └── ValueOf.php
│ ├── Type.php
│ ├── TypeResolver.php
│ └── Types/
│ ├── AbstractList.php
│ ├── AggregatedType.php
│ ├── Array_.php
│ ├── Boolean.php
│ ├── CallableParameter.php
│ ├── Callable_.php
│ ├── Compound.php
│ ├── Context.php
│ ├── ContextFactory.php
│ ├── Expression.php
│ ├── Float_.php
│ ├── Integer.php
│ ├── Intersection.php
│ ├── Iterable_.php
│ ├── Mixed_.php
│ ├── Never_.php
│ ├── Null_.php
│ ├── Nullable.php
│ ├── Object_.php
│ ├── Parent_.php
│ ├── Resource_.php
│ ├── Self_.php
│ ├── Static_.php
│ ├── String_.php
│ ├── This.php
│ └── Void_.php
└── tests/
├── benchmark/
│ ├── Assets/
│ │ └── mpdf.php
│ ├── ContextFactoryBench.php
│ ├── TypeResolverBench.php
│ └── TypeResolverWithContextBench.php
└── unit/
├── CollectionResolverTest.php
├── FqsenResolverTest.php
├── IntegerRangeResolverTest.php
├── NumericResolverTest.php
├── PseudoTypes/
│ ├── ArrayKeyTest.php
│ ├── ArrayShapeItemTest.php
│ ├── ArrayShapeTest.php
│ ├── CallableArrayTest.php
│ ├── CallableStringTest.php
│ ├── ClassStringTest.php
│ ├── ClosedResourceTest.php
│ ├── ConditionalForParameterTest.php
│ ├── ConditionalTest.php
│ ├── ConstExpressionTest.php
│ ├── EnumStringTest.php
│ ├── FalseTest.php
│ ├── FloatValueTest.php
│ ├── GenericTest.php
│ ├── HtmlEscapedStringTest.php
│ ├── IntMaskOfTest.php
│ ├── IntMaskTest.php
│ ├── IntegerRangeTest.php
│ ├── IntegerValueTest.php
│ ├── InterfaceStringTest.php
│ ├── KeyOfTest.php
│ ├── ListShapeTest.php
│ ├── ListTest.php
│ ├── LiteralStringTest.php
│ ├── LowercaseStringTest.php
│ ├── NegativeIntegerTest.php
│ ├── NeverReturnTest.php
│ ├── NeverReturnsTest.php
│ ├── NoReturnTest.php
│ ├── NonEmptyArrayTest.php
│ ├── NonEmptyListTest.php
│ ├── NonEmptyLowercaseStringTest.php
│ ├── NonEmptyStringTest.php
│ ├── NonFalsyStringTest.php
│ ├── NonNegativeIntegerTest.php
│ ├── NonPositiveIntegerTest.php
│ ├── NonZeroIntegerTest.php
│ ├── NumericStringTest.php
│ ├── ObjectShapeTest.php
│ ├── OffsetAccessTest.php
│ ├── OpenResourceTest.php
│ ├── PositiveIntegerTest.php
│ ├── PrivatePropertiesOfTest.php
│ ├── PropertiesOfTest.php
│ ├── ProtectedPropertiesOfTest.php
│ ├── PublicPropertiesOfTest.php
│ ├── ScalarTest.php
│ ├── StringValueTest.php
│ ├── TraitStringTest.php
│ ├── TrueTest.php
│ ├── TruthyStringTest.php
│ └── ValueOfTest.php
├── TypeResolverTest.php
└── Types/
├── ArrayTest.php
├── BooleanTest.php
├── CallableParameterTest.php
├── CallableTest.php
├── CompoundTest.php
├── ContextFactoryTest.php
├── ContextTest.php
├── IterableTest.php
├── NeverTest.php
├── NullableTest.php
├── ObjectTest.php
├── ParentTest.php
├── ResourceTest.php
├── SelfTest.php
├── StaticTest.php
├── ThisTest.php
└── VoidTest.php
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
[*.yml]
indent_size = 2
================================================
FILE: .git-blame-ignore-revs
================================================
# CS: no whitespace before return type colon
6937c3bf9f57533b310c28c1758f4af6bd777f92
# CS: miscellaneous other whitespace fixes
92a009284c653e8576ee544191f2167f97c93aed
# Fix style issues
0339a6b726db84790d94794e36f502c52e0e518a
# CS fixes
2e32a6d48972b2c1976ed5d8967145b6cec4a4a9
# CS fixes
e64841657859b6a3d1ecf01d6922ce1548ad7382
# fix code style
81dea8effddb6afc070552d95b8f5bbc2589fe10
# Style fixes
0cbfc1c752b9fae8c7a0be740e5d6af496463e1e
# Apply doctrine coding standards
d0e860a40b8199a0d5166023bc3764eed7af7d83
================================================
FILE: .gitattributes
================================================
/.github/ export-ignore
/examples/ export-ignore
/docs/ export-ignore
/tests/ export-ignore
/.editorconfig export-ignore
/.git-blame-ignore-revs export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/.yamllint.yaml export-ignore
/Makefile export-ignore
/composer-require-checker.json export-ignore
/composer.lock export-ignore
/phpbench.json export-ignore
/phpcs.xml.dist export-ignore
/phpstan.neon export-ignore
/phpunit.xml.dist export-ignore
/psalm.xml export-ignore
/phpdoc.dist.xml export-ignore
/.phpdoc/ export-ignore
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "composer"
directory: "/"
schedule:
interval: "daily"
open-pull-requests-limit: 10
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
================================================
FILE: .github/workflows/documentation.yaml
================================================
# https://docs.github.com/en/actions
name: "Documentation"
on: # yamllint disable-line rule:truthy
push:
branches:
- "2.x"
workflow_dispatch: null
jobs:
run:
name: "Documentation"
uses: "phpDocumentor/.github/.github/workflows/documentation.yml@main"
with:
deploy: true
component: "type-resolver"
secrets:
token: "${{ secrets.BOT_TOKEN }}"
================================================
FILE: .github/workflows/integrate.yaml
================================================
# https://docs.github.com/en/actions
name: "Integrate"
on: # yamllint disable-line rule:truthy
push:
branches:
- "1.x"
pull_request: null
schedule:
- cron: "0 14 * * 1"
# Allow manually triggering the workflow.
workflow_dispatch: null
jobs:
code-coverage:
name: "Code Coverage"
uses: "phpDocumentor/.github/.github/workflows/code-coverage.yml@main"
with:
php-extensions: "none, ctype, dom, json, mbstring, phar, simplexml, tokenizer, xml, xmlwriter, fileinfo, iconv"
composer-root-version: "1.x-dev"
coding-standards:
name: "Coding Standards"
uses: "phpDocumentor/.github/.github/workflows/coding-standards.yml@v0.9"
with:
php-extensions: "none, ctype, dom, json, mbstring, phar, simplexml, tokenizer, xml, xmlwriter, fileinfo, iconv"
composer-root-version: "1.x-dev"
dependency-analysis:
name: "Dependency analysis"
uses: "phpDocumentor/.github/.github/workflows/dependency-analysis.yml@v0.9"
with:
php-extensions: "none, ctype, dom, json, mbstring, phar, simplexml, tokenizer, xml, xmlwriter, fileinfo, iconv"
composer-root-version: "1.x-dev"
lint-root:
name: "Lint root"
uses: "phpDocumentor/.github/.github/workflows/lint.yml@main"
with:
composer-options: "--no-check-publish --ansi"
static-analysis:
name: "Static analysis"
uses: "phpDocumentor/.github/.github/workflows/static-analysis.yml@v0.9"
with:
php-extensions: "none, ctype, dom, json, mbstring, phar, simplexml, tokenizer, xml, xmlwriter, fileinfo, pcntl, posix, iconv"
composer-root-version: "1.x-dev"
unit-tests:
name: "Unit test"
uses: "phpDocumentor/.github/.github/workflows/continuous-integration.yml@v0.9"
with:
php-extensions: "none, ctype, dom, json, mbstring, phar, simplexml, tokenizer, xml, xmlwriter, fileinfo, iconv"
composer-root-version: "1.x-dev"
upcoming-releases: true
================================================
FILE: .gitignore
================================================
# IDE Shizzle; it is recommended to use a global .gitignore for this but since this is an OSS project we want to make
# it easy to contribute
.idea
.vscode
/nbproject/private/
.buildpath
.project
.settings
# No need to version the binary files of other tools
bin/behat*
bin/phpcs*
bin/phpunit*
bin/jsonlint*
bin/validate-json*
temp/ecs/*
# Build folder and vendor folder are generated code; no need to version this
build/
tools/
vendor/
*.phar
# By default the phpunit.xml.dist is provided; you can override this using a local config file
phpunit.xml
.phpunit.result.cache
================================================
FILE: .phpdoc/template/base.html.twig
================================================
{% extends 'layout.html.twig' %}
{% set topMenu = {
"menu": [
{ "name": "About", "url": "https://phpdoc.org/"},
{ "name": "Components", "url": "https://phpdoc.org/components.html"},
{ "name": "Documentation", "url": "https://docs.phpdoc.org/"},
],
"social": [
{ "iconClass": "fab fa-mastodon", "url": "https://phpc.social/@phpdoc"},
{ "iconClass": "fab fa-github", "url": "https://github.com/phpdocumentor/typeresolver"},
{ "iconClass": "fas fa-envelope-open-text", "url": "https://github.com/orgs/phpDocumentor/discussions"}
]
}
%}
================================================
FILE: .yamllint.yaml
================================================
extends: "default"
ignore: |
.build/
.notes/
vendor/
rules:
braces:
max-spaces-inside-empty: 0
max-spaces-inside: 1
min-spaces-inside-empty: 0
min-spaces-inside: 1
brackets:
max-spaces-inside-empty: 0
max-spaces-inside: 0
min-spaces-inside-empty: 0
min-spaces-inside: 0
colons:
max-spaces-after: 1
max-spaces-before: 0
commas:
max-spaces-after: 1
max-spaces-before: 0
min-spaces-after: 1
comments:
ignore-shebangs: true
min-spaces-from-content: 1
require-starting-space: true
comments-indentation: "enable"
document-end:
present: false
document-start:
present: false
indentation:
check-multi-line-strings: false
indent-sequences: true
spaces: 2
empty-lines:
max-end: 0
max-start: 0
max: 1
empty-values:
forbid-in-block-mappings: true
forbid-in-flow-mappings: true
hyphens:
max-spaces-after: 2
key-duplicates: "enable"
key-ordering: "disable"
line-length: "disable"
new-line-at-end-of-file: "enable"
new-lines:
type: "unix"
octal-values:
forbid-implicit-octal: true
quoted-strings:
quote-type: "double"
trailing-spaces: "enable"
truthy:
allowed-values:
- "false"
- "true"
yaml-files:
- "*.yaml"
- "*.yml"
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2010 Mike van Riel
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.
================================================
FILE: Makefile
================================================
.PHONY: help
help: ## Displays this list of targets with descriptions
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m%-30s\033[0m %s\n", $$1, $$2}'
.PHONY: code-style
code-style:
docker run -it --rm -v${PWD}:/opt/project -w /opt/project phpdoc/phpcs-ga:latest -d memory_limit=1024M -s
.PHONY: fix-code-style
fix-code-style:
docker run -it --rm -v${PWD}:/opt/project -w /opt/project phpdoc/phpcs-ga:latest phpcbf
.PHONY: static-code-analysis
static-code-analysis: vendor ## Runs a static code analysis with phpstan/phpstan and vimeo/psalm
docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:7.4 vendor/bin/phpstan --configuration=phpstan.neon
docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:7.4 vendor/bin/psalm
.PHONY: test
test: test-unit ## Runs all test suites with phpunit/phpunit
docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:7.4 vendor/bin/phpunit
.PHONY: test-unit
test-unit: ## Runs unit tests with phpunit/phpunit
docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:7.4 vendor/bin/phpunit --testsuite=unit
.PHONY: dependency-analysis
dependency-analysis: vendor ## Runs a dependency analysis with maglnet/composer-require-checker
docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:7.4 .phive/composer-require-checker check --config-file=/opt/project/composer-require-checker.json
vendor: composer.json composer.lock
composer validate --no-check-publish
composer install --no-interaction --no-progress
.PHONY: benchmark
benchmark:
docker run -it --rm -v${CURDIR}:/opt/project -w /opt/project php:7.4-cli vendor/bin/phpbench run
.PHONY: pre-commit-test
pre-commit-test: fix-code-style test code-style static-code-analysis
.PHONY: docs
docs: ## Generate documentation with phpDocumentor
docker run -it --rm -v${CURDIR}:/opt/project -w /opt/project phpdoc/phpdoc:3
================================================
FILE: README.md
================================================
[](https://opensource.org/licenses/MIT)

[](https://coveralls.io/github/phpDocumentor/TypeResolver?branch=1.x)
[](https://scrutinizer-ci.com/g/phpDocumentor/TypeResolver/?branch=1.x)
[](https://scrutinizer-ci.com/g/phpDocumentor/TypeResolver/?branch=1.x)


TypeResolver and FqsenResolver
==============================
The specification on types in DocBlocks (PSR-5) describes various keywords and special constructs
but also how to statically resolve the partial name of a Class into a Fully Qualified Class Name (FQCN).
PSR-5 also introduces an additional way to describe deeper elements than Classes, Interfaces and Traits
called the Fully Qualified Structural Element Name (FQSEN). Using this it is possible to refer to methods,
properties and class constants but also functions and global constants.
This package provides two Resolvers that are capable of
1. Returning a series of Value Object for given expression while resolving any partial class names, and
2. Returning an FQSEN object after resolving any partial Structural Element Names into Fully Qualified Structural
Element names.
## Installing
The easiest way to install this library is with [Composer](https://getcomposer.org) using the following command:
$ composer require phpdocumentor/type-resolver
## Examples
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.
## On Types and Element Names
This component can be used in one of two ways
1. To resolve a Type or
2. To resolve a Fully Qualified Structural Element Name
The big difference between these two is in the number of things it can resolve.
The TypeResolver can resolve:
- a php primitive or pseudo-primitive such as a string or void (`@var string` or `@return void`).
- a composite such as an array of string (`@var string[]`).
- a compound such as a string or integer (`@var string|integer`).
- an array expression (`@var (string|TypeResolver)[]`)
- an object or interface such as the TypeResolver class (`@var TypeResolver`
or `@var \phpDocumentor\Reflection\TypeResolver`)
> please note that if you want to pass partial class names that additional steps are necessary, see the
> chapter `Resolving partial classes and FQSENs` for more information.
Where the FqsenResolver can resolve:
- Constant expressions (i.e. `@see \MyNamespace\MY_CONSTANT`)
- Function expressions (i.e. `@see \MyNamespace\myFunction()`)
- Class expressions (i.e. `@see \MyNamespace\MyClass`)
- Interface expressions (i.e. `@see \MyNamespace\MyInterface`)
- Trait expressions (i.e. `@see \MyNamespace\MyTrait`)
- Class constant expressions (i.e. `@see \MyNamespace\MyClass::MY_CONSTANT`)
- Property expressions (i.e. `@see \MyNamespace\MyClass::$myProperty`)
- Method expressions (i.e. `@see \MyNamespace\MyClass::myMethod()`)
## Resolving a type
In order to resolve a type you will have to instantiate the class `\phpDocumentor\Reflection\TypeResolver` and call its `resolve` method like this:
```php
$typeResolver = new \phpDocumentor\Reflection\TypeResolver();
$type = $typeResolver->resolve('string|integer');
```
In this example you will receive a Value Object of class `\phpDocumentor\Reflection\Types\Compound` that has two
elements, one of type `\phpDocumentor\Reflection\Types\String_` and one of type
`\phpDocumentor\Reflection\Types\Integer`.
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.
### Resolving nullable types
Php 7.1 introduced nullable types e.g. `?string`. Type resolver will resolve the original type without the nullable notation `?`
just like it would do without the `?`. After that the type is wrapped in a `\phpDocumentor\Reflection\Types\Nullable` object.
The `Nullable` type has a method to fetch the actual type.
## Resolving an FQSEN
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:
```php
$fqsenResolver = new \phpDocumentor\Reflection\FqsenResolver();
$fqsen = $fqsenResolver->resolve('\phpDocumentor\Reflection\FqsenResolver::resolve()');
```
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`.
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.
## Resolving partial Classes and Structural Element Names
Perhaps the best feature of this library is that it knows how to resolve partial class names into fully qualified class names.
For example, you have this file:
```php
namespace My\Example;
use phpDocumentor\Reflection\Types;
class Classy
{
/**
* @var Types\Context
* @see Classy::otherFunction()
*/
public function __construct($context) {}
public function otherFunction(){}
}
```
Suppose that you would want to resolve (and expand) the type in the `@var` tag and the element name in the `@see` tag.
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.
### Creating a Context
You can do this by manually creating a Context like this:
```php
$context = new \phpDocumentor\Reflection\Types\Context(
'\My\Example',
[ 'Types' => '\phpDocumentor\Reflection\Types']
);
```
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.
```php
$contextFactory = new \phpDocumentor\Reflection\Types\ContextFactory();
$context = $contextFactory->createFromReflector(new ReflectionMethod('\My\Example\Classy', '__construct'));
```
or
```php
$contextFactory = new \phpDocumentor\Reflection\Types\ContextFactory();
$context = $contextFactory->createForNamespace('\My\Example', file_get_contents('My/Example/Classy.php'));
```
### Using the Context
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.
To obtain the resolved class name for the `@var` tag in the example above you can do:
```php
$typeResolver = new \phpDocumentor\Reflection\TypeResolver();
$type = $typeResolver->resolve('Types\Context', $context);
```
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`.
> Why is the FQSEN wrapped in another object `Object_`?
>
> 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.
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:
```php
$fqsenResolver = new \phpDocumentor\Reflection\FqsenResolver();
$type = $fqsenResolver->resolve('Classy::otherFunction()', $context);
```
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()`.
================================================
FILE: composer-require-checker.json
================================================
{
"symbol-whitelist" : [
"null", "true", "false",
"static", "self", "parent",
"array", "string", "int", "float", "bool", "iterable", "callable", "void", "object", "XSLTProcessor", "PHPStan\\PhpDocParser\\ParserConfig",
"T_NAME_QUALIFIED", "T_NAME_FULLY_QUALIFIED"
],
"php-core-extensions" : [
"Core",
"pcre",
"Reflection",
"tokenizer",
"SPL",
"standard"
]
}
================================================
FILE: composer.json
================================================
{
"name": "phpdocumentor/type-resolver",
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Mike van Riel",
"email": "me@mikevanriel.com"
}
],
"require": {
"php": "^7.4 || ^8.0",
"phpdocumentor/reflection-common": "^2.0",
"phpstan/phpdoc-parser": "^2.0",
"doctrine/deprecations": "^1.0"
},
"require-dev": {
"ext-tokenizer": "*",
"phpunit/phpunit": "^9.5",
"phpstan/phpstan": "^2.1",
"phpstan/phpstan-phpunit": "^2.0",
"phpstan/extension-installer": "^1.4",
"phpbench/phpbench": "^1.2",
"psalm/phar": "^4"
},
"autoload": {
"psr-4": {
"phpDocumentor\\Reflection\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"phpDocumentor\\Reflection\\": ["tests/unit", "tests/benchmark"]
}
},
"extra": {
"branch-alias": {
"dev-1.x": "1.x-dev",
"dev-2.x": "2.x-dev"
}
},
"config": {
"platform": {
"php": "7.4.0"
},
"allow-plugins": {
"phpstan/extension-installer": true
}
}
}
================================================
FILE: docs/generics.rst
================================================
========
Generics
========
This project is capable of parsing generics notation as used by PHPStan. But it has some limitations, in regards to
PHPStan. The main difference is that PHPStan does scan your whole codebase to find out what types are used in generics,
while this library only parses the types as they are given to it.
This means that if you use a generic type like.
.. code:: php
namespace MyApp;
/**
* @template T of Item
*/
class Collection {
/**
* @return T[]
*/
public function getItems() : array {
// ...
}
}
The type resolver will not be able to determine what ``T`` is. In fact there is no difference between ``T`` and any other relative
used classname like ``Item``. The resolver will handle ``T`` as a normal class name. In this example it will resolve ``T`` to ``\MyApp\T``.
================================================
FILE: docs/getting-started.rst
================================================
===============
Getting started
===============
On this page you will find a brief introduction on how to use the TypeResolver in your project.
Installation
============
The TypeResolver is available on Packagist and can be installed using Composer:
.. code:: bash
composer require phpdocumentor/type-resolver
General usage
===========
After you installed the TypeResolver you can use it in your project. This can be done by creating a new instance
of the :php:class:`\phpDocumentor\Reflection\TypeResolver` class and calling
:php:method:`\phpDocumentor\Reflection\TypeResolver::resolve()` with the type you want to resolve.
.. code:: php
$typeResolver = new \phpDocumentor\Reflection\TypeResolver();
$type = $typeResolver->resolve('string');
echo get_class($type); // phpDocumentor\Reflection\Types\String_
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 :php:class:`\phpDocumentor\Reflection\Types\Context` class that
will inform the resolver in which namespace the given expression occurs and which namespace aliases (or imports) apply.
Read more about the Context class in the next section.
================================================
FILE: docs/index.rst
================================================
=============
Type resolver
=============
This project part of the phpDocumentor project. It is capable of creating an object structure of the type
specifications found in the PHPDoc blocks of a project. This can be useful for static analysis of a project
or other behavior that requires knowledge of the types used in a project like automatically build forms.
This project aims to cover all types that are available in PHPDoc and PHP itself. And is open for extension by
third party developers.
.. toctree::
:maxdepth: 2
:hidden:
index
getting-started
generics
upgrade-v1-to-v2
================================================
FILE: docs/upgrade-v1-to-v2.rst
================================================
====================
Upgrade to Version 2
====================
Version 2 of the Type Resolver introduces several breaking changes and new features. This guide will help you
upgrade your codebase to be compatible with the latest version. The usage of the TypeResolver remains the same. However,
some classes have been moved or replaced, and the minimum PHP version requirement has been raised.
PHP Version
-----------
Version 2 requires PHP 7.4 or higher. We have been supporting PHP 7.3 in version 1, but due to changing constraints
in our dependencies, we have had to raise the minimum PHP version. At the moment of writing this, PHP 7.3 is used by 2%
of all installations of this package according to Packagist. We believe this is a reasonable trade-off to ensure we
can continue to deliver new features and improvements.
Moved classes
-------------
- ``phpDocumentor\Reflection\Types\InterfaceString`` => :php:class:`phpDocumentor\Reflection\PseudoTypes\InterfaceString`
- ``phpDocumentor\Reflection\Types\ClassString`` => :php:class:`phpDocumentor\Reflection\PseudoTypes\ClassString`
- ``phpDocumentor\Reflection\Types\ArrayKey`` => :php:class:`phpDocumentor\Reflection\PseudoTypes\ArrayKey`
- ``phpDocumentor\Reflection\Types\True_`` => :php:class:`phpDocumentor\Reflection\PseudoTypes\True_`
- ``phpDocumentor\Reflection\Types\False_`` => :php:class:`phpDocumentor\Reflection\PseudoTypes\False_`
Replaced classes
-----------------
- ``phpDocumentor\Reflection\Types\Collection`` => :php:class:`phpDocumentor\Reflection\PseudoTypes\Generic`
Since the introduction of generics in PHP this library was not capable of parsing them correctly. The old Collection
was blocking the use of generics. The new Generic type is a representation of generics like supported in the eco system.
Changed implementations
-----------------------
:php:class:`phpDocumentor\Reflection\PseudoTypes\InterfaceString`, :php:class:`phpDocumentor\Reflection\PseudoTypes\ClassString` and
:php:class:`phpDocumentor\Reflection\PseudoTypes\TraitString` are no longer returning a :php:class:`phpDocumentor\Reflection\Fqsen` since
support for generics these classes can have type arguments like any other generic.
================================================
FILE: examples/01-resolving-simple-types.php
================================================
resolve('string|integer'));
// Will return the string "string|int"
var_dump((string)$typeResolver->resolve('string|integer'));
================================================
FILE: examples/02-resolving-classes.php
================================================
'Mockery' ]);
var_dump((string)$typeResolver->resolve('Types\Resolver|m\MockInterface', $context));
================================================
FILE: examples/03-resolving-all-elements.php
================================================
resolve('Types\Resolver::resolveFqsen()', $context));
// Property named: \phpDocumentor\Types\Types\Resolver::$keyWords
var_dump((string)$fqsenResolver->resolve('Types\Resolver::$keyWords', $context));
================================================
FILE: examples/04-discovering-the-context-using-class-reflection.php
================================================
createFromReflector(new ReflectionClass('My\\Example\\Classy'));
// Class named: \phpDocumentor\Reflection\Types\Resolver
var_dump((string)$typeResolver->resolve('Types\Resolver', $context));
// String
var_dump((string)$typeResolver->resolve('string', $context));
// Property named: \phpDocumentor\Reflection\Types\Resolver::$keyWords
var_dump((string)$fqsenResolver->resolve('Types\Resolver::$keyWords', $context));
// Class named: \My\Example\string
// - Shows the difference between the FqsenResolver and TypeResolver; the FqsenResolver will assume
// that the given value is not a type but most definitely a reference to another element. This is
// because conflicts between type keywords and class names can exist and if you know a reference
// is not a type but an element you can force that keywords are resolved.
var_dump((string)$fqsenResolver->resolve('string', $context));
================================================
FILE: examples/05-discovering-the-context-using-method-reflection.php
================================================
createFromReflector(new ReflectionMethod('My\\Example\\Classy', '__construct'));
// Class named: \phpDocumentor\Reflection\Types\Resolver
var_dump((string)$typeResolver->resolve('Types\Resolver', $context));
// String
var_dump((string)$typeResolver->resolve('string', $context));
// Property named: \phpDocumentor\Reflection\Types\Resolver::$keyWords
var_dump((string)$fqsenResolver->resolve('Types\Resolver::$keyWords', $context));
// Class named: \My\Example\string
// - Shows the difference between the FqsenResolver and TypeResolver; the FqsenResolver will assume
// that the given value is not a type but most definitely a reference to another element. This is
// because conflicts between type keywords and class names can exist and if you know a reference
// is not a type but an element you can force that keywords are resolved.
var_dump((string)$fqsenResolver->resolve('string', $context));
================================================
FILE: examples/06-discovering-the-context-using-file-contents.php
================================================
createForNamespace('My\Example', file_get_contents(__DIR__ . '/Classy.php'));
// Class named: \phpDocumentor\Reflection\Types\Resolver
var_dump((string)$typeResolver->resolve('Types\Resolver', $context));
// String
var_dump((string)$typeResolver->resolve('string', $context));
// Property named: \phpDocumentor\Reflection\Types\Resolver::$keyWords
var_dump((string)$fqsenResolver->resolve('Types\Resolver::$keyWords', $context));
================================================
FILE: examples/Classy.php
================================================
The coding standard for this library.srctests/unit*/tests/unit/Types/ContextFactoryTest\.php*/src/*/Abstract*\.php*/src/PseudoTypes/False_\.php*/src/PseudoTypes/True_\.php*/src/Types/ContextFactory\.php*/src/Types/ContextFactory\.php
================================================
FILE: phpdoc.dist.xml
================================================
Type Resolverlatestsrc/tests/**/*build/**/*var/**/*vendor/**/*phptemplatetemplate-extendstemplate-implementsextendsimplementsphpDocumentordocs
================================================
FILE: phpstan.neon
================================================
parameters:
bootstrapFiles:
- src/PseudoTypes/False_.php
- src/PseudoTypes/True_.php
level: max
excludePaths:
- tests/benchmark/Assets/*
paths:
- src
- tests
ignoreErrors:
# We are intentionally using non-existing classes here
-
message: "#^Class phpDocumentor\\\\Reflection\\\\DocBlock not found#"
count: 1
path: tests/unit/Types/ContextFactoryTest.php
-
message: "#^Class phpDocumentor\\\\Reflection\\\\DocBlock\\\\Tag not found#"
count: 1
path: tests/unit/Types/ContextFactoryTest.php
# We are intentionally adding invalid parameters here
-
message: "#^Parameter \\#2 \\$typeClassName of method phpDocumentor\\\\Reflection\\\\TypeResolver\\:\\:addKeyword\\(\\) expects class\\-string\\\\, string given\\.$#"
count: 2
path: tests/unit/TypeResolverTest.php
# We are intentionally adding invalid parameters here
-
message: "#^Parameter \\#1 \\$types of class phpDocumentor\\\\Reflection\\\\Types\\\\Compound constructor expects array\\\\, array\\ given\\.$#"
count: 1
path: tests/unit/Types/CompoundTest.php
-
message: "#^Parameter \\#1 \\$objectOrClass of class ReflectionClass constructor expects class\\-string\\\\|Foo\\\\Bar\\, string given\\.$#"
count: 1
path: tests/unit/Types/ContextFactoryTest.php
================================================
FILE: phpunit.xml.dist
================================================
src./tests/unit/
================================================
FILE: psalm.xml
================================================
================================================
FILE: src/FqsenResolver.php
================================================
isFqsen($fqsen)) {
return new Fqsen($fqsen);
}
return $this->resolvePartialStructuralElementName($fqsen, $context);
}
/**
* Tests whether the given type is a Fully Qualified Structural Element Name.
*/
private function isFqsen(string $type): bool
{
return strpos($type, self::OPERATOR_NAMESPACE) === 0;
}
/**
* Resolves a partial Structural Element Name (i.e. `Reflection\DocBlock`) to its FQSEN representation
* (i.e. `\phpDocumentor\Reflection\DocBlock`) based on the Namespace and aliases mentioned in the Context.
*
* @throws InvalidArgumentException When type is not a valid FQSEN.
*/
private function resolvePartialStructuralElementName(string $type, Context $context): Fqsen
{
$typeParts = explode(self::OPERATOR_NAMESPACE, $type, 2);
$namespaceAliases = $context->getNamespaceAliases();
// if the first segment is not an alias; prepend namespace name and return
if (!isset($namespaceAliases[$typeParts[0]])) {
$namespace = $context->getNamespace();
if ($namespace !== '') {
$namespace .= self::OPERATOR_NAMESPACE;
}
return new Fqsen(self::OPERATOR_NAMESPACE . $namespace . $type);
}
$typeParts[0] = $namespaceAliases[$typeParts[0]];
return new Fqsen(self::OPERATOR_NAMESPACE . implode(self::OPERATOR_NAMESPACE, $typeParts));
}
}
================================================
FILE: src/PseudoType.php
================================================
items = $items;
}
/**
* @return ArrayShapeItem[]
*/
public function getItems(): array
{
return $this->items;
}
public function underlyingType(): Type
{
return new Array_(new Mixed_(), new ArrayKey());
}
public function __toString(): string
{
return 'array{' . implode(', ', $this->items) . '}';
}
}
================================================
FILE: src/PseudoTypes/ArrayShapeItem.php
================================================
genericType = $genericType;
}
public function underlyingType(): Type
{
return new String_();
}
public function getGenericType(): ?Type
{
return $this->genericType;
}
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString(): string
{
if ($this->genericType === null) {
return 'class-string';
}
return 'class-string<' . (string) $this->genericType . '>';
}
}
================================================
FILE: src/PseudoTypes/ClosedResource.php
================================================
negated = $negated;
$this->subjectType = $subjectType;
$this->targetType = $targetType;
$this->if = $if;
$this->else = $else;
}
public function isNegated(): bool
{
return $this->negated;
}
public function getSubjectType(): Type
{
return $this->subjectType;
}
public function getTargetType(): Type
{
return $this->targetType;
}
public function getIf(): Type
{
return $this->if;
}
public function getElse(): Type
{
return $this->else;
}
public function underlyingType(): Type
{
return new Mixed_();
}
public function __toString(): string
{
return sprintf(
'(%s %s %s ? %s : %s)',
(string) $this->subjectType,
$this->negated ? 'is not' : 'is',
(string) $this->targetType,
(string) $this->if,
(string) $this->else
);
}
}
================================================
FILE: src/PseudoTypes/ConditionalForParameter.php
================================================
negated = $negated;
$this->parameterName = $parameterName;
$this->targetType = $targetType;
$this->if = $if;
$this->else = $else;
}
public function isNegated(): bool
{
return $this->negated;
}
public function getParameterName(): string
{
return $this->parameterName;
}
public function getTargetType(): Type
{
return $this->targetType;
}
public function getIf(): Type
{
return $this->if;
}
public function getElse(): Type
{
return $this->else;
}
public function underlyingType(): Type
{
return new Mixed_();
}
public function __toString(): string
{
return sprintf(
'(%s %s %s ? %s : %s)',
'$' . $this->parameterName,
$this->negated ? 'is not' : 'is',
(string) $this->targetType,
(string) $this->if,
(string) $this->else
);
}
}
================================================
FILE: src/PseudoTypes/ConstExpression.php
================================================
owner = $owner;
$this->expression = $expression;
}
public function getOwner(): Type
{
return $this->owner;
}
public function getExpression(): string
{
return $this->expression;
}
public function underlyingType(): Type
{
return new Mixed_();
}
public function __toString(): string
{
return sprintf('%s::%s', (string) $this->owner, $this->expression);
}
}
================================================
FILE: src/PseudoTypes/EnumString.php
================================================
genericType = $genericType;
}
public function underlyingType(): Type
{
return new String_();
}
public function getGenericType(): ?Type
{
return $this->genericType;
}
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString(): string
{
if ($this->genericType === null) {
return 'enum-string';
}
return 'enum-string<' . (string) $this->genericType . '>';
}
}
================================================
FILE: src/PseudoTypes/False_.php
================================================
value = $value;
}
public function getValue(): float
{
return $this->value;
}
public function underlyingType(): Type
{
return new Float_();
}
public function __toString(): string
{
return (string) $this->value;
}
}
================================================
FILE: src/PseudoTypes/Generic.php
================================================
types = $types;
}
/**
* @return Type[]
*/
public function getTypes(): array
{
return $this->types;
}
public function __toString(): string
{
$objectType = (string) ($this->fqsen ?? 'object');
return $objectType . '<' . implode(', ', $this->types) . '>';
}
}
================================================
FILE: src/PseudoTypes/HtmlEscapedString.php
================================================
types = $types;
}
/**
* @return Type[]
*/
public function getTypes(): array
{
return $this->types;
}
public function underlyingType(): Type
{
return new Integer();
}
public function __toString(): string
{
return 'int-mask<' . implode(', ', $this->types) . '>';
}
}
================================================
FILE: src/PseudoTypes/IntMaskOf.php
================================================
type = $type;
}
public function getType(): Type
{
return $this->type;
}
public function underlyingType(): Type
{
return new Integer();
}
public function __toString(): string
{
return 'int-mask-of<' . $this->type . '>';
}
}
================================================
FILE: src/PseudoTypes/IntegerRange.php
================================================
minValue = $minValue;
$this->maxValue = $maxValue;
}
public function underlyingType(): Type
{
return new Integer();
}
public function getMinValue(): string
{
return $this->minValue;
}
public function getMaxValue(): string
{
return $this->maxValue;
}
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString(): string
{
return 'int<' . $this->minValue . ', ' . $this->maxValue . '>';
}
}
================================================
FILE: src/PseudoTypes/IntegerValue.php
================================================
value = $value;
}
public function getValue(): int
{
return $this->value;
}
public function underlyingType(): Type
{
return new Integer();
}
public function __toString(): string
{
return (string) $this->value;
}
}
================================================
FILE: src/PseudoTypes/InterfaceString.php
================================================
genericType = $genericType;
}
public function underlyingType(): Type
{
return new String_();
}
public function getGenericType(): ?Type
{
return $this->genericType;
}
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString(): string
{
if ($this->genericType === null) {
return 'interface-string';
}
return 'interface-string<' . (string) $this->genericType . '>';
}
}
================================================
FILE: src/PseudoTypes/KeyOf.php
================================================
type = $type;
}
public function getType(): Type
{
return $this->type;
}
public function underlyingType(): Type
{
return new ArrayKey();
}
public function __toString(): string
{
return 'key-of<' . $this->type . '>';
}
}
================================================
FILE: src/PseudoTypes/ListShape.php
================================================
getItems()) . '}';
}
}
================================================
FILE: src/PseudoTypes/ListShapeItem.php
================================================
valueType === null) {
return 'list';
}
return 'list<' . $this->valueType . '>';
}
}
================================================
FILE: src/PseudoTypes/LiteralString.php
================================================
valueType, $this->keyType);
}
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString(): string
{
if ($this->valueType === null) {
return 'non-empty-array';
}
if ($this->keyType) {
return 'non-empty-array<' . $this->keyType . ', ' . $this->valueType . '>';
}
return 'non-empty-array<' . $this->valueType . '>';
}
}
================================================
FILE: src/PseudoTypes/NonEmptyList.php
================================================
valueType, $this->keyType);
}
public function __construct(?Type $valueType = null)
{
parent::__construct($valueType, new Integer());
}
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString(): string
{
if ($this->valueType === null) {
return 'non-empty-list';
}
return 'non-empty-list<' . $this->valueType . '>';
}
}
================================================
FILE: src/PseudoTypes/NonEmptyLowercaseString.php
================================================
items = $items;
}
/**
* @return ObjectShapeItem[]
*/
public function getItems(): array
{
return $this->items;
}
public function underlyingType(): Type
{
return new Object_();
}
public function __toString(): string
{
return 'object{' . implode(', ', $this->items) . '}';
}
}
================================================
FILE: src/PseudoTypes/ObjectShapeItem.php
================================================
type = $type;
$this->offset = $offset;
}
public function getType(): Type
{
return $this->type;
}
public function getOffset(): Type
{
return $this->offset;
}
public function underlyingType(): Type
{
return new Mixed_();
}
public function __toString(): string
{
if (
$this->type instanceof Callable_
|| $this->type instanceof ConstExpression
|| $this->type instanceof Nullable
) {
return '(' . $this->type . ')[' . $this->offset . ']';
}
return $this->type . '[' . $this->offset . ']';
}
}
================================================
FILE: src/PseudoTypes/OpenResource.php
================================================
type . '>';
}
}
================================================
FILE: src/PseudoTypes/PropertiesOf.php
================================================
type = $type;
}
public function getType(): Type
{
return $this->type;
}
public function underlyingType(): Type
{
return new Array_(new Mixed_(), new String_());
}
public function __toString(): string
{
return 'properties-of<' . $this->type . '>';
}
}
================================================
FILE: src/PseudoTypes/ProtectedPropertiesOf.php
================================================
type . '>';
}
}
================================================
FILE: src/PseudoTypes/PublicPropertiesOf.php
================================================
type . '>';
}
}
================================================
FILE: src/PseudoTypes/Scalar.php
================================================
key = $key;
$this->value = $value ?? new Mixed_();
$this->optional = $optional;
}
public function getKey(): ?string
{
return $this->key;
}
public function getValue(): Type
{
return $this->value;
}
public function isOptional(): bool
{
return $this->optional;
}
public function __toString(): string
{
if ($this->key !== null && $this->key !== '') {
return sprintf(
'%s%s: %s',
$this->key,
$this->optional ? '?' : '',
(string) $this->value
);
}
return (string) $this->value;
}
}
================================================
FILE: src/PseudoTypes/StringValue.php
================================================
value = $value;
}
public function getValue(): string
{
return $this->value;
}
public function underlyingType(): Type
{
return new String_();
}
public function __toString(): string
{
return sprintf('"%s"', $this->value);
}
}
================================================
FILE: src/PseudoTypes/TraitString.php
================================================
genericType = $genericType;
}
public function underlyingType(): Type
{
return new String_();
}
public function getGenericType(): ?Type
{
return $this->genericType;
}
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString(): string
{
if ($this->genericType === null) {
return 'trait-string';
}
return 'trait-string<' . (string) $this->genericType . '>';
}
}
================================================
FILE: src/PseudoTypes/True_.php
================================================
type = $type;
}
public function getType(): Type
{
return $this->type;
}
public function underlyingType(): Type
{
return new Mixed_();
}
public function __toString(): string
{
return 'value-of<' . $this->type . '>';
}
}
================================================
FILE: src/Type.php
================================================
List of recognized keywords and unto which Value Object they map
* @psalm-var array>
*/
private $keywords = [
'string' => String_::class,
'class-string' => ClassString::class,
'interface-string' => InterfaceString::class,
'html-escaped-string' => HtmlEscapedString::class,
'lowercase-string' => LowercaseString::class,
'non-empty-lowercase-string' => NonEmptyLowercaseString::class,
'non-empty-string' => NonEmptyString::class,
'numeric-string' => NumericString::class,
'numeric' => Numeric_::class,
'trait-string' => TraitString::class,
'enum-string' => EnumString::class,
'int' => Integer::class,
'integer' => Integer::class,
'positive-int' => PositiveInteger::class,
'negative-int' => NegativeInteger::class,
'bool' => Boolean::class,
'boolean' => Boolean::class,
'real' => Float_::class,
'float' => Float_::class,
'double' => Float_::class,
'object' => Object_::class,
'mixed' => Mixed_::class,
'array' => Array_::class,
'callable-array' => CallableArray::class,
'array-key' => ArrayKey::class,
'non-empty-array' => NonEmptyArray::class,
'resource' => Resource_::class,
'open-resource' => OpenResource::class,
'closed-resource' => ClosedResource::class,
'void' => Void_::class,
'null' => Null_::class,
'scalar' => Scalar::class,
'callback' => Callable_::class,
'callable' => Callable_::class,
'callable-string' => CallableString::class,
'false' => False_::class,
'true' => True_::class,
'literal-string' => LiteralString::class,
'self' => Self_::class,
'$this' => This::class,
'static' => Static_::class,
'parent' => Parent_::class,
'iterable' => Iterable_::class,
'never' => Never_::class,
'never-return' => NeverReturn::class,
'never-returns' => NeverReturns::class,
'no-return' => NoReturn::class,
'list' => List_::class,
'non-empty-list' => NonEmptyList::class,
'non-falsy-string' => NonFalsyString::class,
'truthy-string' => TruthyString::class,
'non-positive-int' => NonPositiveInteger::class,
'non-negative-int' => NonNegativeInteger::class,
'non-zero-int' => NonZeroInteger::class,
];
/**
* @psalm-readonly
* @var FqsenResolver
*/
private $fqsenResolver;
/**
* @psalm-readonly
* @var TypeParser
*/
private $typeParser;
/**
* @psalm-readonly
* @var Lexer
*/
private $lexer;
/**
* Initializes this TypeResolver with the means to create and resolve Fqsen objects.
*/
public function __construct(?FqsenResolver $fqsenResolver = null)
{
$this->fqsenResolver = $fqsenResolver ?: new FqsenResolver();
$this->typeParser = new TypeParser(new ParserConfig([]), new ConstExprParser(new ParserConfig([])));
$this->lexer = new Lexer(new ParserConfig([]));
}
/**
* Analyzes the given type and returns the FQCN variant.
*
* When a type is provided this method checks whether it is not a keyword or
* Fully Qualified Class Name. If so it will use the given namespace and
* aliases to expand the type to a FQCN representation.
*
* This method only works as expected if the namespace and aliases are set;
* no dynamic reflection is being performed here.
*
* @uses Context::getNamespace() to determine with what to prefix the type name.
* @uses Context::getNamespaceAliases() to check whether the first part of the relative type name should not be
* replaced with another namespace.
*
* @param string $type The relative or absolute type.
*/
public function resolve(string $type, ?Context $context = null): Type
{
$type = trim($type);
if (!$type) {
throw new InvalidArgumentException('Attempted to resolve "' . $type . '" but it appears to be empty');
}
if ($context === null) {
$context = new Context('');
}
$tokens = $this->lexer->tokenize($type);
$tokenIterator = new TokenIterator($tokens);
$ast = $this->parse($tokenIterator);
$type = $this->createType($ast, $context);
if (
$tokenIterator->isCurrentTokenType(Lexer::TOKEN_UNION) ||
$tokenIterator->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)
) {
Deprecation::trigger(
'phpdocumentor/type-resolver',
'https://github.com/phpDocumentor/TypeResolver/issues/184',
'Legacy nullable type detected, please update your code as
you are using nullable types in a docblock. support is removed in v2.0.0'
);
}
return $type;
}
public function createType(?TypeNode $type, Context $context): Type
{
if ($type === null) {
return new Mixed_();
}
switch (get_class($type)) {
case ArrayTypeNode::class:
return new Array_(
$this->createType($type->type, $context)
);
case ArrayShapeNode::class:
switch ($type->kind) {
case ArrayShapeNode::KIND_ARRAY:
return new ArrayShape(
...array_map(
function (ArrayShapeItemNode $item) use ($context): ArrayShapeItem {
return new ArrayShapeItem(
$item->keyName !== null ? (string) $item->keyName : null,
$this->createType($item->valueType, $context),
$item->optional
);
},
$type->items
)
);
case ArrayShapeNode::KIND_LIST:
return new ListShape(
...array_map(
function (ArrayShapeItemNode $item) use ($context): ListShapeItem {
return new ListShapeItem(
null,
$this->createType($item->valueType, $context),
$item->optional
);
},
$type->items
)
);
default:
throw new RuntimeException('Unsupported array shape kind');
}
case ObjectShapeNode::class:
return new ObjectShape(
...array_map(
function (ObjectShapeItemNode $item) use ($context): ObjectShapeItem {
return new ObjectShapeItem(
(string) $item->keyName,
$this->createType($item->valueType, $context),
$item->optional
);
},
$type->items
)
);
case CallableTypeNode::class:
return $this->createFromCallable($type, $context);
case ConstTypeNode::class:
return $this->createFromConst($type, $context);
case GenericTypeNode::class:
return $this->createFromGeneric($type, $context);
case IdentifierTypeNode::class:
return $this->resolveSingleType($type->name, $context);
case IntersectionTypeNode::class:
return new Intersection(
array_map(
function (TypeNode $nestedType) use ($context): Type {
$type = $this->createType($nestedType, $context);
if ($type instanceof AggregatedType) {
return new Expression($type);
}
return $type;
},
$type->types
)
);
case NullableTypeNode::class:
$nestedType = $this->createType($type->type, $context);
return new Nullable($nestedType);
case UnionTypeNode::class:
return new Compound(
array_map(
function (TypeNode $nestedType) use ($context): Type {
$type = $this->createType($nestedType, $context);
if ($type instanceof AggregatedType) {
return new Expression($type);
}
return $type;
},
$type->types
)
);
case ThisTypeNode::class:
return new This();
case ConditionalTypeNode::class:
return new Conditional(
$type->negated,
$this->createType($type->subjectType, $context),
$this->createType($type->targetType, $context),
$this->createType($type->if, $context),
$this->createType($type->else, $context),
);
case ConditionalTypeForParameterNode::class:
return new ConditionalForParameter(
$type->negated,
substr($type->parameterName, 1),
$this->createType($type->targetType, $context),
$this->createType($type->if, $context),
$this->createType($type->else, $context),
);
case OffsetAccessTypeNode::class:
return new OffsetAccess(
$this->createType($type->type, $context),
$this->createType($type->offset, $context)
);
default:
return new Mixed_();
}
}
private function createFromGeneric(GenericTypeNode $type, Context $context): Type
{
switch (strtolower($type->type->name)) {
case 'array':
$genericTypes = array_reverse($this->createTypesByTypeNodes($type->genericTypes, $context));
return new Array_(...$genericTypes);
case 'non-empty-array':
$genericTypes = array_reverse($this->createTypesByTypeNodes($type->genericTypes, $context));
return new NonEmptyArray(...$genericTypes);
case 'class-string':
return new ClassString($this->createType($type->genericTypes[0], $context));
case 'interface-string':
return new InterfaceString($this->createType($type->genericTypes[0], $context));
case 'trait-string':
return new TraitString($this->createType($type->genericTypes[0], $context));
case 'enum-string':
return new EnumString($this->createType($type->genericTypes[0], $context));
case 'list':
return new List_(
$this->createType($type->genericTypes[0], $context)
);
case 'non-empty-list':
return new NonEmptyList(
$this->createType($type->genericTypes[0], $context)
);
case 'int':
if (isset($type->genericTypes[1]) === false) {
throw new RuntimeException('int has not the correct format');
}
return new IntegerRange((string) $type->genericTypes[0], (string) $type->genericTypes[1]);
case 'iterable':
return new Iterable_(...array_reverse($this->createTypesByTypeNodes($type->genericTypes, $context)));
case 'key-of':
return new KeyOf($this->createType($type->genericTypes[0], $context));
case 'value-of':
return new ValueOf($this->createType($type->genericTypes[0], $context));
case 'properties-of':
return new PropertiesOf($this->createType($type->genericTypes[0], $context));
case 'public-properties-of':
return new PublicPropertiesOf($this->createType($type->genericTypes[0], $context));
case 'protected-properties-of':
return new ProtectedPropertiesOf($this->createType($type->genericTypes[0], $context));
case 'private-properties-of':
return new PrivatePropertiesOf($this->createType($type->genericTypes[0], $context));
case 'int-mask':
return new IntMask(...$this->createTypesByTypeNodes($type->genericTypes, $context));
case 'int-mask-of':
return new IntMaskOf($this->createType($type->genericTypes[0], $context));
case 'static':
return new Static_(...$this->createTypesByTypeNodes($type->genericTypes, $context));
case 'self':
return new Self_(...$this->createTypesByTypeNodes($type->genericTypes, $context));
default:
$mainType = $this->createType($type->type, $context);
if ($mainType instanceof Object_ === false) {
throw new RuntimeException(sprintf('%s is an unsupported generic', (string) $mainType));
}
$types = $this->createTypesByTypeNodes($type->genericTypes, $context);
return new Generic($mainType->getFqsen(), $types);
}
}
private function createFromCallable(CallableTypeNode $type, Context $context): Callable_
{
return new Callable_(
(string) $type->identifier,
array_map(
function (CallableTypeParameterNode $param) use ($context): CallableParameter {
return new CallableParameter(
$this->createType($param->type, $context),
$param->parameterName !== '' ? trim($param->parameterName, '$') : null,
$param->isReference,
$param->isVariadic,
$param->isOptional
);
},
$type->parameters
),
$this->createType($type->returnType, $context)
);
}
private function createFromConst(ConstTypeNode $type, Context $context): Type
{
switch (true) {
case $type->constExpr instanceof ConstExprIntegerNode:
return new IntegerValue((int) $type->constExpr->value);
case $type->constExpr instanceof ConstExprFloatNode:
return new FloatValue((float) $type->constExpr->value);
case $type->constExpr instanceof ConstExprStringNode:
return new StringValue($type->constExpr->value);
case $type->constExpr instanceof ConstFetchNode:
return new ConstExpression(
$this->resolve($type->constExpr->className, $context),
$type->constExpr->name
);
default:
throw new RuntimeException(sprintf('Unsupported constant type %s', get_class($type)));
}
}
/**
* resolve the given type into a type object
*
* @param string $type the type string, representing a single type
*
* @return Type|Array_|Object_
*
* @psalm-mutation-free
*/
private function resolveSingleType(string $type, Context $context): object
{
switch (true) {
case $this->isKeyword($type):
return $this->resolveKeyword($type);
case $this->isFqsen($type):
return $this->resolveTypedObject($type);
case $this->isPartialStructuralElementName($type):
return $this->resolveTypedObject($type, $context);
// @codeCoverageIgnoreStart
default:
// I haven't got the foggiest how the logic would come here but added this as a defense.
throw new RuntimeException(
'Unable to resolve type "' . $type . '", there is no known method to resolve it'
);
}
// @codeCoverageIgnoreEnd
}
/**
* Adds a keyword to the list of Keywords and associates it with a specific Value Object.
*
* @psalm-param class-string $typeClassName
*/
public function addKeyword(string $keyword, string $typeClassName): void
{
if (!class_exists($typeClassName)) {
throw new InvalidArgumentException(
'The Value Object that needs to be created with a keyword "' . $keyword . '" must be an existing class'
. ' but we could not find the class ' . $typeClassName
);
}
$interfaces = class_implements($typeClassName);
if ($interfaces === false) {
throw new InvalidArgumentException(
'The Value Object that needs to be created with a keyword "' . $keyword . '" must be an existing class'
. ' but we could not find the class ' . $typeClassName
);
}
if (!in_array(Type::class, $interfaces, true)) {
throw new InvalidArgumentException(
'The class "' . $typeClassName . '" must implement the interface "phpDocumentor\Reflection\Type"'
);
}
$this->keywords[$keyword] = $typeClassName;
}
/**
* Detects whether the given type represents a PHPDoc keyword.
*
* @param string $type A relative or absolute type as defined in the phpDocumentor documentation.
*
* @psalm-mutation-free
*/
private function isKeyword(string $type): bool
{
return array_key_exists(strtolower($type), $this->keywords);
}
/**
* Detects whether the given type represents a relative structural element name.
*
* @param string $type A relative or absolute type as defined in the phpDocumentor documentation.
*
* @psalm-mutation-free
*/
private function isPartialStructuralElementName(string $type): bool
{
return (isset($type[0]) && $type[0] !== self::OPERATOR_NAMESPACE) && !$this->isKeyword($type);
}
/**
* Tests whether the given type is a Fully Qualified Structural Element Name.
*
* @psalm-mutation-free
*/
private function isFqsen(string $type): bool
{
return strpos($type, self::OPERATOR_NAMESPACE) === 0;
}
/**
* Resolves the given keyword (such as `string`) into a Type object representing that keyword.
*
* @psalm-mutation-free
*/
private function resolveKeyword(string $type): Type
{
$className = $this->keywords[strtolower($type)];
return new $className();
}
/**
* Resolves the given FQSEN string into an FQSEN object.
*
* @psalm-mutation-free
*/
private function resolveTypedObject(string $type, ?Context $context = null): Object_
{
return new Object_($this->fqsenResolver->resolve($type, $context));
}
private function parse(TokenIterator $tokenIterator): TypeNode
{
try {
$ast = $this->typeParser->parse($tokenIterator);
} catch (ParserException $e) {
throw new RuntimeException($e->getMessage(), 0, $e);
}
return $ast;
}
/**
* @param TypeNode[] $nodes
*
* @return Type[]
*/
private function createTypesByTypeNodes(array $nodes, Context $context): array
{
return array_map(
function (TypeNode $node) use ($context): Type {
return $this->createType($node, $context);
},
$nodes
);
}
}
================================================
FILE: src/Types/AbstractList.php
================================================
defaultValueType = new Mixed_();
$this->valueType = $valueType;
$this->defaultKeyType = new Compound([new String_(), new Integer()]);
$this->keyType = $keyType;
}
public function getOriginalKeyType(): ?Type
{
return $this->keyType;
}
public function getOriginalValueType(): ?Type
{
return $this->valueType;
}
/**
* Returns the type for the keys of this array.
*/
public function getKeyType(): Type
{
return $this->keyType ?? $this->defaultKeyType;
}
/**
* Returns the type for the values of this array.
*/
public function getValueType(): Type
{
return $this->valueType ?? $this->defaultValueType;
}
}
================================================
FILE: src/Types/AggregatedType.php
================================================
*/
abstract class AggregatedType implements Type, IteratorAggregate
{
/**
* @psalm-allow-private-mutation
* @var array
*/
private $types = [];
/** @var string */
private $token;
/**
* @param array $types
*/
public function __construct(array $types, string $token)
{
foreach ($types as $type) {
$this->add($type);
}
$this->token = $token;
}
/**
* Returns the type at the given index.
*/
public function get(int $index): ?Type
{
if (!$this->has($index)) {
return null;
}
return $this->types[$index];
}
/**
* Tests if this compound type has a type with the given index.
*/
public function has(int $index): bool
{
return array_key_exists($index, $this->types);
}
/**
* Tests if this compound type contains the given type.
*/
public function contains(Type $type): bool
{
foreach ($this->types as $typePart) {
// if the type is duplicate; do not add it
if ((string) $typePart === (string) $type) {
return true;
}
}
return false;
}
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString(): string
{
return implode($this->token, $this->types);
}
/**
* @return ArrayIterator
*/
public function getIterator(): ArrayIterator
{
return new ArrayIterator($this->types);
}
/**
* @psalm-suppress ImpureMethodCall
*/
private function add(Type $type): void
{
if ($type instanceof static) {
foreach ($type->getIterator() as $subType) {
$this->add($subType);
}
return;
}
// if the type is duplicate; do not add it
if ($this->contains($type)) {
return;
}
$this->types[] = $type;
}
}
================================================
FILE: src/Types/Array_.php
================================================
valueType === null) {
return 'array';
}
$valueTypeString = (string) $this->valueType;
if ($this->keyType) {
return 'array<' . $this->keyType . ', ' . $valueTypeString . '>';
}
if (!preg_match('/[^\w\\\\]/', $valueTypeString) || substr($valueTypeString, -2, 2) === '[]') {
return $valueTypeString . '[]';
}
return 'array<' . $valueTypeString . '>';
}
}
================================================
FILE: src/Types/Boolean.php
================================================
type = $type;
$this->isReference = $isReference;
$this->isVariadic = $isVariadic;
$this->isOptional = $isOptional;
$this->name = $name;
}
public function getName(): ?string
{
return $this->name;
}
public function getType(): Type
{
return $this->type;
}
public function isReference(): bool
{
return $this->isReference;
}
public function isVariadic(): bool
{
return $this->isVariadic;
}
public function isOptional(): bool
{
return $this->isOptional;
}
public function __toString(): string
{
$reference = $this->isReference ? '&' : '';
$variadic = $this->isVariadic ? '...' : '';
$optional = $this->isOptional ? '=' : '';
$name = $this->name !== null ? '$' . $this->name : '';
return trim($this->type . ' ' . $reference . $variadic . $name . $optional);
}
}
================================================
FILE: src/Types/Callable_.php
================================================
identifier = $identifier;
$this->parameters = $parameters;
$this->returnType = $returnType;
}
public function getIdentifier(): string
{
return $this->identifier;
}
/** @return CallableParameter[] */
public function getParameters(): array
{
return $this->parameters;
}
public function getReturnType(): ?Type
{
return $this->returnType;
}
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString(): string
{
if (!$this->parameters && $this->returnType === null) {
return $this->identifier;
}
if ($this->returnType instanceof self) {
$returnType = '(' . (string) $this->returnType . ')';
} else {
$returnType = (string) $this->returnType;
}
return $this->identifier . '(' . implode(', ', $this->parameters) . '): ' . $returnType;
}
}
================================================
FILE: src/Types/Compound.php
================================================
$types
*/
public function __construct(array $types)
{
parent::__construct($types, '|');
}
}
================================================
FILE: src/Types/Context.php
================================================
Fully Qualified Namespace.
* @psalm-var array
*/
private $namespaceAliases;
/**
* Initializes the new context and normalizes all passed namespaces to be in Qualified Namespace Name (QNN)
* format (without a preceding `\`).
*
* @param string $namespace The namespace where this DocBlock resides in.
* @param string[] $namespaceAliases List of namespace aliases => Fully Qualified Namespace.
* @psalm-param array $namespaceAliases
*/
public function __construct(string $namespace, array $namespaceAliases = [])
{
$this->namespace = $namespace !== 'global' && $namespace !== 'default'
? trim($namespace, '\\')
: '';
foreach ($namespaceAliases as $alias => $fqnn) {
if ($fqnn[0] === '\\') {
$fqnn = substr($fqnn, 1);
}
if ($fqnn[strlen($fqnn) - 1] === '\\') {
$fqnn = substr($fqnn, 0, -1);
}
$namespaceAliases[$alias] = $fqnn;
}
$this->namespaceAliases = $namespaceAliases;
}
/**
* Returns the Qualified Namespace Name (thus without `\` in front) where the associated element is in.
*/
public function getNamespace(): string
{
return $this->namespace;
}
/**
* Returns a list of Qualified Namespace Names (thus without `\` in front) that are imported, the keys represent
* the alias for the imported Namespace.
*
* @return string[]
* @psalm-return array
*/
public function getNamespaceAliases(): array
{
return $this->namespaceAliases;
}
}
================================================
FILE: src/Types/ContextFactory.php
================================================
$reflector */
return $this->createFromReflectionClass($reflector);
}
if ($reflector instanceof ReflectionParameter) {
return $this->createFromReflectionParameter($reflector);
}
if ($reflector instanceof ReflectionMethod) {
return $this->createFromReflectionMethod($reflector);
}
if ($reflector instanceof ReflectionProperty) {
return $this->createFromReflectionProperty($reflector);
}
if ($reflector instanceof ReflectionClassConstant) {
return $this->createFromReflectionClassConstant($reflector);
}
throw new UnexpectedValueException('Unhandled \Reflector instance given: ' . get_class($reflector));
}
private function createFromReflectionParameter(ReflectionParameter $parameter): Context
{
$class = $parameter->getDeclaringClass();
if (!$class) {
throw new InvalidArgumentException('Unable to get class of ' . $parameter->getName());
}
return $this->createFromReflectionClass($class);
}
private function createFromReflectionMethod(ReflectionMethod $method): Context
{
$class = $method->getDeclaringClass();
return $this->createFromReflectionClass($class);
}
private function createFromReflectionProperty(ReflectionProperty $property): Context
{
$class = $property->getDeclaringClass();
return $this->createFromReflectionClass($class);
}
private function createFromReflectionClassConstant(ReflectionClassConstant $constant): Context
{
//phpcs:ignore SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.MissingVariable
/** @phpstan-var ReflectionClass