Skip to content

Commit

Permalink
Fixed and optimized ClassReflection and ReflectorCompatibilityTest, b…
Browse files Browse the repository at this point in the history
…etter Exception handling
  • Loading branch information
vudaltsov committed Feb 8, 2024
1 parent 0dade2d commit 07e02dd
Show file tree
Hide file tree
Showing 19 changed files with 298 additions and 178 deletions.
3 changes: 2 additions & 1 deletion src/AttributeReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Typhoon\Reflection;

use Typhoon\Reflection\Exception\DefaultReflectionException;
use Typhoon\Reflection\Metadata\AttributeMetadata;

/**
Expand Down Expand Up @@ -63,6 +64,6 @@ public function newInstance(): object
private function native(): \ReflectionAttribute
{
/** @var \ReflectionAttribute<TAttribute> */
return ($this->nativeAttributes)()[$this->metadata->position] ?? throw new ReflectionException();
return ($this->nativeAttributes)()[$this->metadata->position] ?? throw new DefaultReflectionException();
}
}
133 changes: 71 additions & 62 deletions src/ClassReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

use Typhoon\Reflection\Attributes\AttributeReflections;
use Typhoon\Reflection\ClassReflection\ClassReflector;
use Typhoon\Reflection\Exception\ClassDoesNotExistException;
use Typhoon\Reflection\Exception\DefaultReflectionException;
use Typhoon\Reflection\Inheritance\MethodsInheritanceResolver;
use Typhoon\Reflection\Inheritance\PropertiesInheritanceResolver;
use Typhoon\Reflection\Metadata\ClassMetadata;
Expand Down Expand Up @@ -154,33 +156,12 @@ public function getInterfaceNames(): array
*/
public function getInterfaces(): array
{
$interfaces = [];
$ancestors = [];

foreach ($this->metadata->ownInterfaceTypes as $ownInterfaceType) {
$ownInterface = $this->reflectClass($ownInterfaceType->class);
$interfaces[$ownInterface->name] = $ownInterface;
$ancestors[] = $ownInterface;
}

$parent = $this->getParentClass();

if ($parent !== false) {
$ancestors[] = $parent;
}

foreach ($ancestors as $ancestor) {
foreach ($ancestor->getInterfaces() as $interface) {
$interfaces[$interface->name] = $interface;
}
}

return $interfaces;
return iterator_to_array($this->yieldInterfaces());
}

public function getMethod(string $name): MethodReflection
{
return $this->getResolvedMethods()[$name] ?? throw new ReflectionException();
return $this->getResolvedMethods()[$name] ?? throw new DefaultReflectionException();
}

/**
Expand Down Expand Up @@ -228,34 +209,6 @@ public function getParentClass(): self|false
return $this->reflectClass($this->metadata->parentType->class);
}

/**
* @return list<self>
*/
public function getParentClasses(): array
{
$parent = $this->getParentClass();

if ($parent === false) {
return [];
}

return [$parent, ...$parent->getParentClasses()];
}

/**
* @return list<class-string>
*/
public function getParentClassNames(): array
{
$parent = $this->getParentClass();

if ($parent === false) {
return [];
}

return [$parent->name, ...$parent->getParentClassNames()];
}

/**
* @return list<PropertyReflection>
*/
Expand All @@ -273,7 +226,7 @@ public function getProperties(?int $filter = null): array

public function getProperty(string $name): PropertyReflection
{
return $this->getResolvedProperties()[$name] ?? throw new ReflectionException();
return $this->getResolvedProperties()[$name] ?? throw new DefaultReflectionException();
}

public function getReflectionConstant(string $name): \ReflectionClassConstant|false
Expand Down Expand Up @@ -324,7 +277,7 @@ public function getStaticPropertyValue(string $name, mixed $default = null): mix
public function getTemplate(int|string $nameOrPosition): TemplateReflection
{
if (\is_int($nameOrPosition)) {
return $this->metadata->templates[$nameOrPosition] ?? throw new ReflectionException();
return $this->metadata->templates[$nameOrPosition] ?? throw new DefaultReflectionException();
}

foreach ($this->metadata->templates as $template) {
Expand All @@ -333,7 +286,7 @@ public function getTemplate(int|string $nameOrPosition): TemplateReflection
}
}

