@@ -147,6 +147,13 @@ abstract class GenerateCommand extends Command
147147 */
148148 protected array $ Methods = self ::MEMBER_STUB ;
149149
150+ /**
151+ * Lowercase alias => alias
152+ *
153+ * @var array<string,string>
154+ */
155+ protected array $ AliasIndex = [];
156+
150157 /**
151158 * Lowercase alias => qualified name
152159 *
@@ -161,6 +168,13 @@ abstract class GenerateCommand extends Command
161168 */
162169 protected array $ ImportMap = [];
163170
171+ /**
172+ * Lowercase qualified name => qualified name
173+ *
174+ * @var array<class-string,class-string>
175+ */
176+ protected array $ FqcnMap = [];
177+
164178 // --
165179
166180 /**
@@ -173,7 +187,7 @@ abstract class GenerateCommand extends Command
173187 */
174188 protected string $ InputClassName ;
175189
176- protected PhpDoc $ InputClassPhpDoc ;
190+ protected ? PhpDoc $ InputClassPhpDoc ;
177191
178192 /**
179193 * @var PhpDocTemplateTag[]
@@ -270,6 +284,17 @@ protected function reset(): void
270284 $ this ->clearInputClass ();
271285 }
272286
287+ /**
288+ * @param class-string $fqcn
289+ */
290+ protected function assertClassExists (string $ fqcn ): void
291+ {
292+ if (class_exists ($ fqcn ) || interface_exists ($ fqcn )) {
293+ return ;
294+ }
295+ throw new CliInvalidArgumentsException (sprintf ('class not found: %s ' , $ fqcn ));
296+ }
297+
273298 /**
274299 * @param class-string $fqcn
275300 */
@@ -293,7 +318,9 @@ protected function loadInputClass(string $fqcn): void
293318 $ this ->InputClass = new ReflectionClass ($ fqcn );
294319 $ this ->InputClassName = $ this ->InputClass ->getName ();
295320 $ this ->InputClassPhpDoc = PhpDoc::fromDocBlocks (Reflect::getAllClassDocComments ($ this ->InputClass ));
296- $ this ->InputClassTemplates = $ this ->InputClassPhpDoc ->Templates ;
321+ $ this ->InputClassTemplates = $ this ->InputClassPhpDoc
322+ ? $ this ->InputClassPhpDoc ->Templates
323+ : [];
297324 $ this ->InputClassType = $ this ->InputClassTemplates
298325 ? '< ' . implode (', ' , array_keys ($ this ->InputClassTemplates )) . '> '
299326 : '' ;
@@ -342,7 +369,12 @@ protected function clearInputClass(): void
342369
343370 protected function getClassPrefix (): string
344371 {
345- return $ this ->OutputNamespace ? '\\' : '' ;
372+ return $ this ->OutputNamespace === '' ? '' : '\\' ;
373+ }
374+
375+ protected function getOutputFqcn (): string
376+ {
377+ return Arr::implode ('\\' , [$ this ->OutputNamespace , $ this ->OutputClass ]);
346378 }
347379
348380 /**
@@ -495,36 +527,47 @@ protected function getFqcnAlias(string $fqcn, ?string $alias = null, bool $retur
495527 $ _fqcn = Str::lower ($ fqcn );
496528
497529 // If $fqcn has already been imported, use its alias
498- if ($ lastAlias = $ this ->ImportMap [$ _fqcn ] ?? null ) {
499- return $ lastAlias ;
530+ if (isset ( $ this ->ImportMap [$ _fqcn ]) ) {
531+ return $ this -> ImportMap [ $ _fqcn ] ;
500532 }
501533
502- $ alias = $ alias === null ? Get::basename ($ fqcn ) : $ alias ;
534+ $ alias ??= Get::basename ($ fqcn );
503535 $ _alias = Str::lower ($ alias );
504536
537+ // Normalise $alias to the first capitalisation seen
538+ $ alias = $ this ->AliasIndex [$ _alias ] ?? $ alias ;
539+
505540 // Use $alias if it already maps to $fqcn
506- if (( $ aliasFqcn = $ this ->AliasMap [$ _alias ] ?? null ) &&
507- !strcasecmp ($ aliasFqcn , $ fqcn )) {
541+ $ aliasFqcn = $ this ->AliasMap [$ _alias ] ?? null ;
542+ if ( $ aliasFqcn !== null && !strcasecmp ($ aliasFqcn , $ fqcn )) {
508543 return $ alias ;
509544 }
510545
511546 // Use the canonical basename of the generated class
512- if (!strcasecmp ($ fqcn , "{ $ this ->OutputNamespace }\\{ $ this -> OutputClass }" )) {
547+ if (!strcasecmp ($ fqcn , $ this ->getOutputFqcn () )) {
513548 return $ this ->OutputClass ;
514549 }
515550
516- // Don't allow a conflict with the name of the generated class
517- if (!strcasecmp ($ alias , $ this ->OutputClass ) ||
518- array_key_exists ($ _alias , $ this ->AliasMap )) {
519- return $ returnFqcn ? $ this ->getClassPrefix () . $ fqcn : null ;
551+ // Don't allow a conflict with the name of the generated class or an
552+ // existing alias
553+ if (
554+ !strcasecmp ($ alias , $ this ->OutputClass ) ||
555+ isset ($ this ->AliasMap [$ _alias ])
556+ ) {
557+ $ this ->FqcnMap [$ _fqcn ] ??= $ this ->getClassPrefix () . $ fqcn ;
558+
559+ return $ returnFqcn
560+ ? $ this ->FqcnMap [$ _fqcn ]
561+ : null ;
520562 }
521563
564+ $ this ->AliasIndex [$ _alias ] = $ alias ;
522565 $ this ->AliasMap [$ _alias ] = $ fqcn ;
523566
524567 // Use $alias without importing $fqcn if:
525568 // - $fqcn is in the same namespace as the generated class; and
526569 // - the basename of $fqcn is the same as $alias
527- if (!strcasecmp ($ fqcn , "{ $ this ->OutputNamespace }\\{ $ alias}" )) {
570+ if (!strcasecmp ($ fqcn , Arr:: implode ( '\\' , [ $ this ->OutputNamespace , $ alias]) )) {
528571 return $ alias ;
529572 }
530573
@@ -613,32 +656,62 @@ protected function generate(array $innerBlocks = []): string
613656 */
614657 protected function generateImports (): array
615658 {
616- $ map = [];
659+ foreach ($ this ->getImportMap () as $ import => $ alias ) {
660+ $ imports [] = !strcasecmp ($ alias , Get::basename ($ import ))
661+ ? sprintf ('use %s; ' , $ import )
662+ : sprintf ('use %s as %s; ' , $ import , $ alias );
663+ }
664+
665+ return $ imports ?? [];
666+ }
667+
668+ /**
669+ * Get an array that maps imports to aliases
670+ *
671+ * @return array<class-string,string|null>
672+ */
673+ protected function getImportMap (bool $ sort = true ): array
674+ {
675+ if (!$ this ->ImportMap ) {
676+ return [];
677+ }
678+
617679 foreach ($ this ->ImportMap as $ alias ) {
618680 $ import = $ this ->AliasMap [Str::lower ($ alias )];
619- if (!strcasecmp ($ alias , Get::basename ($ import ))) {
620- $ map [$ import ] = null ;
621- continue ;
622- }
623681 $ map [$ import ] = $ alias ;
624682 }
625683
684+ if (!$ sort ) {
685+ return $ map ;
686+ }
687+
626688 // Sort by FQCN, depth-first
627689 uksort (
628690 $ map ,
629691 fn (string $ a , string $ b ): int =>
630692 $ this ->getSortableFqcn ($ a ) <=> $ this ->getSortableFqcn ($ b )
631693 );
632694
633- $ imports = [];
634- foreach ($ map as $ import => $ alias ) {
635- $ imports [] =
636- $ alias === null
637- ? sprintf ('use %s; ' , $ import )
638- : sprintf ('use %s as %s; ' , $ import , $ alias );
695+ return $ map ;
696+ }
697+
698+ /**
699+ * Get an array that maps aliases to qualified names
700+ *
701+ * @return array<string,class-string>
702+ */
703+ protected function getAliasMap (): array
704+ {
705+ if (!$ this ->AliasMap ) {
706+ return [];
639707 }
640708
641- return $ imports ;
709+ foreach ($ this ->AliasMap as $ _alias => $ fqcn ) {
710+ $ alias = $ this ->AliasIndex [$ _alias ];
711+ $ map [$ alias ] = $ fqcn ;
712+ }
713+
714+ return $ map ;
642715 }
643716
644717 /**
@@ -651,12 +724,18 @@ protected function generateGetter(
651724 string $ name ,
652725 string $ valueCode ,
653726 $ phpDoc = '@inheritDoc ' ,
654- string $ returnType = 'string ' ,
727+ ? string $ returnType = 'string ' ,
655728 string $ visibility = GenerateCommand::VISIBILITY_PUBLIC
656729 ): array {
657730 return [
658731 ...$ this ->generatePhpDocBlock ($ phpDoc ),
659- sprintf ('%s static function %s(): %s ' , $ visibility , $ name , $ returnType ),
732+ sprintf (
733+ '%s static function %s()%s%s ' ,
734+ $ visibility ,
735+ $ name ,
736+ $ returnType === null ? '' : ': ' ,
737+ $ returnType ,
738+ ),
660739 '{ ' ,
661740 $ this ->indent (sprintf ('return %s; ' , $ valueCode )),
662741 '} '
@@ -808,10 +887,12 @@ protected function handleOutput($lines): void
808887 }
809888 if ($ this ->Check || !$ this ->ReplaceIfExists ) {
810889 $ relative = File::relativeToParent ($ file , Package::path (), $ file );
811- print (new Differ (new StrictUnifiedDiffOutputBuilder ([
890+ $ formatter = Console::getStdoutTarget ()->getFormatter ();
891+ $ diff = (new Differ (new StrictUnifiedDiffOutputBuilder ([
812892 'fromFile ' => "a/ $ relative " ,
813893 'toFile ' => "b/ $ relative " ,
814894 ])))->diff ($ input , $ output );
895+ print $ formatter ->formatDiff ($ diff );
815896 if (!$ this ->Check ) {
816897 Console::info ('Out of date: ' , $ file );
817898 return ;
@@ -844,7 +925,14 @@ protected function handleOutput($lines): void
844925 */
845926 protected function code ($ value ): string
846927 {
847- return Get::code ($ value , ', ' . \PHP_EOL , ' => ' , null , self ::TAB );
928+ return Get::code (
929+ $ value ,
930+ ', ' . \PHP_EOL ,
931+ ' => ' ,
932+ null ,
933+ self ::TAB ,
934+ array_merge (array_keys ($ this ->getAliasMap ()), $ this ->FqcnMap ),
935+ );
848936 }
849937
850938 /**
0 commit comments