Skip to content

Commit

Permalink
feat: Added method to resolved php documentator type
Browse files Browse the repository at this point in the history
  • Loading branch information
patrykbaszak committed Aug 10, 2024
1 parent d7a0f72 commit bcb81c3
Show file tree
Hide file tree
Showing 2 changed files with 220 additions and 62 deletions.
257 changes: 195 additions & 62 deletions src/Reflection/Domain/Factories/TypeReflectionFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,24 @@
use phpDocumentor\Reflection\DocBlock\Tags\Return_;
use phpDocumentor\Reflection\DocBlock\Tags\Var_;
use phpDocumentor\Reflection\DocBlockFactory;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\PseudoType;
use phpDocumentor\Reflection\PseudoTypes\False_;
use phpDocumentor\Reflection\PseudoTypes\True_;
use phpDocumentor\Reflection\Type as PhpDocumentorReflectionType;
use phpDocumentor\Reflection\Types\AbstractList;
use phpDocumentor\Reflection\Types\Array_;
use phpDocumentor\Reflection\Types\Collection;
use phpDocumentor\Reflection\Types\Compound;
use phpDocumentor\Reflection\Types\ContextFactory;
use phpDocumentor\Reflection\Types\Intersection;
use phpDocumentor\Reflection\Types\Iterable_;
use phpDocumentor\Reflection\Types\Nullable;
use phpDocumentor\Reflection\Types\Object_;
use phpDocumentor\Reflection\Types\Parent_;
use phpDocumentor\Reflection\Types\Self_;
use phpDocumentor\Reflection\Types\Static_;
use phpDocumentor\Reflection\Types\This;

class TypeReflectionFactory
{
Expand Down Expand Up @@ -53,93 +70,206 @@ public function create(?\ReflectionType $ref, ?PhpDocumentorReflectionType $docC

private function createTypeReflectionBasedOnReflectionType(?\ReflectionType $ref): TypeReflection
{
if ($ref instanceof \ReflectionNamedType) {
$name = $ref->getName();
$flags = 0;
$isCollection = in_array($name, ['array', 'iterable']);
// if there is no reflection type
if (null === $ref) {
return NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN);
}

// if the reflection type is a union
if ($ref instanceof \ReflectionUnionType) {
$types = array_map(
fn (\ReflectionType $type) => $this->createTypeReflectionBasedOnReflectionType($type),
$ref->getTypes()
);

return UnionTypeReflection::create($types);
}

// if the reflection type is an intersection
if ($ref instanceof \ReflectionIntersectionType) {
$types = array_map(
fn (\ReflectionType $type) => $this->createTypeReflectionBasedOnReflectionType($type),
$ref->getTypes()
);

return IntersectionTypeReflection::create($types);
}

if ($ref->isBuiltin()) {
$flags |= NamedTypeReflection::IS_BUILT_IN;
// if the reflection is a collection or named
if (!$ref instanceof \ReflectionNamedType) {
throw new \LogicException('The package require an update. On this stage the ReflectionType has to be an ReflectionNamedType.');
}

/** @var \ReflectionNamedType $ref */
$name = $ref->getName();
$flags = 0;
$isCollection = in_array($name, ['array', 'iterable']);

if ($ref->isBuiltin()) {
$flags |= NamedTypeReflection::IS_BUILT_IN;
}

if (class_exists($name, false) && !enum_exists($name, false)) {
$flags |= NamedTypeReflection::IS_CLASS;
$class = (new \ReflectionClass($name));

if ($class->isAbstract()) {
$flags |= NamedTypeReflection::IS_ABSTRACT;
}

if (class_exists($name, false) && !enum_exists($name, false)) {
$flags |= NamedTypeReflection::IS_CLASS;
$class = (new \ReflectionClass($name));
if ($class->implementsInterface(\Traversable::class) || $class->implementsInterface(\ArrayAccess::class)) {
$isCollection = true;
}
}

if ($class->isAbstract()) {
$flags |= NamedTypeReflection::IS_ABSTRACT;
}
if (interface_exists($name, false)) {
$flags |= NamedTypeReflection::IS_INTERFACE;
$interface = (new \ReflectionClass($name));

if ($class->implementsInterface(\Traversable::class) || $class->implementsInterface(\ArrayAccess::class)) {
$isCollection = true;
}
if ($interface->implementsInterface(\Traversable::class) || $interface->implementsInterface(\ArrayAccess::class)) {
$isCollection = true;
}
}

if (interface_exists($name, false)) {
$flags |= NamedTypeReflection::IS_INTERFACE;
$interface = (new \ReflectionClass($name));
if (enum_exists($name, false)) {
$flags |= NamedTypeReflection::IS_ENUM;
}

if ($interface->implementsInterface(\Traversable::class) || $interface->implementsInterface(\ArrayAccess::class)) {
$isCollection = true;
}
$output = $isCollection
? CollectionTypeReflection::create(
NamedTypeReflection::create($name, $flags),
\SplObjectStorage::class === $name || (@$class && $class->isSubclassOf(\SplObjectStorage::class))
? NamedTypeReflection::create('object', NamedTypeReflection::IS_BUILT_IN)
: UnionTypeReflection::create([
NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN),
NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN),
]),
NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN),
)
: NamedTypeReflection::create($name, $flags);

// if the reflection type allows null
if ($ref->allowsNull()) {
if (!in_array($name, ['null', 'mixed'])) {
return UnionTypeReflection::create([
NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN),
$output,
]);
}

if (enum_exists($name, false)) {
$flags |= NamedTypeReflection::IS_ENUM;
if ('null' === $name) {
return NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN);
}

$output = $isCollection
? CollectionTypeReflection::create(
NamedTypeReflection::create($name, $flags),
\SplObjectStorage::class === $name || (@$class && $class->isSubclassOf(\SplObjectStorage::class))
? NamedTypeReflection::create('object', NamedTypeReflection::IS_BUILT_IN)
: UnionTypeReflection::create([
NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN),
NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN),
]),
NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN),
)
: NamedTypeReflection::create($name, $flags);