throw new ReflectionException();
throw new DefaultReflectionException();
}

/**
Expand Down Expand Up @@ -367,7 +320,7 @@ public function getTraits(): array

public function getTypeAlias(string $name): Type
{
return $this->metadata->typeAliases[$name] ?? throw new ReflectionException();
return $this->metadata->typeAliases[$name] ?? throw new DefaultReflectionException();
}

/**
Expand Down Expand Up @@ -398,18 +351,29 @@ public function hasProperty(string $name): bool
public function implementsInterface(string|\ReflectionClass $interface): bool
{
if (\is_string($interface)) {
$interface = $this->reflectClass($interface);
try {
$interface = $this->reflectClass($interface);
} catch (ClassDoesNotExistException) {
/** @var string $interface */
throw new ClassDoesNotExistException(sprintf('Interface "%s" does not exist', DefaultReflectionException::normalizeClass($interface)));
}
}

if (!$interface->isInterface()) {
throw new ReflectionException();
throw new DefaultReflectionException(sprintf('%s is not an interface', DefaultReflectionException::normalizeClass($interface->name)));
}

if ($this->metadata->name === $interface->name) {
return true;
}

return \in_array($interface->name, $this->getInterfaceNames(), true);
foreach ($this->yieldInterfaces() as $implementedInterface) {
if ($implementedInterface->name === $interface->name) {
return true;
}
}

return false;
}

public function inNamespace(): bool
Expand Down Expand Up @@ -467,7 +431,7 @@ public function isFinal(): bool

public function isInstance(object $object): bool
{
return $this->reflectClass($object::class)->isSubclassOf($this);
return $this->metadata->name === $object::class || $this->reflectClass($object::class)->isSubclassOf($this);

Check warning on line 434 in src/ClassReflection.php

View workflow job for this annotation

GitHub Actions / infection (8.1)

Escaped Mutant for Mutator "LogicalOrAllSubExprNegation": --- Original +++ New @@ @@ } public function isInstance(object $object) : bool { - return $this->metadata->name === $object::class || $this->reflectClass($object::class)->isSubclassOf($this); + return !($this->metadata->name === $object::class) || !$this->reflectClass($object::class)->isSubclassOf($this); } public function isInstantiable() : bool {
}

public function isInstantiable(): bool
Expand Down Expand Up @@ -509,18 +473,26 @@ public function isReadOnly(): bool
public function isSubclassOf(string|\ReflectionClass $class): bool
{
if (\is_string($class)) {
if ($class === $this->metadata->name) {
return false;
}

$class = $this->reflectClass($class);
} elseif ($class->name === $this->metadata->name) {
return false;
}

if ($class->isInterface() && $this->implementsInterface($class)) {
return true;
}

if ($class->name === $this->metadata->name) {
return true;
$parentClass = $this->metadata->parentType?->class;

if ($parentClass === null) {
return false;
}

return \in_array($class->name, $this->getParentClassNames(), true);
return $class->name === $parentClass || $this->reflectClass($parentClass)->isSubclassOf($class);
}

public function isTrait(): bool
Expand Down Expand Up @@ -611,6 +583,14 @@ private function getResolvedProperties(): array
);
}

/**
* @psalm-assert-if-true self $this->getParentClass()
*/
private function hasParent(): bool
{
return $this->metadata->parentType !== null;
}

