44namespace Tasuku43 \MermaidClassDiagram \ClassDiagramRenderer \Node \Relationship ;
55
66use Tasuku43 \MermaidClassDiagram \ClassDiagramRenderer \RenderOptions \RenderOptions ;
7+ use Tasuku43 \MermaidClassDiagram \ClassDiagramRenderer \Node \Trait_ ;
8+ use Tasuku43 \MermaidClassDiagram \ClassDiagramRenderer \Node \Node ;
79
810class 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