if ($ref->allowsNull()) {
if (!in_array($name, ['null', 'mixed'])) {
return UnionTypeReflection::create([
NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN),
$output,
]);
}

if ('null' === $name) {
return NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN);
}

if ('mixed' === $name) {
return NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN);
}
if ('mixed' === $name) {
return NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN);
}
}

return $output;
}

return $output;
private function createTypeReflectionBasedOnPhpDocumentatorReflectionType(?PhpDocumentorReflectionType $ref, \ReflectionClass $declarationClass): TypeReflection
{
// if there is no reflection type
if (null === $ref) {
return NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN);
}

if ($ref instanceof \ReflectionUnionType) {
// if the reflection type is a pseudo-type
if ($ref instanceof PseudoType && !$ref instanceof True_ && !$ref instanceof False_) {
$ref = $ref->underlyingType();
}

// if the reflection type is a union
if ($ref instanceof Compound) {
$types = array_map(
fn (\ReflectionType $type) => $this->createTypeReflectionBasedOnReflectionType($type),
$ref->getTypes()
fn (PhpDocumentorReflectionType $type) => $this->createTypeReflectionBasedOnPhpDocumentatorReflectionType($type, $declarationClass),
$ref->getIterator()->getArrayCopy()
);

return UnionTypeReflection::create($types);
}

if ($ref instanceof \ReflectionIntersectionType) {
// if the reflection type is an intersection
if ($ref instanceof Intersection) {
$types = array_map(
fn (\ReflectionType $type) => $this->createTypeReflectionBasedOnReflectionType($type),
$ref->getTypes()
fn (PhpDocumentorReflectionType $type) => $this->createTypeReflectionBasedOnPhpDocumentatorReflectionType($type, $declarationClass),
$ref->getIterator()->getArrayCopy()
);

return IntersectionTypeReflection::create($types);
}

return NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN);
// if the reflection is a collection
if ($ref instanceof AbstractList || in_array(get_class($ref), [Array_::class, Iterable_::class])) {
return CollectionTypeReflection::create(
match (get_class($ref)) {
Array_::class => NamedTypeReflection::create('array', NamedTypeReflection::IS_BUILT_IN),
Iterable_::class => NamedTypeReflection::create('iterable', NamedTypeReflection::IS_BUILT_IN),
Collection::class => NamedTypeReflection::create($ref->getFqsen()?->__toString() ?? 'object', NamedTypeReflection::IS_CLASS),
},
$this->createTypeReflectionBasedOnPhpDocumentatorReflectionType($ref->getKeyType(), $declarationClass),
$this->createTypeReflectionBasedOnPhpDocumentatorReflectionType($ref->getValueType(), $declarationClass)
);
}

// it the reflection is object pseudo-type
$ref = match (get_class($ref)) {
Self_::class, Static_::class, This::class => new Object_(new Fqsen('\\'.ltrim($declarationClass->getName(), '\\'))),
Parent_::class => $declarationClass->getParentClass()
? new Object_(new Fqsen('\\'.ltrim($declarationClass->getParentClass()->getName(), '\\')))
: throw new \LogicException('The reflection is Parent but the class does not have a parent.'),
default => $ref,
};

$flags = 0;
// if the reflection is class, interface or enum
if ($ref instanceof Object_ && null !== $ref->getFqsen()) {
$class = (new \ReflectionClass($ref->getFqsen()->__toString()));
$isClass = true;
$isCollection = $class->implementsInterface(\Traversable::class) || $class->implementsInterface(\ArrayAccess::class);
if ($class->isAbstract()) {
$flags |= NamedTypeReflection::IS_ABSTRACT;
}
if ($class->isInterface()) {
$flags |= NamedTypeReflection::IS_INTERFACE;
$isClass = false;
}
if ($class->isEnum()) {
$flags |= NamedTypeReflection::IS_ENUM;
$isClass = false;
}
if ($isClass) {
$flags |= NamedTypeReflection::IS_CLASS;
}

return $isCollection
? CollectionTypeReflection::create(
NamedTypeReflection::create($class->getName(), $flags),
\SplObjectStorage::class === $class->getName() || $class->isSubclassOf(\SplObjectStorage::class)
? NamedTypeReflection::create('object', NamedTypeReflection::IS_BUILT_IN)
: UnionTypeReflection::create([
NamedTypeReflection::create('int', NamedTypeReflection::IS_BUILT_IN),
NamedTypeReflection::create('string', NamedTypeReflection::IS_BUILT_IN),
]),
NamedTypeReflection::create('mixed', NamedTypeReflection::IS_BUILT_IN),
)
: NamedTypeReflection::create($class->getName(), $flags);
}

// if the reflection allows null
if ($ref instanceof Nullable) {
return UnionTypeReflection::create([
NamedTypeReflection::create('null', NamedTypeReflection::IS_BUILT_IN),
$this->createTypeReflectionBasedOnPhpDocumentatorReflectionType($ref->getActualType(), $declarationClass),
]);
}

// if the reflection is built-in
$flags |= NamedTypeReflection::IS_BUILT_IN;

return NamedTypeReflection::create($ref->__toString(), $flags);
}

