Skip to content

Commit b219340

Browse files
committed
Use reflection as the only source for interfaces, extends, and traits
Remove the dual AST+reflection approach for resolving interfaces, extends, and traits. Now FileVisitor uses only PHP reflection on the class FQCN to populate these lists, ensuring a single consistent source of truth. This means the tool requires classes to be autoloaded for full analysis. Key changes: - FileVisitor: replace AST-based implements/extends/trait-use loops with resolveClassViaReflection, resolveEnumViaReflection, and resolveInterfaceViaReflection methods - Remove handleTraitUseNode (traits now discovered via reflection) - ClassDescriptionBuilder: remove addReflectedInterface/addReflectedExtends (no longer needed since reflection is the only source) - Update all tests to use autoloaded fixture classes instead of vfsStream for tests that verify interfaces, extends, and traits resolution - Add fixture classes under tests/Fixtures/ for inheritance, implements, trait, and extends testing scenarios - Add E2E fixture directories to composer.json classmap autoloading https://claude.ai/code/session_01Ko24Jtok9YQagSFair7zQh
1 parent 97c5364 commit b219340

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+414
-603
lines changed

composer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@
5151
"Arkitect\\Tests\\": "tests/"
5252
},
5353
"classmap": [
54-
"tests/E2E/_fixtures/mvc/"
54+
"tests/E2E/_fixtures/mvc/",
55+
"tests/E2E/_fixtures/DependenciesLeak/",
56+
"tests/E2E/_fixtures/line_numbers/"
5557
]
5658
},
5759
"config": {

src/Analyzer/ClassDescriptionBuilder.php

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -80,26 +80,6 @@ public function addInterface(string $FQCN, int $line): self
8080
return $this;
8181
}
8282

83-
/**
84-
* Add an interface discovered via reflection (inherited from parent class or parent interface).
85-
* Adds both the interface and a dependency, with deduplication on the interfaces list.
86-
*/
87-
public function addReflectedInterface(string $FQCN, int $line): self
88-
{
89-
$fqcn = FullyQualifiedClassName::fromString($FQCN);
90-
91-
foreach ($this->interfaces as $existing) {
92-
if ($existing->toString() === $fqcn->toString()) {
93-
return $this;
94-
}
95-
}
96-
97-
$this->addDependency(new ClassDependency($FQCN, $line));
98-
$this->interfaces[] = $fqcn;
99-
100-
return $this;
101-
}
102-
10383
public function addDependency(ClassDependency $cd): self
10484
{
10585
if ($this->isPhpCoreClass($cd)) {
@@ -119,26 +99,6 @@ public function addExtends(string $FQCN, int $line): self
11999
return $this;
120100
}
121101

122-
/**
123-
* Add a parent class discovered via reflection (ancestor beyond the direct parent).
124-
* Adds both the extends entry and a dependency, with deduplication on the extends list.
125-
*/
126-
public function addReflectedExtends(string $FQCN, int $line): self
127-
{
128-
$fqcn = FullyQualifiedClassName::fromString($FQCN);
129-
130-
foreach ($this->extends as $existing) {
131-
if ($existing->toString() === $fqcn->toString()) {
132-
return $this;
133-
}
134-
}
135-
136-
$this->addDependency(new ClassDependency($FQCN, $line));
137-
$this->extends[] = $fqcn;
138-
139-
return $this;
140-
}
141-
142102
public function setFinal(bool $final): self
143103
{
144104
$this->final = $final;

src/Analyzer/FileVisitor.php

Lines changed: 49 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,6 @@ public function enterNode(Node $node): void
4848
// handles trait definition like trait MyTrait {}
4949
$this->handleTraitNode($node);
5050

51-
// handles trait usage like use MyTrait;
52-
$this->handleTraitUseNode($node);
53-
5451
// handles code like $constantValue = StaticClass::constant;
5552
$this->handleStaticClassConstantNode($node);
5653

@@ -143,17 +140,7 @@ private function handleClassNode(Node $node): void
143140
$this->classDescriptionBuilder->setClassName($node->namespacedName->toCodeString());
144141
}
145142

146-
foreach ($node->implements as $interface) {
147-
$this->classDescriptionBuilder
148-
->addInterface($interface->toString(), $interface->getLine());
149-
}
150-
151-
if (null !== $node->extends) {
152-
$this->classDescriptionBuilder
153-
->addExtends($node->extends->toString(), $node->getLine());
154-
}
155-
156-
$this->resolveInheritedInterfacesAndExtends($node);
143+
$this->resolveClassViaReflection($node);
157144

158145
$this->classDescriptionBuilder->setFinal($node->isFinal());
159146

@@ -196,15 +183,7 @@ private function handleEnumNode(Node $node): void
196183
$this->classDescriptionBuilder->setClassName($node->namespacedName->toCodeString());
197184
$this->classDescriptionBuilder->setEnum(true);
198185

199-
foreach ($node->implements as $interface) {
200-
$this->classDescriptionBuilder
201-
->addInterface($interface->toString(), $interface->getLine());
202-
}
203-
204-
// Resolve inherited interfaces from directly implemented interfaces
205-
foreach ($node->implements as $interface) {
206-
$this->addReflectedInterfaceParents($interface->toString(), $interface->getLine());
207-
}
186+
$this->resolveEnumViaReflection($node);
208187
}
209188

