1515final class CodeGenerator
1616{
1717 /**
18- * @var array<string, string >
18+ * @var array<string, Alias|FullyQualified|FunctionName|NamespaceName >
1919 */
2020 private array $ imports = [];
21+ private readonly ?NamespaceName $ namespace ;
2122
2223 public function __construct (
23- private readonly ?string $ namespace = null ,
24- ) {}
24+ null | NamespaceName | string $ namespace = null ,
25+ ) {
26+ $ this ->namespace = NamespaceName::maybeFromString ($ namespace );
27+ }
2528
2629 /**
2730 * Dumps the generated code with proper formatting (removes consecutive newlines, trims)
@@ -74,30 +77,46 @@ public function dumpFile(array | Closure | Generator | string $iterable) : strin
7477 */
7578 private function dumpImports () : Generator
7679 {
77- uasort (
78- $ this ->imports ,
79- fn ($ left , $ right ) => strcasecmp (
80- str_replace ('\\' , ' ' , str_starts_with ($ left , 'function ' ) ? substr ($ left , 9 ) : $ left ),
81- str_replace ('\\' , ' ' , str_starts_with ($ right , 'function ' ) ? substr ($ right , 9 ) : $ right ),
82- ),
83- );
80+ uasort ($ this ->imports , fn ($ left , $ right ) => $ left ->compare ($ right ));
8481
8582 foreach ($ this ->imports as $ alias => $ import ) {
86- if ( ! str_starts_with ( $ import, ' function ' ) ) {
87- [ $ namespace , $ class ] = $ this -> splitFqcn ( $ import );
83+ if ($ import instanceof Alias ) {
84+ yield $ import -> toUseStatement ( );
8885
89- if ($ namespace === $ this ->namespace ) {
90- continue ;
91- }
86+ continue ;
87+ }
9288
93- if ($ alias !== $ class ) {
94- yield sprintf ('use %s as %s; ' , $ import , $ alias );
89+ if ($ import instanceof FunctionName) {
90+ // Handle function imports
91+ yield sprintf ('use %s; ' , $ import );
9592
96- continue ;
93+ continue ;
94+ }
95+
96+ if ($ import instanceof NamespaceName) {
97+ // Parent namespace import - check if we need an alias
98+ $ lastPart = $ import ->lastPart ;
99+
100+ if ($ alias !== $ lastPart ) {
101+ yield sprintf ('use %s as %s; ' , $ import , $ alias );
102+ } else {
103+ yield sprintf ('use %s; ' , $ import );
97104 }
105+
106+ continue ;
98107 }
99108
100- yield sprintf ('use %s; ' , $ import );
109+ // Skip if it's in the same namespace as the file
110+ if ($ import ->namespace !== null && $ this ->namespace !== null && $ import ->namespace ->equals ($ this ->namespace )) {
111+ continue ;
112+ }
113+
114+ // Check if we need an alias
115+ if ($ alias !== $ import ->className ->name ) {
116+ yield sprintf ('use %s as %s; ' , $ import , $ alias );
117+ } else {
118+ yield sprintf ('use %s; ' , $ import );
119+ }
101120 }
102121 }
103122
@@ -137,27 +156,21 @@ public function maybeDump(
137156 yield from self ::resolveIterable ($ after );
138157 }
139158
140- /**
141- * Splits a fully qualified class name into namespace and class name parts
142- * @return array{string, string}
143- */
144- public function splitFqcn (string $ fqcn ) : array
145- {
146- $ parts = explode ('\\' , $ fqcn );
147- $ className = array_pop ($ parts );
148- $ namespace = implode ('\\' , $ parts );
149-
150- return [$ namespace , $ className ];
151- }
152-
153159 /**
154160 * Finds an available alias for a type, appending numbers if the alias is already taken
155161 */
156- private function findAvailableAlias (string $ type , string $ alias , int $ i = 1 ) : string
162+ private function findAvailableAlias (Alias | FullyQualified | FunctionName | NamespaceName $ type , string $ alias , int $ i = 1 ) : string
157163 {
158164 $ aliasToCheck = $ i === 1 ? $ alias : sprintf ('%s%d ' , $ alias , $ i );
159165
160- if ( ! isset ($ this ->imports [$ aliasToCheck ]) || $ this ->imports [$ aliasToCheck ] === $ type ) {
166+ if ( ! isset ($ this ->imports [$ aliasToCheck ])) {
167+ return $ aliasToCheck ;
168+ }
169+
170+ $ existing = $ this ->imports [$ aliasToCheck ];
171+
172+ // Check if it's the same import
173+ if ($ existing ->equals ($ type )) {
161174 return $ aliasToCheck ;
162175 }
163176
@@ -167,60 +180,44 @@ private function findAvailableAlias(string $type, string $alias, int $i = 1) : s
167180 /**
168181 * Imports a class, function, or enum and returns the alias to use in the generated code
169182 */
170- public function import (string | UnitEnum $ fqcnOrEnum , bool $ byParent = false ) : string
183+ public function import (FullyQualified | FunctionName | string | UnitEnum $ fqcnOrEnum ) : string
171184 {
172- if (is_string ($ fqcnOrEnum ) && str_starts_with ($ fqcnOrEnum , 'function ' )) {
173- $ this ->imports [$ fqcnOrEnum ] = $ fqcnOrEnum ;
174-
175- $ parts = explode ('\\' , $ fqcnOrEnum );
185+ if ($ fqcnOrEnum instanceof FunctionName) {
186+ $ alias = $ this ->findAvailableAlias ($ fqcnOrEnum , $ fqcnOrEnum ->shortName );
187+ $ this ->imports [$ alias ] = $ fqcnOrEnum ;
176188
177- return array_pop ( $ parts ) ;
189+ return $ alias ;
178190 }
179191
180192 if ($ fqcnOrEnum instanceof UnitEnum) {
181- $ type = $ fqcnOrEnum ::class;
182- } else {
183- $ type = $ fqcnOrEnum ;
184- }
185-
186- [$ namespace , $ alias ] = $ this ->splitFqcn ($ type );
187-
188- if ($ byParent ) {
189- [, $ parent ] = $ this ->splitFqcn ($ namespace );
190-
191- $ parent = $ this ->findAvailableAlias ($ namespace , $ parent );
193+ $ fqcn = new FullyQualified ($ fqcnOrEnum ::class);
194+ $ alias = $ this ->findAvailableAlias ($ fqcn , $ fqcn ->className ->name );
195+ $ this ->imports [$ alias ] = $ fqcn ;
192196
193- $ this ->imports [$ parent ] = $ namespace ;
194-
195- $ reference = sprintf ('%s \\%s ' , $ parent , $ alias );
196- } else {
197- $ alias = $ this ->findAvailableAlias ($ type , $ alias );
198-
199- $ this ->imports [$ alias ] = $ type ;
200- $ reference = $ alias ;
197+ return sprintf ('%s::%s ' , $ alias , $ fqcnOrEnum ->name );
201198 }
202199
203- if ($ fqcnOrEnum instanceof UnitEnum) {
204- return sprintf ( ' %s::%s ' , $ reference , $ fqcnOrEnum ->name );
205- }
200+ $ fqcn = FullyQualified:: maybeFromString ($ fqcnOrEnum);
201+ $ alias = $ this -> findAvailableAlias ( $ fqcn , $ fqcn -> className ->name );
202+ $ this -> imports [ $ alias ] = $ fqcn ;
206203
207- return $ reference ;
204+ return $ alias ;
208205 }
209206
210207 /**
211208 * Generates a PHP attribute string for the given fully qualified class name
212209 */
213- public function dumpAttribute (string $ fqcn ) : string
210+ public function dumpAttribute (FullyQualified | string $ fqcn ) : string
214211 {
215212 return sprintf ('#[%s] ' , $ this ->import ($ fqcn ));
216213 }
217214
218215 /**
219216 * Generates a class reference string (e.g., Foo::class)
220217 */
221- public function dumpClassReference (string $ fqcn , bool $ import = true , bool $ byParent = false ) : string
218+ public function dumpClassReference (FullyQualified | string $ fqcn , bool $ import = true ) : string
222219 {
223- return sprintf ('%s::class ' , $ import ? $ this ->import ($ fqcn, $ byParent ) : '\\' . $ fqcn );
220+ return sprintf ('%s::class ' , $ import ? $ this ->import ($ fqcn ) : '\\' . ( string ) $ fqcn );
224221 }
225222
226223 /**
0 commit comments