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

Commit f1a0941

Browse files
committed
feat: Refactor filter methods to optimize relationships and nodes handling
1 parent 45e7788 commit f1a0941

File tree

4 files changed

+172
-9
lines changed

4 files changed

+172
-9
lines changed

src/ClassDiagramRenderer/ClassDiagram.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,11 @@ public function addRelationships(Relationship ...$relationships): self
3939

4040
public function render(RenderOptions $options): string
4141
{
42-
$nodes = $this->nodes->filter($options)->sort()->getAll();
43-
$relationships = $this->relationships->filter($options)->sort()->getAll();
42+
$nodes = $this->nodes->optimize($options)->sort()->getAll();
43+
$relationships = $this->relationships
44+
->optimize($options)
45+
->sort()
46+
->getAll();
4447

4548
$output = "classDiagram\n";
4649

src/ClassDiagramRenderer/ClassDiagramDumper.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ private function extract(RenderOptions $options): array
6161
$relationships = $relationshipsProp->getValue($this->diagram);
6262

6363
// Apply the same filtering/sorting policy as render()
64-
$nodes = $nodes->filter($options)->sort()->getAll();
65-
$relationships = $relationships->filter($options)->sort()->getAll();
64+
$nodes = $nodes->optimize($options)->sort()->getAll();
65+
$relationships = $relationships->optimize($options)->sort()->getAll();
6666

6767
return [$nodes, $relationships];
6868
}
@@ -91,4 +91,3 @@ private function relationshipType(Relationship $relationship): string
9191
};
9292
}
9393
}
94-

src/ClassDiagramRenderer/Node/Nodes.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,12 @@ public function sort(): self
4141
return $this;
4242
}
4343