private function loadNative(): void
{
if (!$this->nativeLoaded) {
Expand All @@ -623,9 +603,38 @@ private function loadNative(): void
* @template TObject of object
* @param class-string<TObject> $class
* @return ClassReflection<TObject>
* @throws ReflectionException
*/
private function reflectClass(string $class): self
{
return $this->classReflector->reflectClass($class);
}

/**
* @return \Generator<interface-string, self>
*/
private function yieldInterfaces(): \Generator
{
$interfaces = [];
$ancestors = [];

foreach ($this->metadata->ownInterfaceTypes as $ownInterfaceType) {
$ancestors[] = $interface = $this->reflectClass($ownInterfaceType->class);
yield $interface->name => $interface;
}

foreach ($ancestors as $ancestor) {
foreach ($ancestor->getInterfaces() as $interface) {
yield $interface->name => $interface;
}
}

if ($this->hasParent()) {
foreach ($this->getParentClass()->getInterfaces() as $interface) {
yield $interface->name => $interface;
}
}

return $interfaces;
}
}
2 changes: 2 additions & 0 deletions src/ClassReflection/ClassReflector.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Typhoon\Reflection\ClassReflection;

use Typhoon\Reflection\ClassReflection;
use Typhoon\Reflection\ReflectionException;

/**
* @internal
Expand All @@ -16,6 +17,7 @@ interface ClassReflector
* @template T of object
* @param string|class-string<T>|T $nameOrObject
* @return ClassReflection<T>
* @throws ReflectionException
*/
public function reflectClass(string|object $nameOrObject): ClassReflection;
}
12 changes: 12 additions & 0 deletions src/Exception/ClassDoesNotExistException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Typhoon\Reflection\Exception;

use Typhoon\Reflection\ReflectionException;

/**
* @api
*/
final class ClassDoesNotExistException extends ReflectionException {}
14 changes: 14 additions & 0 deletions src/Exception/DefaultReflectionException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace Typhoon\Reflection\Exception;

use Typhoon\Reflection\ReflectionException;

/**
* @internal
* @psalm-internal Typhoon\Reflection
* @TODO Refactor to actual exception classes
*/
final class DefaultReflectionException extends ReflectionException {}
13 changes: 7 additions & 6 deletions src/MethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Typhoon\Reflection\Attributes\AttributeReflections;
use Typhoon\Reflection\ClassReflection\ClassReflector;
use Typhoon\Reflection\Exception\DefaultReflectionException;
use Typhoon\Reflection\Metadata\MethodMetadata;
use Typhoon\Reflection\Metadata\ParameterMetadata;
use Typhoon\Type\Type;
Expand Down Expand Up @@ -43,7 +44,7 @@ public function __construct(
*/
public static function createFromMethodName(string $method): static
{
throw new ReflectionException('Not implemented.');
throw new DefaultReflectionException('Not implemented.');
}

public function __get(string $name)
Expand Down Expand Up @@ -197,7 +198,7 @@ public function getParameter(int|string $nameOrPosition): ParameterReflection
return $parameters[$nameOrPosition];
}

throw new ReflectionException();
throw new DefaultReflectionException();
}

foreach ($parameters as $parameter) {
Expand All @@ -206,7 +207,7 @@ public function getParameter(int|string $nameOrPosition): ParameterReflection
}
}

throw new ReflectionException();
throw new DefaultReflectionException();
}

