uuf6429 / phpstan-phpdoc-type-resolver
从PHPStan的PHPDoc解析器中解析(完全限定)类型
3.0.0
2024-08-04 12:52 UTC
Requires
- php: ^8.1
- phpstan/phpdoc-parser: ^1.29
Requires (Dev)
- ergebnis/composer-normalize: ^2.42
- friendsofphp/php-cs-fixer: ^3.53
- phpstan/phpstan: ^1.11
- phpunit/phpunit: ^10.5.26
- roave/security-advisories: dev-latest
README
从PHPStan的PHPDoc解析器中解析(完全限定)类型。
💾 安装
可以使用 Composer 安装此包,只需运行以下命令
composer require uuf6429/phpstan-phpdoc-type-resolver
如果打算仅在开发期间使用此库,请考虑使用 --dev
。
🤔 为什么?
因为 phpstan/phpdoc-parser
不解析类型(这不是它的责任)并且 phpdocument/type-resolver
目前有一些重大限制。
🚀 使用方法
原则上,解析器需要以下两点
- PHPStan-PHPDoc 类型(
TypeNode
的实例)。 - 该类型出现位置的 'Scope' 信息。
有两种方式可以检索这些信息,如下所示。
重要:解析器将始终将一些特定的 PHPStan 类型转换为以下类型
- *
ThisTypeNode
转换为IdentifierTypeNode
,用于当前传递的类。 - *
GenericTypeNode
根据接收到的实例是否包含未解析的泛型/模板类型,转换为ConcreteGenericTypeNode
或TemplateGenericTypeNode
。 - PHPStan 本地定义或导入的类型,将提供
TypeDefTypeNode
(而不是仅包含类型名称的IdentifierTypeNode
)。
(*) 转换是强制性的,失败将触发某种异常(意味着:原始类型 不应 返回)。
😎 通过反射
假设我们有一个 \My\Project\Greeter
类,它有一个 greet
方法,以下是解析该方法的返回类型的方法
<?php // Reflect our class method $reflector = new \ReflectionMethod(\My\Project\Greeter::class, 'greet'); // Use the provided factory to easily parse the PHPDoc, which additionally automatically resolves the types $docBlock = \uuf6429\PHPStanPHPDocTypeResolver\PhpDoc\Factory::createInstance() ->createFromReflector($reflector); // And finally, retrieve the resolved type of the return tag /** @var \PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode $returnTag */ $returnTag = $docBlock->getTag('@return'); $finalReturnType = $returnTag->type;
🙈 无工厂/DocBlock 包装器
假设我们有一个 \My\Project\Greeter
类,它有一个 greet
方法,以下是解析该方法的返回类型的更长的途径
<?php // Reflect our class method $reflector = new \ReflectionMethod(\My\Project\Greeter::class, 'greet'); // Use the scope resolver to get information about that method $scopeResolver = new \uuf6429\PHPStanPHPDocTypeResolver\PhpDoc\ReflectorScopeResolver(); $scope = $scopeResolver->resolve($reflector); // Parse the PHPDoc block with PHPStan PHPDoc parser $lexer = new \PHPStan\PhpDocParser\Lexer\Lexer(); $constExprParser = new \PHPStan\PhpDocParser\Parser\ConstExprParser(); $typeParser = new \PHPStan\PhpDocParser\Parser\TypeParser($constExprParser); $parser = new \PHPStan\PhpDocParser\Parser\PhpDocParser($typeParser, $constExprParser); $docBlock = $parser->parse( new \PHPStan\PhpDocParser\Parser\TokenIterator( $lexer->tokenize($scope->comment) // 👈 note that the scope resolver also retrieves the PHPDoc block for us ) ); // Finally, we initialize the type resolver and resolve the first return type of the doc block $typeResolver = new \uuf6429\PHPStanPHPDocTypeResolver\TypeResolver(); $finalReturnType = $typeResolver->resolve($scope, $docBlock->getReturnTagValues()[0]->type);
🤪 通过源字符串
也可以在不实际加载PHP源代码的情况下解析类型(这是反射的要求)。然而,这将需要更多的工作 - 主要区别在于您需要自行设置作用域。
假设我们想解析PHP源代码字符串中的类型
<?php $source = <<<'PHP' <?php namespace My\Project\Services; use My\Project\PersonEntity as Person; class Greeter { /** * @param Person|object{name: string} $person */ public function greet($person): void { echo "Hello, {$person->name}!"; } } PHP; // Construct the scope manually - automating this will take some work $scope = new \uuf6429\PHPStanPHPDocTypeResolver\PhpDoc\Scope( // In-memory file; you could also use php memory streams etc file: 'data:base64,' . base64_encode($source), // approximate line where the type has occurred - everything else below has to be specified manually line: 73, class: 'My\Project\Services\Greeter', comment: <<<'PHP' /** * @param Person|object{name: string} $person */ PHP ); // The factory can also be used with a custom scope $docBlock = \uuf6429\PHPStanPHPDocTypeResolver\PhpDoc\Factory::createInstance() ->createFromScope($scope); // And as before, retrieve the resolved type of the return tag /** @var \PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode $returnTag */ $returnTag = $docBlock->getTag('@return'); $finalReturnType = $returnTag->type;