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

Commit 05f5f45

Browse files
committed
feat: Implement buildTraitClassUsersMap for transitive trait user mapping and add TraitA/B classes for testing
1 parent f1a0941 commit 05f5f45

File tree

7 files changed

+156
-11
lines changed

7 files changed

+156
-11
lines changed

src/ClassDiagramRenderer/Node/Relationship/Relationships.php

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public function optimize(RenderOptions $options): self
6262
*/
6363
private function optimizeFlattenInternal(): array
6464
{
65-
$traitUsers = $this->buildTraitUsersMap();
65+
$traitUsers = $this->buildTraitClassUsersMap();
6666
$flattened = [];
6767
$seen = [];
6868

@@ -156,6 +156,45 @@ private function buildTraitUsersMap(): array
156156
return $traitUsers;
157157
}
158158

159+
/**
160+
* Build a map from trait name to transitive class users (flattening trait->trait chains).
161+
* Only final class-like nodes (non-trait) are included as users.
162+
* @return array<string, array<string, Node>> traitName => [className => Node]
163+
*/
164+
private function buildTraitClassUsersMap(): array
165+
{
166+
$directUsers = $this->buildTraitUsersMap();
167+
$result = [];
168+
169+
foreach ($directUsers as $traitName => $usersMap) {
170+
$finals = [];
171+
$queue = array_values($usersMap);
172+
$visitedTraits = [$traitName => true];
173+
174+
while (!empty($queue)) {
175+
/** @var Node $user */
176+
$user = array_shift($queue);
177+
if ($user instanceof Trait_) {
178+
$tName = $user->nodeName();
179+
if (!empty($visitedTraits[$tName])) {
180+
continue;
181+
}
182+
$visitedTraits[$tName] = true;
183+
foreach ($directUsers[$tName] ?? [] as $next) {
184+
$queue[] = $next;
185+
}
186+
continue;
187+
}
188+
189+
$finals[$user->nodeName()] = $user;
190+
}
191+
192+
$result[$traitName] = $finals;
193+
}
194+
195+
return $result;
196+
}
197+
159198
/**
160199
* Build a map of trait-provided targets by relationship type:
161200
* type(FQCN) => [ toNodeName => true ].

tests/ClassDiagramRenderer/ClassDiagramBuilderTest.php

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,6 @@ protected function setUp(): void
2828
$this->classDigagramBuilder = new ClassDiagramBuilder($this->nodeParser);
2929
}
3030

31-
public function testDump(): void
32-
{
33-
$path = __DIR__ . '/../data/Project';
34-
35-
$classDiagram = $this->classDigagramBuilder
36-
->build($path);
37-
$dumper = new ClassDiagramDumper($classDiagram);
38-
echo $dumper->toYaml();
39-
}
40-
4131
public function testBuildFromSampleProject(): void
4232
{
4333
$path = __DIR__ . '/../data/Project';
@@ -195,6 +185,66 @@ class UserStatus {
195185
UserRepositoryInterface <|.. UserRepository: realization
196186
UserService --> RepositoryAwareTrait: use
197187

188+
EOT;
189+
190+
$this->assertSame($expectedDiagram, $classDiagram);
191+
}
192+
193+
public function testTraitUsesTrait_WithTraits(): void
194+
{
195+
$path = __DIR__ . '/../data/TraitChain';
196+
197+
$classDiagram = $this->classDigagramBuilder
198+
->build($path)
199+
->render(new RenderOptions(true, true, true, true, \Tasuku43\MermaidClassDiagram\ClassDiagramRenderer\TraitRenderMode::WithTraits));
200+
201+
$expectedDiagram = <<<'EOT'
202+
classDiagram
203+
class ChainUser {
204+
}
205+
class DepClass {
206+
}
207+
class DepInterface {
208+
<<interface>>
209+
}
210+
class TraitA {
211+
<<trait>>
212+
}
213+
class TraitB {
214+
<<trait>>
215+
}
216+
217+
ChainUser --> TraitA: use
218+
TraitA --> TraitB: use
219+
TraitB *-- DepClass: composition
220+
TraitB ..> DepInterface: dependency
221+
222+
EOT;
223+
224+
$this->assertSame($expectedDiagram, $classDiagram);
225+
}
226+
227+
public function testTraitUsesTrait_Flatten(): void
228+
{
229+
$path = __DIR__ . '/../data/TraitChain';
230+
231+
$classDiagram = $this->classDigagramBuilder
232+
->build($path)
233+
->render(new RenderOptions(true, true, true, true, \Tasuku43\MermaidClassDiagram\ClassDiagramRenderer\TraitRenderMode::Flatten));
234+
235+
$expectedDiagram = <<<'EOT'
236+
classDiagram
237+
class ChainUser {
238+
}
239+
class DepClass {
240+
}
241+
class DepInterface {
242+
<<interface>>
243+
}
244+
245+
ChainUser *-- DepClass: composition
246+
ChainUser ..> DepInterface: dependency
247+
198248
EOT;
199249

200250
$this->assertSame($expectedDiagram, $classDiagram);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace TraitChain;
5+
6+
class ChainUser
7+
{
8+
use TraitA;
9+
}
10+

tests/data/TraitChain/DepClass.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Tasuku43\Tests\MermaidClassDiagram\data\TraitChain;
5+
6+
class DepClass
7+
{
8+
}
9+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Tasuku43\Tests\MermaidClassDiagram\data\TraitChain;
5+
6+
interface DepInterface
7+
{
8+
}
9+

tests/data/TraitChain/TraitA.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace TraitChain;
5+
6+
trait TraitA
7+
{
8+
use TraitB;
9+
}
10+

tests/data/TraitChain/TraitB.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace TraitChain;
5+
6+
use Tasuku43\Tests\MermaidClassDiagram\data\TraitChain\DepClass;
7+
use Tasuku43\Tests\MermaidClassDiagram\data\TraitChain\DepInterface;
8+
9+
trait TraitB
10+
{
11+
private DepClass $dep;
12+
13+
public function doSomething(DepInterface $iface): void
14+
{
15+
// noop in tests
16+
}
17+
}
18+

0 commit comments

Comments
 (0)