private function getDocBlockReflectionTypeFromVarTag(\ReflectionProperty $ref): ?PhpDocumentorReflectionType
Expand All @@ -150,7 +280,8 @@ private function getDocBlockReflectionTypeFromVarTag(\ReflectionProperty $ref):
}

$factory = DocBlockFactory::createInstance();
$docBlock = $factory->create($docBlock);
$context = (new ContextFactory())->createFromReflector($ref);
$docBlock = $factory->create($docBlock, $context);

/** @var Var_[] $tags */
$tags = $docBlock->getTagsByName('var');
Expand All @@ -170,7 +301,8 @@ private function getDocBlockReflectionTypeFromReturnTag(\ReflectionMethod $ref):
}

$factory = DocBlockFactory::createInstance();
$docBlock = $factory->create($docBlock);
$context = (new ContextFactory())->createFromReflector($ref);
$docBlock = $factory->create($docBlock, $context);

/** @var Return_[] $tags */
$tags = $docBlock->getTagsByName('return');
Expand All @@ -190,7 +322,8 @@ private function getDocBlockReflectionTypeFromParamTag(\ReflectionParameter $ref
}

$factory = DocBlockFactory::createInstance();
$docBlock = $factory->create($docBlock);
$context = (new ContextFactory())->createFromReflector($ref);
$docBlock = $factory->create($docBlock, $context);

/** @var Param[] $tags */
$tags = $docBlock->getTagsByName('param');
Expand Down
25 changes: 25 additions & 0 deletions tools/phpstan/fpm-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -295,11 +295,36 @@ parameters:
count: 1
path: ../../src/Reflection/Domain/Entities/Type/NamedTypeReflection.php

-
message: "#^Call to an undefined method phpDocumentor\\\\Reflection\\\\Type\\:\\:getKeyType\\(\\)\\.$#"
count: 1
path: ../../src/Reflection/Domain/Factories/TypeReflectionFactory.php

-
message: "#^Call to an undefined method phpDocumentor\\\\Reflection\\\\Type\\:\\:getValueType\\(\\)\\.$#"
count: 1
path: ../../src/Reflection/Domain/Factories/TypeReflectionFactory.php

-
message: "#^Left side of && is always true\\.$#"
count: 1
path: ../../src/Reflection/Domain/Factories/TypeReflectionFactory.php

-
message: "#^Match expression does not handle remaining value\\: class\\-string\\<phpDocumentor\\\\Reflection\\\\Type\\>$#"
count: 1
path: ../../src/Reflection/Domain/Factories/TypeReflectionFactory.php

-
message: "#^Method PBaszak\\\\UltraMapper\\\\Reflection\\\\Domain\\\\Factories\\\\TypeReflectionFactory\\:\\:createTypeReflectionBasedOnPhpDocumentatorReflectionType\\(\\) is unused\\.$#"
count: 1
path: ../../src/Reflection/Domain/Factories/TypeReflectionFactory.php

-
message: "#^Parameter \\#1 \\$objectOrClass of class ReflectionClass constructor expects class\\-string\\<T of object\\>\\|T of object, string given\\.$#"
count: 1
path: ../../src/Reflection/Domain/Factories/TypeReflectionFactory.php

-
message: "#^Variable \\$class might not be defined\\.$#"
count: 1
Expand Down

0 comments on commit bcb81c3

Please sign in to comment.