Skip to content

Commit 774eee4

Browse files
committed
Merge branch 'refactor-facade'
2 parents 1aa998f + ec035ee commit 774eee4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+814
-250
lines changed

.gitattributes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
/tests/data/entity/posts.json linguist-generated
3131
/tests/data/entity/todos.json linguist-generated
3232
/tests/data/entity/users.json linguist-generated
33+
/tests/fixtures/Concept/Facade/MyBrokenFacade.php linguist-generated
34+
/tests/fixtures/Concept/Facade/MyClassFacade.php linguist-generated
35+
/tests/fixtures/Concept/Facade/MyInterfaceFacade.php linguist-generated
3336
/tests/fixtures/Sync/Entity/Album.php linguist-generated
3437
/tests/fixtures/Sync/Entity/Comment.php linguist-generated
3538
/tests/fixtures/Sync/Entity/Photo.php linguist-generated

lk-util/Command/.prettyphp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"enable": [
3+
"preserve-one-line",
4+
"strict-lists"
5+
],
6+
"heredocIndent": "none"
7+
}

lk-util/Command/Concept/Command.php

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ abstract class Command extends CliCommand
2222
* Normalise a user-supplied class name, optionally assigning its base name
2323
* and/or namespace to variables passed by reference
2424
*
25-
* @return class-string<object>
25+
* @return class-string|''
2626
*/
2727
protected function getFqcnOptionValue(
2828
string $value,
@@ -31,13 +31,13 @@ protected function getFqcnOptionValue(
3131
?string &$namespace = null
3232
): string {
3333
$namespace = null;
34-
if ($namespaceEnvVar) {
34+
if ($namespaceEnvVar !== null) {
3535
$namespace = Env::get($namespaceEnvVar, null);
3636
}
3737
if ($namespace === null) {
3838
$namespace = Env::get(EnvVar::NS_DEFAULT, null);
3939
}
40-
if ($namespace && trim($value) && strpos($value, '\\') === false) {
40+
if ($namespace !== null && trim($value) && strpos($value, '\\') === false) {
4141
$fqcn = trim($namespace, '\\') . "\\$value";
4242
} else {
4343
$fqcn = ltrim($value, '\\');
@@ -52,7 +52,7 @@ protected function getFqcnOptionValue(
5252
* Normalise a mandatory user-supplied class name, optionally assigning its
5353
* base name and/or namespace to variables passed by reference
5454
*
55-
* @return class-string<object>
55+
* @return class-string
5656
*/
5757
protected function getRequiredFqcnOptionValue(
5858
string $valueName,
@@ -62,26 +62,29 @@ protected function getRequiredFqcnOptionValue(
6262
?string &$namespace = null
6363
): string {
6464
$fqcn = $this->getFqcnOptionValue($value, $namespaceEnvVar, $class, $namespace);
65-
if (!$fqcn) {
65+
66+
if ($fqcn === '') {
6667
throw new CliInvalidArgumentsException(sprintf('invalid %s: %s', $valueName, $value));
6768
}
6869

6970
return $fqcn;
7071
}
7172

7273
/**
73-
* Normalise user-supplied class names
74+
* Normalise mandatory user-supplied class names
7475
*
7576
* @param string[] $values
76-
* @return array<class-string<object>>
77+
* @return array<class-string>
7778
*/
78-
protected function getMultipleFqcnOptionValue(array $values, ?string $namespaceEnvVar = null): array
79-
{
79+
protected function requireMultipleFqcnValues(
80+
string $valueName,
81+
array $values,
82+
?string $namespaceEnvVar = null
83+
): array {
8084
$fqcn = [];
8185
foreach ($values as $value) {
82-
$fqcn[] = $this->getFqcnOptionValue($value, $namespaceEnvVar);
86+
$fqcn[] = $this->getRequiredFqcnOptionValue($valueName, $value, $namespaceEnvVar);
8387
}
84-
8588
return $fqcn;
8689
}
8790

lk-util/Command/Generate/Concept/GenerateCommand.php

Lines changed: 118 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)