Skip to content
This repository was archived by the owner on Sep 15, 2025. It is now read-only.

Commit 8a58f17

Browse files
committed
feat: Implement trait aggregation in relationships and add TraitAggregationTest for validation
1 parent 13e4dc6 commit 8a58f17

File tree

6 files changed

+84
-17
lines changed

6 files changed

+84
-17
lines changed

src/ClassDiagramRenderer/ClassDiagramBuilder.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
use Exception;
77
use Tasuku43\MermaidClassDiagram\ClassDiagramRenderer\Node\NodeParser;
8+
use Tasuku43\MermaidClassDiagram\ClassDiagramRenderer\Node\Trait_ as DiagramTrait;
89

910
class ClassDiagramBuilder
1011
{

src/ClassDiagramRenderer/Node/Node.php

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -57,20 +57,7 @@ public function useTrait(Node $trait): void
5757
$this->traits->add($trait);
5858
}
5959

60-
/**
61-
* Return trait-derived compositions and dependencies for this node.
62-
* @return array{array<string, Node>, array<string, Node>} [compositions, dependencies]
63-
*/
64-
public function traitAggregates(): array
65-
{
66-
$traitCompositions = [];
67-
$traitDependencies = [];
68-
$visitedTraits = [];
69-
foreach ($this->traits->getAllNodes() as $traitNode) {
70-
$this->collectTraitRelations($traitNode, $visitedTraits, $traitCompositions, $traitDependencies);
71-
}
72-
return [$traitCompositions, $traitDependencies];
73-
}
60+
// Intentionally left without public API; trait aggregation is done in relationships()
7461

7562
public function nodeName(): string
7663
{

src/ClassDiagramRenderer/Node/NodeParser.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44
namespace Tasuku43\MermaidClassDiagram\ClassDiagramRenderer\Node;
55

66
use Exception;
7-
use PhpParser\Node;
87
use PhpParser\Node\Stmt;
9-
use PhpParser\Node\Stmt\ClassLike;
108
use PhpParser\NodeFinder;
119
use PhpParser\NodeTraverser;
1210
use PhpParser\NodeVisitor\NameResolver;
@@ -17,6 +15,7 @@
1715
use Tasuku43\MermaidClassDiagram\ClassDiagramRenderer\Node\Connector\DependencyConnector;
1816
use Tasuku43\MermaidClassDiagram\ClassDiagramRenderer\Node\Connector\InheritanceConnector;
1917
use Tasuku43\MermaidClassDiagram\ClassDiagramRenderer\Node\Connector\RealizationConnector;
18+
use Tasuku43\MermaidClassDiagram\ClassDiagramRenderer\Node\Connector\TraitUsageConnector;
2019
use Tasuku43\MermaidClassDiagram\ClassDiagramRenderer\Node\Exception\CannnotParseToClassLikeException;
2120
use Tasuku43\MermaidClassDiagram\ClassDiagramRenderer\Node\Node as ClassDiagramNode;
2221

@@ -37,6 +36,7 @@ public function __construct(
3736
fn(Stmt\Interface_|Stmt\Class_|Stmt\Enum_|Stmt\Trait_ $classLike, ClassDiagramNode $classDiagramNode) => RealizationConnector::parse($classLike, $classDiagramNode),
3837
fn(Stmt\Interface_|Stmt\Class_|Stmt\Enum_|Stmt\Trait_ $classLike, ClassDiagramNode $classDiagramNode) => CompositionConnector::parse($nodeFinder, $classLike, $classDiagramNode),
3938
fn(Stmt\Interface_|Stmt\Class_|Stmt\Enum_|Stmt\Trait_ $classLike, ClassDiagramNode $classDiagramNode) => DependencyConnector::parse($nodeFinder, $classLike, $classDiagramNode),
39+
fn(Stmt\Interface_|Stmt\Class_|Stmt\Enum_|Stmt\Trait_ $classLike, ClassDiagramNode $classDiagramNode) => TraitUsageConnector::parse($nodeFinder, $classLike, $classDiagramNode),
4040
];
4141
}
4242

@@ -111,7 +111,8 @@ private function parseClassLikes(string $code): array
111111
$validClassLikes = array_filter($allClassLikes, function ($node) {
112112
return $node instanceof Stmt\Class_
113113
|| $node instanceof Stmt\Interface_
114-
|| $node instanceof Stmt\Enum_;
114+
|| $node instanceof Stmt\Enum_
115+
|| $node instanceof Stmt\Trait_;
115116
});
116117

117118
if (empty($validClassLikes)) {
@@ -132,6 +133,7 @@ private function createClassDiagramNodeFromClassLike($classLike): ClassDiagramNo
132133
: new Class_((string)$classLike->name->name),
133134
$classLike instanceof Stmt\Interface_ => new Interface_((string)$classLike->name->name),
134135
$classLike instanceof Stmt\Enum_ => new Enum_((string)$classLike->name->name),
136+
$classLike instanceof Stmt\Trait_ => new Trait_((string)$classLike->name->name),
135137
default => throw new Exception('Unexpected match value: ' . get_class($classLike))
136138
};
137139
}

