diff --git a/src/Alias.php b/src/Alias.php index 1b5d74d..1ffdece 100644 --- a/src/Alias.php +++ b/src/Alias.php @@ -6,16 +6,15 @@ use InvalidArgumentException; use Override; -use Stringable; -final readonly class Alias implements Stringable +final readonly class Alias implements Importable { public string $alias; - public FullyQualified | FunctionName | NamespaceName $target; + public Importable $target; public function __construct( string $alias, - FullyQualified | FunctionName | NamespaceName $target, + Importable $target, ) { $alias = trim($alias); @@ -37,6 +36,7 @@ public function __toString() : string return sprintf('%s as %s', $this->target, $this->alias); } + #[Override] public function equals(object $other) : bool { return $other instanceof self @@ -44,6 +44,7 @@ public function equals(object $other) : bool && $this->target->equals($other->target); } + #[Override] public function compare(object $other) : int { // Aliases should sort by their target, not their alias name diff --git a/src/ClassName.php b/src/ClassName.php index b3b5494..5686355 100644 --- a/src/ClassName.php +++ b/src/ClassName.php @@ -6,9 +6,8 @@ use InvalidArgumentException; use Override; -use Stringable; -final readonly class ClassName implements Stringable +final readonly class ClassName implements Importable { /** * @var non-empty-string @@ -34,7 +33,7 @@ public function __construct( /** * @phpstan-return ($input is null ? null : self) */ - public static function maybeFromString(null | self | string $input) : ?self + public static function maybeFromString(null | Importable | self | string $input) : ?self { if ($input === null) { return null; @@ -44,7 +43,7 @@ public static function maybeFromString(null | self | string $input) : ?self return $input; } - return new self($input); + return new self((string) $input); } #[Override] @@ -53,11 +52,13 @@ public function __toString() : string return $this->name; } + #[Override] public function equals(object $other) : bool { return $other instanceof self && $this->name === $other->name; } + #[Override] public function compare(object $other) : int { if ($other instanceof self) { diff --git a/src/CodeGenerator.php b/src/CodeGenerator.php index de90d6e..4156ef0 100644 --- a/src/CodeGenerator.php +++ b/src/CodeGenerator.php @@ -159,7 +159,7 @@ public function maybeDump( /** * Finds an available alias for a type, appending numbers if the alias is already taken */ - private function findAvailableAlias(Alias | FullyQualified | FunctionName | NamespaceName $type, string $alias, int $i = 1) : string + private function findAvailableAlias(Importable $type, string $alias, int $i = 1) : string { $aliasToCheck = $i === 1 ? $alias : sprintf('%s%d', $alias, $i); @@ -188,7 +188,7 @@ public function importEnum(UnitEnum $enum) : string /** * Imports a class, namespace, or function and returns the alias to use in the generated code */ - public function import(FullyQualified | FunctionName | NamespaceName | string $fqcnOrEnum) : string + public function import(Importable | string $fqcnOrEnum) : string { if ($fqcnOrEnum instanceof FunctionName) { $alias = $this->findAvailableAlias($fqcnOrEnum, $fqcnOrEnum->shortName); @@ -214,7 +214,7 @@ public function import(FullyQualified | FunctionName | NamespaceName | string $f /** * Imports a class by importing its parent namespace and returning the relative path */ - public function importByParent(FullyQualified | string $name) : string + public function importByParent(Importable | string $name) : string { $fqcn = FullyQualified::maybeFromString($name); @@ -231,7 +231,7 @@ public function importByParent(FullyQualified | string $name) : string // Import the namespace and return the alias with class name return (string) new FullyQualified( $this->import($fqcn->namespace), - (string) $fqcn->className, + $fqcn->className, ); } diff --git a/src/FullyQualified.php b/src/FullyQualified.php index 05aa5c5..a4bb88f 100644 --- a/src/FullyQualified.php +++ b/src/FullyQualified.php @@ -6,19 +6,24 @@ use InvalidArgumentException; use Override; -use Stringable; -final readonly class FullyQualified implements Stringable +final readonly class FullyQualified implements Importable { public ClassName $className; public ?NamespaceName $namespace; public function __construct( - string $part, - string ...$parts, + Importable | string $part, + Importable | string ...$parts, ) { $flattened = array_filter( - explode('\\', implode('\\', [$part, ...$parts])), + explode( + '\\', + implode( + '\\', + array_map(strval(...), [$part, ...$parts]), + ), + ), fn($p) => $p !== '', ); @@ -29,15 +34,13 @@ public function __construct( $classNamePart = array_pop($flattened); $this->className = new ClassName($classNamePart); - $this->namespace = $flattened !== [] - ? new NamespaceName(implode('\\', $flattened)) - : null; + $this->namespace = $flattened !== [] ? new NamespaceName(implode('\\', $flattened)) : null; } /** * @phpstan-return ($input is null ? null : self) */ - public static function maybeFromString(null | self | string $input) : ?self + public static function maybeFromString(null | Importable | self | string $input) : ?self { if ($input === null) { return null; @@ -47,7 +50,7 @@ public static function maybeFromString(null | self | string $input) : ?self return $input; } - return new self($input); + return new self((string) $input); } #[Override] @@ -60,11 +63,13 @@ public function __toString() : string return $this->namespace . '\\' . $this->className; } + #[Override] public function equals(object $other) : bool { return $other instanceof self && (string) $this === (string) $other; } + #[Override] public function compare(object $other) : int { $thisStr = str_replace('\\', ' ', (string) $this); diff --git a/src/FunctionName.php b/src/FunctionName.php index bb926ec..86e980f 100644 --- a/src/FunctionName.php +++ b/src/FunctionName.php @@ -6,9 +6,8 @@ use InvalidArgumentException; use Override; -use Stringable; -final class FunctionName implements Stringable +final class FunctionName implements Importable { public readonly string $name; @@ -38,7 +37,7 @@ public function __construct( /** * @phpstan-return ($input is null ? null : self) */ - public static function maybeFromString(null | self | string $input) : ?self + public static function maybeFromString(null | Importable | self | string $input) : ?self { if ($input === null) { return null; @@ -48,7 +47,7 @@ public static function maybeFromString(null | self | string $input) : ?self return $input; } - return new self($input); + return new self((string) $input); } #[Override] @@ -57,11 +56,13 @@ public function __toString() : string return 'function ' . $this->name; } + #[Override] public function equals(object $other) : bool { return $other instanceof self && (string) $this === (string) $other; } + #[Override] public function compare(object $other) : int { $thisStr = str_replace('\\', ' ', $this->name); diff --git a/src/Importable.php b/src/Importable.php new file mode 100644 index 0000000..ed421b4 --- /dev/null +++ b/src/Importable.php @@ -0,0 +1,23 @@ +namespace, $part, ...$parts); } + #[Override] public function equals(object $other) : bool { return $other instanceof self && $this->namespace === $other->namespace; } + #[Override] public function compare(object $other) : int { $thisStr = str_replace('\\', ' ', $this->namespace); diff --git a/tests/ImportableTest.php b/tests/ImportableTest.php new file mode 100644 index 0000000..e454cb1 --- /dev/null +++ b/tests/ImportableTest.php @@ -0,0 +1,154 @@ +equals($importable)); + // Test that string conversion works + $stringRepresentation = (string) $importable; + self::assertNotEmpty($stringRepresentation); + } + } + + public function testFullyQualifiedMaybeFromStringWithImportable() : void + { + $className = new ClassName('MyClass'); + $functionName = new FunctionName('App\\Helpers\\format'); + $namespaceName = new NamespaceName('App\\Models'); + + // Test with ClassName + $result1 = FullyQualified::maybeFromString($className); + self::assertSame('MyClass', (string) $result1); + + // Test with FunctionName - should convert its string representation + $result2 = FullyQualified::maybeFromString($functionName); + // FunctionName __toString includes "function " prefix, so this will create a class with that name + self::assertSame('function App\\Helpers\\format', (string) $result2); + + // Test with NamespaceName + $result3 = FullyQualified::maybeFromString($namespaceName); + self::assertSame('App\\Models', (string) $result3); + } + + public function testClassNameMaybeFromStringWithImportable() : void + { + $fullyQualified = new FullyQualified('User'); // No namespace, just class name + $namespaceName = new NamespaceName('Models'); + + // Test with FullyQualified (no namespace) + $result1 = ClassName::maybeFromString($fullyQualified); + self::assertSame('User', $result1->name); + + // Test with NamespaceName + $result2 = ClassName::maybeFromString($namespaceName); + self::assertSame('Models', $result2->name); + } + + public function testFunctionNameMaybeFromStringWithImportable() : void + { + $fullyQualified = new FullyQualified('App\\Helpers\\format'); + $namespaceName = new NamespaceName('App\\Helpers'); + + // Test with FullyQualified + $result1 = FunctionName::maybeFromString($fullyQualified); + self::assertSame('function App\\Helpers\\format', (string) $result1); + + // Test with NamespaceName + $result2 = FunctionName::maybeFromString($namespaceName); + self::assertSame('function App\\Helpers', (string) $result2); + } + + public function testNamespaceNameMaybeFromStringWithImportable() : void + { + $fullyQualified = new FullyQualified('App\\Models\\User'); + $className = new ClassName('Helper'); + + // Test with FullyQualified + $result1 = NamespaceName::maybeFromString($fullyQualified); + self::assertSame('App\\Models\\User', $result1->namespace); + + // Test with ClassName + $result2 = NamespaceName::maybeFromString($className); + self::assertSame('Helper', $result2->namespace); + } + + public function testCodeGeneratorImportWithImportableTypes() : void + { + $generator = new CodeGenerator('App\\Controllers'); + + $className = new ClassName('User'); + $fullyQualified = new FullyQualified('App\\Models\\Product'); + $functionName = new FunctionName('sprintf'); + $namespaceName = new NamespaceName('App\\Services'); + + // Test importing different Importable types + $alias1 = $generator->import($className); + $alias2 = $generator->import($fullyQualified); + $alias3 = $generator->import($functionName); + $alias4 = $generator->import($namespaceName); + + self::assertSame('User', $alias1); + self::assertSame('Product', $alias2); + self::assertSame('sprintf', $alias3); + self::assertSame('Services', $alias4); + } + + public function testCodeGeneratorImportByParentWithImportableTypes() : void + { + $generator = new CodeGenerator('App\\Controllers'); + + $fullyQualified = new FullyQualified('App\\Models\\User'); + $className = new ClassName('Helper'); + + // Test importByParent with different Importable types + $result1 = $generator->importByParent($fullyQualified); + $result2 = $generator->importByParent($className); + + self::assertSame('Models\\User', $result1); + self::assertSame('Helper', $result2); + } + + public function testAliasConstructorWithImportableTypes() : void + { + $fullyQualified = new FullyQualified('App\\Models\\User'); + $functionName = new FunctionName('App\\Helpers\\format'); + $namespaceName = new NamespaceName('App\\Services'); + $className = new ClassName('Helper'); + + // Test creating aliases with different Importable types + $alias1 = new Alias('UserModel', $fullyQualified); + $alias2 = new Alias('format', $functionName); + $alias3 = new Alias('Services', $namespaceName); + $alias4 = new Alias('HelperClass', $className); + + self::assertSame('UserModel', $alias1->alias); + self::assertSame($fullyQualified, $alias1->target); + + self::assertSame('format', $alias2->alias); + self::assertSame($functionName, $alias2->target); + + self::assertSame('Services', $alias3->alias); + self::assertSame($namespaceName, $alias3->target); + + self::assertSame('HelperClass', $alias4->alias); + self::assertSame($className, $alias4->target); + } +}