/**
Expand All @@ -223,7 +224,7 @@ public function getParameters(): array
public function getPrototype(): \ReflectionMethod
{
if ($this->metadata->prototype === null) {
throw new ReflectionException();
throw new DefaultReflectionException();
}

[$class, $name] = $this->metadata->prototype;
Expand Down Expand Up @@ -258,7 +259,7 @@ public function getStaticVariables(): array
public function getTemplate(int|string $nameOrPosition): TemplateReflection
{
if (\is_int($nameOrPosition)) {
return $this->metadata->templates[$nameOrPosition] ?? throw new ReflectionException();
return $this->metadata->templates[$nameOrPosition] ?? throw new DefaultReflectionException();
}

foreach ($this->metadata->templates as $template) {
Expand All @@ -267,7 +268,7 @@ public function getTemplate(int|string $nameOrPosition): TemplateReflection
}
}

throw new ReflectionException();
throw new DefaultReflectionException();
}

/**
Expand Down
8 changes: 4 additions & 4 deletions src/NameContext/NameContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Typhoon\Reflection\NameContext;

use Typhoon\Reflection\ReflectionException;
use Typhoon\Reflection\Exception\DefaultReflectionException;

/**
* Inspired by PhpParser\NameContext.
Expand Down Expand Up @@ -91,7 +91,7 @@ public function addUse(string $name, ?string $alias = null): void
$resolvedAlias = ($alias === null ? $resolvedName->lastSegment() : new UnqualifiedName($alias))->toString();

if (isset($this->namespaceImportTable[$resolvedAlias])) {
throw new ReflectionException(sprintf(
throw new DefaultReflectionException(sprintf(
'Cannot use %s as %s because the name is already in use.',
$name,
$resolvedAlias,
Expand All @@ -107,7 +107,7 @@ public function addConstantUse(string $name, ?string $alias = null): void
$resolvedAlias = ($alias === null ? $resolvedName->lastSegment() : new UnqualifiedName($alias))->toString();

Check warning on line 107 in src/NameContext/NameContext.php

View workflow job for this annotation

GitHub Actions / infection (8.1)

Escaped Mutant for Mutator "Identical": --- Original +++ New @@ @@ public function addConstantUse(string $name, ?string $alias = null) : void { $resolvedName = self::parse($name)->resolve(); - $resolvedAlias = ($alias === null ? $resolvedName->lastSegment() : new UnqualifiedName($alias))->toString(); + $resolvedAlias = ($alias !== null ? $resolvedName->lastSegment() : new UnqualifiedName($alias))->toString(); if (isset($this->constantImportTable[$resolvedAlias])) { throw new DefaultReflectionException(sprintf('Cannot use constant %s as %s because the name is already in use.', $name, $resolvedAlias)); }

Check warning on line 107 in src/NameContext/NameContext.php

View workflow job for this annotation

GitHub Actions / infection (8.1)

Escaped Mutant for Mutator "Ternary": --- Original +++ New @@ @@ public function addConstantUse(string $name, ?string $alias = null) : void { $resolvedName = self::parse($name)->resolve(); - $resolvedAlias = ($alias === null ? $resolvedName->lastSegment() : new UnqualifiedName($alias))->toString(); + $resolvedAlias = ($alias === null ? new UnqualifiedName($alias) : $resolvedName->lastSegment())->toString(); if (isset($this->constantImportTable[$resolvedAlias])) { throw new DefaultReflectionException(sprintf('Cannot use constant %s as %s because the name is already in use.', $name, $resolvedAlias)); }

if (isset($this->constantImportTable[$resolvedAlias])) {
throw new ReflectionException(sprintf(
throw new DefaultReflectionException(sprintf(
'Cannot use constant %s as %s because the name is already in use.',
$name,
$resolvedAlias,
Expand All @@ -123,7 +123,7 @@ public function addFunctionUse(string $name, ?string $alias = null): void
$resolvedAlias = ($alias === null ? $resolvedName->lastSegment() : new UnqualifiedName($alias))->toString();

Check warning on line 123 in src/NameContext/NameContext.php

View workflow job for this annotation

GitHub Actions / infection (8.1)

Escaped Mutant for Mutator "Ternary": --- Original +++ New @@ @@ public function addFunctionUse(string $name, ?string $alias = null) : void { $resolvedName = self::parse($name)->resolve(); - $resolvedAlias = ($alias === null ? $resolvedName->lastSegment() : new UnqualifiedName($alias))->toString(); + $resolvedAlias = ($alias === null ? new UnqualifiedName($alias) : $resolvedName->lastSegment())->toString(); if (isset($this->functionImportTable[$resolvedAlias])) { throw new DefaultReflectionException(sprintf('Cannot use constant %s as %s because the name is already in use.', $name, $resolvedAlias)); }

Check warning on line 123 in src/NameContext/NameContext.php

View workflow job for this annotation

GitHub Actions / infection (8.1)

Escaped Mutant for Mutator "Identical": --- Original +++ New @@ @@ public function addFunctionUse(string $name, ?string $alias = null) : void { $resolvedName = self::parse($name)->resolve(); - $resolvedAlias = ($alias === null ? $resolvedName->lastSegment() : new UnqualifiedName($alias))->toString(); + $resolvedAlias = ($alias !== null ? $resolvedName->lastSegment() : new UnqualifiedName($alias))->toString(); if (isset($this->functionImportTable[$resolvedAlias])) { throw new DefaultReflectionException(sprintf('Cannot use constant %s as %s because the name is already in use.', $name, $resolvedAlias)); }

if (isset($this->functionImportTable[$resolvedAlias])) {
throw new ReflectionException(sprintf(
throw new DefaultReflectionException(sprintf(
'Cannot use constant %s as %s because the name is already in use.',
$name,
$resolvedAlias,
Expand Down
Loading

0 comments on commit 07e02dd

Please sign in to comment.