44-
public function filter(RenderOptions $options): self
44+
public function optimize(RenderOptions $options): self
45+
{
46+
return $this->filterByOption($options);
47+
}
48+
49+
private function filterByOption(RenderOptions $options): self
4550
{
4651
$filtered = new self();
4752
foreach ($this->nodes as $node) {

src/ClassDiagramRenderer/Node/Relationship/Relationships.php

Lines changed: 159 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
namespace Tasuku43\MermaidClassDiagram\ClassDiagramRenderer\Node\Relationship;
55

66
use Tasuku43\MermaidClassDiagram\ClassDiagramRenderer\RenderOptions\RenderOptions;
7+
use Tasuku43\MermaidClassDiagram\ClassDiagramRenderer\Node\Trait_;
8+
use Tasuku43\MermaidClassDiagram\ClassDiagramRenderer\Node\Node;
79

810
class Relationships
911
{
@@ -43,9 +45,163 @@ public function sort(): self
4345
return $this;
4446
}
4547

46-
public function filter(RenderOptions $options): self
48+
public function optimize(RenderOptions $options): self
4749
{
48-
$filtered = array_filter($this->relationships, function (Relationship $relationship) use ($options) {
50+
// Delegate to clearer, private implementations per mode
51+
$relationships = $options->traitRenderMode->isWithTraits()
52+
? $this->optimizeWithTraitsInternal()
53+
: $this->optimizeFlattenInternal();
54+
55+
return new self($this->filterByOptions($relationships, $options));
56+
}
57+
58+
/**
59+
* Flatten mode: reassign trait-origin relationships to using classes,
60+
* hide TraitUsage edges, and deduplicate by (type, from, to).
61+
* @return Relationship[]
62+
*/
63+
private function optimizeFlattenInternal(): array
64+
{
65+
$traitUsers = $this->buildTraitUsersMap();
66+
$flattened = [];
67+
$seen = [];
68+
69+
foreach ($this->relationships as $rel) {
70+
if ($rel instanceof TraitUsage) {
71+
continue; // hide trait usage edges in flatten mode
72+
}
73+
74+
if ($rel->from instanceof Trait_) {
75+
$users = $traitUsers[$rel->from->nodeName()] ?? [];
76+
if (!empty($users)) {
77+
foreach ($users as $userNode) {
78+
$className = get_class($rel);
79+
/** @var Relationship $new */
80+
$new = new $className($userNode, $rel->to);
81+
$k = $this->generateKey($new);
82+
if (!isset($seen[$k])) {
83+
$seen[$k] = true;
84+
$flattened[] = $new;
85+
}
86+
}
87+
}
88+
// drop original trait-origin edge
89+
continue;
90+
}
91+
92+
$k = $this->generateKey($rel);
93+
if (!isset($seen[$k])) {
94+
$seen[$k] = true;
95+
$flattened[] = $rel;
96+
}
97+
}
98+
99+
return $flattened;
100+
}
101+
102+
/**
103+
* WithTraits mode: keep trait and `use` edges, and suppress class-level
104+
* duplicates for composition/dependency that are already provided by traits.
105+
* @return Relationship[]
106+
*/
107+
private function optimizeWithTraitsInternal(): array
108+
{
109+
$traitProvides = $this->buildTraitProvidesMap();
110+
$classUses = $this->buildClassUsesMap();
111+
112+
$result = [];
113+
$seen = [];
114+
foreach ($this->relationships as $rel) {
115+
$from = $rel->from;
116+
117+
$isSuppressedType = $rel instanceof Dependency || $rel instanceof Composition;
118+
if ($isSuppressedType && !($from instanceof Trait_)) {
119+
$traits = $classUses[$from->nodeName()] ?? [];
120+
$suppress = false;
121+
foreach ($traits as $tName) {
122+
if (!empty($traitProvides[get_class($rel)][$rel->to->nodeName()] ?? false)) {
123+
$suppress = true;
124+
break;
125+
}
126+
}
127+
if ($suppress) {
128+
continue;
129+
}
130+
}
131+
132+
$k = $this->generateKey($rel);
133+
if (!isset($seen[$k])) {
134+
$seen[$k] = true;
135+
$result[] = $rel;
136+
}
137+
}
138+
139+
return $result;
140+
}
141+
142+
/**
143+
* Build a map: traitName => [ className => Node ].
144+
* @return array<string, array<string, Node>>
145+
*/
146+
private function buildTraitUsersMap(): array
147+
{
148+
$traitUsers = [];
149+
foreach ($this->relationships as $rel) {
150+
if ($rel instanceof TraitUsage) {
151+
$traitName = $rel->to->nodeName();
152+
$traitUsers[$traitName] ??= [];
153+
$traitUsers[$traitName][$rel->from->nodeName()] = $rel->from;
154+
}
155+
}
156+
return $traitUsers;
157+
}
158+
159+
/**
160+
* Build a map of trait-provided targets by relationship type:
161+
* type(FQCN) => [ toNodeName => true ].
162+
* @return array<string, array<string, bool>>
163+
*/
164+
private function buildTraitProvidesMap(): array
165+
{
166+
$traitProvides = [];
167+
foreach ($this->relationships as $rel) {
168+
if ($rel->from instanceof Trait_) {
169+
$type = get_class($rel);
170+
$traitProvides[$type] ??= [];
171+
$traitProvides[$type][$rel->to->nodeName()] = true;
172+
}
173+
}
174+
return $traitProvides;
175+
}
176+
177+
/**
178+
* Build a map: className => [ traitName, ... ].
179+
* @return array<string, string[]>
180+
*/
181+
private function buildClassUsesMap(): array
182+
{
183+
$classUses = [];
184+
foreach ($this->relationships as $rel) {
185+
if ($rel instanceof TraitUsage) {
186+
$classUses[$rel->from->nodeName()] ??= [];
187+
$classUses[$rel->from->nodeName()][] = $rel->to->nodeName();
188+
}
189+
}
190+
return $classUses;
191+
}
192+
193+
private function generateKey(Relationship $r): string
194+
{
195+
return get_class($r) . '|' . $r->from->nodeName() . '|' . $r->to->nodeName();
196+
}
197+
198+
/**
199+
* @param Relationship[] $relationships
200+
* @return Relationship[]
201+
*/
202+
private function filterByOptions(array $relationships, RenderOptions $options): array
203+
{
204+
$filtered = array_filter($relationships, function (Relationship $relationship) use ($options) {
49205
if ($relationship instanceof TraitUsage && !$options->traitRenderMode->isWithTraits()) {
50206
return false;
51207
}
@@ -65,6 +221,6 @@ public function filter(RenderOptions $options): self
65221
return true;
66222
});
67223

68-
return new self(array_values($filtered));
224+
return array_values($filtered);
69225
}
70226
}

0 commit comments

Comments
 (0)