210189
private function handleStaticClassConstantNode(Node $node): void
@@ -314,15 +293,7 @@ private function handleInterfaceNode(Node $node): void
314293
$this->classDescriptionBuilder->setClassName($node->namespacedName->toCodeString());
315294
$this->classDescriptionBuilder->setInterface(true);
316295

317-
foreach ($node->extends as $interface) {
318-
$this->classDescriptionBuilder
319-
->addExtends($interface->toString(), $interface->getLine());
320-
}
321-
322-
// Resolve ancestor interfaces from directly extended interfaces
323-
foreach ($node->extends as $interface) {
324-
$this->addReflectedExtendedInterfaceParents($interface->toString(), $interface->getLine());
325-
}
296+
$this->resolveInterfaceViaReflection($node);
326297
}
327298

328299
private function handleTraitNode(Node $node): void
@@ -339,18 +310,6 @@ private function handleTraitNode(Node $node): void
339310
$this->classDescriptionBuilder->setTrait(true);
340311
}
341312

342-
private function handleTraitUseNode(Node $node): void
343-
{
344-
if (!$node instanceof Node\Stmt\TraitUse) {
345-
return;
346-
}
347-
348-
foreach ($node->traits as $trait) {
349-
$this->classDescriptionBuilder
350-
->addTrait($trait->toString(), $trait->getLine());
351-
}
352-
}
353-
354313
private function handleReturnTypeDependency(Node $node): void
355314
{
356315
if (!$node instanceof Node\Stmt\ClassMethod) {
@@ -425,38 +384,33 @@ private function handlePropertyHookNode(Node $node): void
425384
}
426385

427386
/**
428-
* Use reflection to resolve interfaces and parent classes from the full inheritance chain.
429-
* This enriches the ClassDescription with interfaces inherited from parent classes
430-
* and parent interfaces, so that rules like Implement and Extend work across
431-
* the entire hierarchy.
387+
* Use reflection to resolve all interfaces, parent classes and traits for a class.
432388
*/
433-
private function resolveInheritedInterfacesAndExtends(Node\Stmt\Class_ $node): void
389+
private function resolveClassViaReflection(Node\Stmt\Class_ $node): void
434390
{
435-
// Resolve inherited interfaces from directly implemented interfaces
436-
foreach ($node->implements as $interface) {
437-
$this->addReflectedInterfaceParents($interface->toString(), $interface->getLine());
438-
}
439-
440-
// Resolve inherited interfaces and ancestor classes from parent class
441-
if (null === $node->extends) {
391+
if (null === $node->namespacedName) {
442392
return;
443393
}
444394

445-
$parentClassName = $node->extends->toString();
446-
$line = $node->extends->getLine();
395+
$className = $node->namespacedName->toCodeString();
396+
$line = $node->getLine();
447397

448398
try {
449-
/** @var class-string $parentClassName */
450-
$reflection = new \ReflectionClass($parentClassName);
399+
/** @var class-string $className */
400+
$reflection = new \ReflectionClass($className);
451401

452402
foreach ($reflection->getInterfaceNames() as $interfaceName) {
453-
$this->classDescriptionBuilder->addReflectedInterface($interfaceName, $line);
403+
$this->classDescriptionBuilder->addInterface($interfaceName, $line);
404+
}
405+
406+
$parent = $reflection->getParentClass();
407+
while (false !== $parent) {
408+
$this->classDescriptionBuilder->addExtends($parent->getName(), $line);
409+
$parent = $parent->getParentClass();
454410
}
455411

456-
$ancestor = $reflection->getParentClass();
457-
while (false !== $ancestor) {
458-
$this->classDescriptionBuilder->addReflectedExtends($ancestor->getName(), $line);
459-
$ancestor = $ancestor->getParentClass();
412+
foreach ($reflection->getTraitNames() as $traitName) {
413+
$this->classDescriptionBuilder->addTrait($traitName, $line);
460414
}
461415
} catch (\ReflectionException $e) {
462416
$this->parsingErrors[] = ParsingError::create(
@@ -467,17 +421,27 @@ private function resolveInheritedInterfacesAndExtends(Node\Stmt\Class_ $node): v
467421
}
468422

469423
/**
470-
* Use reflection to discover parent interfaces of a given interface,
471-
* adding them to the interfaces list (for classes and enums).
424+
* Use reflection to resolve all interfaces and traits for an enum.
472425
*/
473-
private function addReflectedInterfaceParents(string $interfaceName, int $line): void
426+
private function resolveEnumViaReflection(Node\Stmt\Enum_ $node): void
474427
{
428+
if (null === $node->namespacedName) {
429+
return;
430+
}
431+
432+
$className = $node->namespacedName->toCodeString();
433+
$line = $node->getLine();
434+
475435
try {
476-
/** @var class-string $interfaceName */
477-
$reflection = new \ReflectionClass($interfaceName);
436+
/** @var class-string $className */
437+
$reflection = new \ReflectionClass($className);
438+
439+
foreach ($reflection->getInterfaceNames() as $interfaceName) {
440+
$this->classDescriptionBuilder->addInterface($interfaceName, $line);
441+
}
478442

479-
foreach ($reflection->getInterfaceNames() as $parentInterfaceName) {
480-
$this->classDescriptionBuilder->addReflectedInterface($parentInterfaceName, $line);
443+
foreach ($reflection->getTraitNames() as $traitName) {
444+
$this->classDescriptionBuilder->addTrait($traitName, $line);
481445
}
482446
} catch (\ReflectionException $e) {
483447
$this->parsingErrors[] = ParsingError::create(
@@ -488,17 +452,23 @@ private function addReflectedInterfaceParents(string $interfaceName, int $line):
488452
}
489453

490454
/**
491-
* Use reflection to discover parent interfaces of a given interface,
492-
* adding them to the extends list (for interface definitions).
455+
* Use reflection to resolve all parent interfaces for an interface definition.
493456
*/
494-
private function addReflectedExtendedInterfaceParents(string $interfaceName, int $line): void
457+
private function resolveInterfaceViaReflection(Node\Stmt\Interface_ $node): void
495458
{
459+
if (null === $node->namespacedName) {
460+
return;
461+
}
462+
463+
$className = $node->namespacedName->toCodeString();
464+
$line = $node->getLine();
465+
496466
try {
497-
/** @var class-string $interfaceName */
498-
$reflection = new \ReflectionClass($interfaceName);
467+
/** @var class-string $className */
468+
$reflection = new \ReflectionClass($className);
499469

500-
foreach ($reflection->getInterfaceNames() as $parentInterfaceName) {
501-
$this->classDescriptionBuilder->addReflectedExtends($parentInterfaceName, $line);
470+
foreach ($reflection->getInterfaceNames() as $interfaceName) {
471+
$this->classDescriptionBuilder->addExtends($interfaceName, $line);
502472
}
503473
} catch (\ReflectionException $e) {
504474
$this->parsingErrors[] = ParsingError::create(
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Tests\Fixtures\ExtendsThrowable;
6+
7+
class AThrowable extends \Exception
8+
{
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Tests\Fixtures\ExtendsThrowable;
6+
7+
class AnException extends \Exception
8+
{
9+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Tests\Fixtures\ExtendsThrowable;
6+
7+
interface AnInterface
8+
{
9+
public function amethod();
10+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Tests\Fixtures\ExtendsThrowable;
6+
7+
enum BillingEnum
8+
{
9+
case PENDING;
10+
case PAID;
11+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Tests\Fixtures\ExtendsThrowable;
6+
7+
trait OneTrait
8+
{
9+
public function one()
10+
{
11+
}
12+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Tests\Fixtures\HaveTrait;
6+
7+
trait DatabaseTransactions
8+
{
9+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Arkitect\Tests\Fixtures\HaveTrait\Feature;
6+
7+
use Arkitect\Tests\Fixtures\HaveTrait\DatabaseTransactions;
8+
9+
class OrderFeatureSpec
10+
{
11+
use DatabaseTransactions;
12+
}

0 commit comments

Comments
 (0)