tests/ClassDiagramRenderer/ClassDiagramBuilderTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ classDiagram
4040
class AbstractController {
4141
<<abstract>>
4242
}
43+
class AuditLogger {
44+
}
45+
class RepositoryAwareTrait {
46+
<<trait>>
47+
}
4348
class User {
4449
}
4550
class UserController {
@@ -55,12 +60,16 @@ class UserStatus {
5560
<<enum>>
5661
}
5762
63+
RepositoryAwareTrait *-- AuditLogger: composition
64+
RepositoryAwareTrait ..> User: dependency
65+
RepositoryAwareTrait *-- UserRepositoryInterface: composition
5866
User *-- UserStatus: composition
5967
AbstractController <|-- UserController: inheritance
6068
UserController *-- UserService: composition
6169
UserRepository ..> User: dependency
6270
UserRepositoryInterface <|.. UserRepository: realization
6371
UserRepositoryInterface ..> User: dependency
72+
UserService *-- AuditLogger: composition
6473
UserService ..> InvalidArgumentException: dependency
6574
UserService ..> User: dependency
6675
UserService *-- UserRepositoryInterface: composition
@@ -104,6 +113,11 @@ classDiagram
104113
class AbstractController {
105114
<<abstract>>
106115
}
116+
class AuditLogger {
117+
}
118+
class RepositoryAwareTrait {
119+
<<trait>>
120+
}
107121
class User {
108122
}
109123
class UserController {
@@ -119,10 +133,13 @@ class UserStatus {
119133
<<enum>>
120134
}
121135
136+
RepositoryAwareTrait *-- AuditLogger: composition
137+
RepositoryAwareTrait *-- UserRepositoryInterface: composition
122138
User *-- UserStatus: composition
123139
AbstractController <|-- UserController: inheritance
124140
UserController *-- UserService: composition
125141
UserRepositoryInterface <|.. UserRepository: realization
142+
UserService *-- AuditLogger: composition
126143
UserService *-- UserRepositoryInterface: composition
127144

128145
EOT;
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Tasuku43\Tests\MermaidClassDiagram\ClassDiagramRenderer\Node;
5+
6+
use PHPUnit\Framework\TestCase;
7+
use Tasuku43\MermaidClassDiagram\ClassDiagramRenderer\Node\Class_ as DiagramClass;
8+
use Tasuku43\MermaidClassDiagram\ClassDiagramRenderer\Node\Trait_ as DiagramTrait;
9+
use Tasuku43\MermaidClassDiagram\ClassDiagramRenderer\Node\Relationship\Composition;
10+
use Tasuku43\MermaidClassDiagram\ClassDiagramRenderer\Node\Relationship\Dependency;
11+
12+
class TraitAggregationTest extends TestCase
13+
{
14+
public function testClassInheritsTraitDependenciesAndCompositions(): void
15+
{
16+
$trait = new DiagramTrait('T');
17+
$dep = new DiagramClass('Dep');
18+
$prop = new DiagramClass('Prop');
19+
$trait->depend($dep);
20+
$trait->composition($prop);
21+
22+
$using = new DiagramClass('Using');
23+
$using->useTrait($trait);
24+
25+
$relationships = $using->relationships();
26+
27+
$this->assertTrue($this->hasRelationship($relationships, Composition::class, 'Using', 'Prop'));
28+
$this->assertTrue($this->hasRelationship($relationships, Dependency::class, 'Using', 'Dep'));
29+
}
30+
31+
public function testNestedTraitDependenciesAreAggregated(): void
32+
{
33+
$t1 = new DiagramTrait('T1');
34+
$t1->depend(new DiagramClass('D1'));
35+
36+
$t2 = new DiagramTrait('T2');
37+
$t2->useTrait($t1);
38+
39+
$using = new DiagramClass('Using');
40+
$using->useTrait($t2);
41+
42+
$relationships = $using->relationships();
43+
$this->assertTrue($this->hasRelationship($relationships, Dependency::class, 'Using', 'D1'));
44+
}
45+
46+
private function hasRelationship(array $relationships, string $class, string $from, string $to): bool
47+
{
48+
foreach ($relationships as $rel) {
49+
if ($rel instanceof $class) {
50+
$render = $rel->render();
51+
if (str_contains($render, $from) && str_contains($render, $to)) {
52+
return true;
53+
}
54+
}
55+
}
56+
return false;
57+
}
58+
}
59+

tests/data/Project/Service/UserService.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
class UserService
1111
{
12+
use RepositoryAwareTrait;
1213
public function __construct(
1314
private UserRepositoryInterface $userRepository
1415
) {

0 commit comments

Comments
 (0)