mirror of
https://github.com/barryvdh/ReflectionDocBlock.git
synced 2026-08-18 10:07:12 +00:00
WIP
This commit is contained in:
@@ -45,6 +45,7 @@ use function array_slice;
|
||||
use function call_user_func_array;
|
||||
use function count;
|
||||
use function get_class;
|
||||
use function is_object;
|
||||
use function preg_match;
|
||||
use function strpos;
|
||||
use function trim;
|
||||
@@ -162,18 +163,23 @@ final class StandardTagFactory implements TagFactory
|
||||
$this->serviceLocator[$alias ?: get_class($service)] = $service;
|
||||
}
|
||||
|
||||
public function registerTagHandler(string $tagName, string $handler): void
|
||||
public function registerTagHandler(string $tagName, $handler): void
|
||||
{
|
||||
Assert::stringNotEmpty($tagName);
|
||||
Assert::classExists($handler);
|
||||
Assert::implementsInterface($handler, Tag::class);
|
||||
|
||||
if (strpos($tagName, '\\') && $tagName[0] !== '\\') {
|
||||
throw new InvalidArgumentException(
|
||||
'A namespaced tag must have a leading backslash as it must be fully qualified'
|
||||
);
|
||||
}
|
||||
|
||||
if (is_object($handler)) {
|
||||
Assert::implementsInterface($handler, TagFactory::class);
|
||||
$this->tagHandlerMappings[$tagName] = $handler;
|
||||
return;
|
||||
}
|
||||
|
||||
Assert::classExists($handler);
|
||||
Assert::implementsInterface($handler, Tag::class);
|
||||
$this->tagHandlerMappings[$tagName] = $handler;
|
||||
}
|
||||
|
||||
@@ -210,6 +216,8 @@ final class StandardTagFactory implements TagFactory
|
||||
$this->getServiceLocatorWithDynamicParameters($context, $name, $body)
|
||||
);
|
||||
|
||||
$arguments['tagLine'] = sprintf('@%s %s', $name, $body);
|
||||
|
||||
try {
|
||||
$callable = [$handlerClassName, 'create'];
|
||||
Assert::isCallable($callable);
|
||||
@@ -225,9 +233,9 @@ final class StandardTagFactory implements TagFactory
|
||||
/**
|
||||
* Determines the Fully Qualified Class Name of the Factory or Tag (containing a Factory Method `create`).
|
||||
*
|
||||
* @return class-string<Tag>
|
||||
* @return class-string<Tag>|TagFactory
|
||||
*/
|
||||
private function findHandlerClassName(string $tagName, TypeContext $context): string
|
||||
private function findHandlerClassName(string $tagName, TypeContext $context)
|
||||
{
|
||||
$handlerClassName = Generic::class;
|
||||
if (isset($this->tagHandlerMappings[$tagName])) {
|
||||
@@ -275,11 +283,11 @@ final class StandardTagFactory implements TagFactory
|
||||
|
||||
$parameterName = $parameter->getName();
|
||||
if (isset($locator[$parameterName])) {
|
||||
$arguments[] = $locator[$parameterName];
|
||||
$arguments[$parameterName] = $locator[$parameterName];
|
||||
continue;
|
||||
}
|
||||
|
||||
$arguments[] = null;
|
||||
$arguments[$parameterName] = null;
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
@@ -289,12 +297,14 @@ final class StandardTagFactory implements TagFactory
|
||||
* Retrieves a series of ReflectionParameter objects for the static 'create' method of the given
|
||||
* tag handler class name.
|
||||
*
|
||||
* @param class-string $handlerClassName
|
||||
* @param class-string|TagFactory $handler
|
||||
*
|
||||
* @return ReflectionParameter[]
|
||||
*/
|
||||
private function fetchParametersForHandlerFactoryMethod(string $handlerClassName): array
|
||||
private function fetchParametersForHandlerFactoryMethod($handler): array
|
||||
{
|
||||
$handlerClassName = is_object($handler) ? get_class($handler) : $handler;
|
||||
|
||||
if (!isset($this->tagHandlerParameterCache[$handlerClassName])) {
|
||||
$methodReflection = new ReflectionMethod($handlerClassName, 'create');
|
||||
$this->tagHandlerParameterCache[$handlerClassName] = $methodReflection->getParameters();
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of phpDocumentor.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* @link http://phpdoc.org
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
|
||||
|
||||
use phpDocumentor\Reflection\DocBlock\Tag;
|
||||
use phpDocumentor\Reflection\DocBlock\TagFactory;
|
||||
use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag;
|
||||
use phpDocumentor\Reflection\Types\Context as TypeContext;
|
||||
use PHPStan\PhpDocParser\Lexer\Lexer;
|
||||
use PHPStan\PhpDocParser\Parser\ConstExprParser;
|
||||
use PHPStan\PhpDocParser\Parser\PhpDocParser;
|
||||
use PHPStan\PhpDocParser\Parser\TokenIterator;
|
||||
use PHPStan\PhpDocParser\Parser\TypeParser;
|
||||
|
||||
/**
|
||||
* Factory class creating tags using phpstan's parser
|
||||
*
|
||||
* This class uses {@see PHPStanFactory} implementations to create tags
|
||||
* from the ast of the phpstan docblock parser.
|
||||
*
|
||||
* @internal This class is not part of the BC promise of this library.
|
||||
*/
|
||||
class AbstractPHPStanFactory implements TagFactory
|
||||
{
|
||||
private PhpDocParser $parser;
|
||||
private Lexer $lexer;
|
||||
private array $factories;
|
||||
|
||||
public function __construct(PHPStanFactory ...$factories)
|
||||
{
|
||||
$this->lexer = new Lexer();
|
||||
$constParser = new ConstExprParser();
|
||||
$this->parser = new PhpDocParser(new TypeParser($constParser), $constParser);
|
||||
$this->factories = $factories;
|
||||
}
|
||||
|
||||
public function addParameter(string $name, $value): void
|
||||
{
|
||||
// TODO: Implement addParameter() method.
|
||||
}
|
||||
|
||||
public function create(string $tagLine, ?TypeContext $context = null): Tag
|
||||
{
|
||||
$tokens = $this->lexer->tokenize($tagLine);
|
||||
$ast = $this->parser->parseTag(new TokenIterator($tokens));
|
||||
|
||||
foreach ($this->factories as $factory) {
|
||||
if ($factory->supports($ast, $context)) {
|
||||
return $factory->create($ast, $context);
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidTag::create(
|
||||
$ast->name,
|
||||
(string) $ast->value
|
||||
);
|
||||
}
|
||||
|
||||
public function addService(object $service): void
|
||||
{
|
||||
// TODO: Implement addService() method.
|
||||
}
|
||||
|
||||
public function registerTagHandler(string $tagName, string $handler): void
|
||||
{
|
||||
// TODO: Implement registerTagHandler() method.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
|
||||
|
||||
use phpDocumentor\Reflection\DocBlock\Tag;
|
||||
use phpDocumentor\Reflection\Types\Context;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
|
||||
|
||||
interface PHPStanFactory
|
||||
{
|
||||
public function create(PhpDocTagNode $node, Context $context): Tag;
|
||||
|
||||
public function supports(PhpDocTagNode $node, ?Context $context): bool;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
|
||||
|
||||
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
|
||||
use phpDocumentor\Reflection\DocBlock\Tag;
|
||||
use phpDocumentor\Reflection\DocBlock\Tags\Param;
|
||||
use phpDocumentor\Reflection\Types\Context;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
use function trim;
|
||||
|
||||
/**
|
||||
* @internal This class is not part of the BC promise of this library.
|
||||
*/
|
||||
final class ParamFactory implements PHPStanFactory
|
||||
{
|
||||
private TypeFactory $typeFactory;
|
||||
private DescriptionFactory $descriptionFactory;
|
||||
|
||||
public function __construct(TypeFactory $typeFactory, DescriptionFactory $descriptionFactory)
|
||||
{
|
||||
$this->typeFactory = $typeFactory;
|
||||
$this->descriptionFactory = $descriptionFactory;
|
||||
}
|
||||
|
||||
public function create(PhpDocTagNode $node, Context $context): Tag
|
||||
{
|
||||
$tagValue = $node->value;
|
||||
Assert::isInstanceOf($tagValue, ParamTagValueNode::class);
|
||||
|
||||
return new Param(
|
||||
trim($tagValue->parameterName, '$'),
|
||||
$this->typeFactory->createType($tagValue->type, $context),
|
||||
$tagValue->isVariadic,
|
||||
$this->descriptionFactory->create($tagValue->description, $context),
|
||||
$tagValue->isReference
|
||||
);
|
||||
}
|
||||
|
||||
public function supports(PhpDocTagNode $node, ?Context $context): bool
|
||||
{
|
||||
return $node->value instanceof ParamTagValueNode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
|
||||
|
||||
use phpDocumentor\Reflection\PseudoTypes\ArrayShape;
|
||||
use phpDocumentor\Reflection\PseudoTypes\ArrayShapeItem;
|
||||
use phpDocumentor\Reflection\PseudoTypes\IntegerRange;
|
||||
use phpDocumentor\Reflection\PseudoTypes\List_;
|
||||
use phpDocumentor\Reflection\Type;
|
||||
use phpDocumentor\Reflection\TypeResolver;
|
||||
use phpDocumentor\Reflection\Types\Array_;
|
||||
use phpDocumentor\Reflection\Types\Callable_;
|
||||
use phpDocumentor\Reflection\Types\ClassString;
|
||||
use phpDocumentor\Reflection\Types\Collection;
|
||||
use phpDocumentor\Reflection\Types\Compound;
|
||||
use phpDocumentor\Reflection\Types\Context;
|
||||
use phpDocumentor\Reflection\Types\InterfaceString;
|
||||
use phpDocumentor\Reflection\Types\Intersection;
|
||||
use phpDocumentor\Reflection\Types\Nullable;
|
||||
use phpDocumentor\Reflection\Types\This;
|
||||
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeForParameterNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\OffsetAccessTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
|
||||
|
||||
use function array_map;
|
||||
use function array_reverse;
|
||||
use function get_class;
|
||||
use function strtolower;
|
||||
|
||||
/**
|
||||
* @internal This class is not part of the BC promise of this library.
|
||||
*/
|
||||
class TypeFactory
|
||||
{
|
||||
private TypeResolver $resolver;
|
||||
|
||||
public function __construct(TypeResolver $resolver)
|
||||
{
|
||||
$this->resolver = $resolver;
|
||||
}
|
||||
|
||||
public function createType(TypeNode $type, Context $context): ?Type
|
||||
{
|
||||
switch (get_class($type)) {
|
||||
case ArrayTypeNode::class:
|
||||
return new Array_(
|
||||
$this->createType($type->type, $context)
|
||||
);
|
||||
|
||||
case ArrayShapeNode::class:
|
||||
return new ArrayShape(
|
||||
...array_map(
|
||||
fn (ArrayShapeItemNode $item) => new ArrayShapeItem(
|
||||
(string) $item->keyName,
|
||||
$this->createType($item->valueType, $context),
|
||||
$item->optional
|
||||
),
|
||||
$type->items
|
||||
)
|
||||
);
|
||||
|
||||
case CallableTypeNode::class:
|
||||
return $this->createFromCallable($type, $context);
|
||||
|
||||
case ConstTypeNode::class:
|
||||
case GenericTypeNode::class:
|
||||
return $this->createFromGeneric($type, $context);
|
||||
|
||||
case IdentifierTypeNode::class:
|
||||
return $this->resolver->resolve($type->name, $context);
|
||||
|
||||
case IntersectionTypeNode::class:
|
||||
return new Intersection(
|
||||
array_map(
|
||||
fn (TypeNode $nestedType) => $this->createType($nestedType, $context),
|
||||
$type->types
|
||||
)
|
||||
);
|
||||
|
||||
case NullableTypeNode::class:
|
||||
return new Nullable(
|
||||
$this->createType($type->type, $context)
|
||||
);
|
||||
|
||||
case UnionTypeNode::class:
|
||||
return new Compound(
|
||||
array_map(
|
||||
fn (TypeNode $nestedType) => $this->createType($nestedType, $context),
|
||||
$type->types
|
||||
)
|
||||
);
|
||||
|
||||
case ThisTypeNode::class:
|
||||
return new This();
|
||||
|
||||
case ConditionalTypeNode::class:
|
||||
case ConditionalTypeForParameterNode::class:
|
||||
case OffsetAccessTypeNode::class:
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function createFromGeneric(GenericTypeNode $type, Context $context): Type
|
||||
{
|
||||
switch (strtolower($type->type->name)) {
|
||||
case 'array':
|
||||
return new Array_(
|
||||
...array_reverse(
|
||||
array_map(
|
||||
fn (TypeNode $genericType) => $this->createType($genericType, $context),
|
||||
$type->genericTypes
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
case 'class-string':
|
||||
return new ClassString(
|
||||
$this->createType($type->genericTypes[0], $context)->getFqsen()
|
||||
);
|
||||
|
||||
case 'interface-string':
|
||||
return new InterfaceString(
|
||||
$this->createType($type->genericTypes[0], $context)->getFqsen()
|
||||
);
|
||||
|
||||
case 'list':
|
||||
return new List_(
|
||||
$this->createType($type->genericTypes[0], $context)
|
||||
);
|
||||
|
||||
case 'int':
|
||||
return new IntegerRange(
|
||||
(string) $type->genericTypes[0],
|
||||
(string) ($type->genericTypes[1] ?? ''),
|
||||
);
|
||||
|
||||
default:
|
||||
return new Collection(
|
||||
$this->createType($type->type, $context)->getFqsen(),
|
||||
...array_reverse(
|
||||
array_map(
|
||||
fn (TypeNode $genericType) => $this->createType($genericType, $context),
|
||||
$type->genericTypes
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function createFromCallable(CallableTypeNode $type, Context $context): Callable_
|
||||
{
|
||||
return new Callable_();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user