diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b671cc..534d91f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +* Improved namespace mapping algorithm +* Upgraded namespace aliases to mount at any level +* Enable recursive aliases +* Added priority to aliases + ## v0.3.3 (2024-04-29) * Fixed Veneer stubs in gitattributes diff --git a/src/Handler.php b/src/Handler.php index 914e268..031bd6f 100644 --- a/src/Handler.php +++ b/src/Handler.php @@ -177,9 +177,10 @@ public function map( */ public function alias( string $interface, - string $alias + string $alias, + int $priority = 0 ): void { - $this->getNamespaceMap()->addAlias($interface, $alias); + $this->getNamespaceMap()->addAlias($interface, $alias, $priority); } diff --git a/src/NamespaceList.php b/src/NamespaceList.php index 24174a3..242a8c3 100644 --- a/src/NamespaceList.php +++ b/src/NamespaceList.php @@ -9,9 +9,9 @@ namespace DecodeLabs\Archetype; -use ArrayIterator; use Countable; use DecodeLabs\Glitch\Dumpable; +use Generator; use IteratorAggregate; /** @@ -88,15 +88,17 @@ public function import( /** * Get iterator * - * @return ArrayIterator + * @return Generator */ - public function getIterator(): ArrayIterator + public function getIterator(): Generator { uasort($this->namespaces, function ($a, $b) { return $a <=> $b; }); - return new ArrayIterator(array_reverse(array_keys($this->namespaces))); + foreach (array_reverse($this->namespaces) as $namespace => $priority) { + yield $priority => $namespace; + } } /** diff --git a/src/NamespaceMap.php b/src/NamespaceMap.php index 13972cf..4a930c6 100644 --- a/src/NamespaceMap.php +++ b/src/NamespaceMap.php @@ -17,7 +17,7 @@ class NamespaceMap protected array $namespaces = []; /** - * @var array> + * @var array */ protected array $aliases = []; @@ -69,16 +69,20 @@ public function remove( /** * Add alias + * + * @return $this */ public function addAlias( string $interface, - string $alias - ): void { + string $alias, + int $priority = 0 + ): static { if (!isset($this->aliases[$interface])) { - $this->aliases[$interface] = []; + $this->aliases[$interface] = new NamespaceList(); } - $this->aliases[$interface][$alias] = $alias; + $this->aliases[$interface]->add($alias, $priority); + return $this; } /** @@ -88,51 +92,97 @@ public function hasAlias( string $interface, string $alias ): bool { - return isset($this->aliases[$interface][$alias]); + return + isset($this->aliases[$interface]) && + $this->aliases[$interface]->has($alias); } /** * Remove alias + * + * @return $this */ public function removeAlias( string $interface, string $alias - ): void { - unset($this->aliases[$interface][$alias]); + ): static { + if (isset($this->aliases[$interface])) { + $this->aliases[$interface]->remove($alias); + } + + return $this; } /** * Map namespace */ public function map( - string $namespace + string $namespace, + bool $includeRoot = true ): NamespaceList { $output = new NamespaceList(); - $this->applyMap($namespace, $output); - - foreach ($this->aliases[$namespace] ?? [] as $alias) { - $this->applyMap($alias, $output); - } - + $this->applyMap($namespace, $output, -1, $includeRoot); return $output; } protected function applyMap( string $namespace, - NamespaceList $namespaces + NamespaceList $namespaces, + int $priority = 0, + bool $includeRoot = true ): NamespaceList { + // Import root + if ($includeRoot) { + $namespaces->add($namespace, $priority); + } + $parts = explode('\\', $namespace); $inner = []; - $namespaces->add($namespace, -1); while (!empty($parts)) { $root = implode('\\', $parts); + // Import root maps if (isset($this->namespaces[$root])) { $mapTo = empty($inner) ? null : implode('\\', $inner); $namespaces->import($this->namespaces[$root], $mapTo, $namespace); } + + // Aliases + $wild = false; + $key = null; + + if ( + isset($this->aliases[$root . '\\*']) && + // Wildcards only make sense for one level + count($inner) <= 1 + ) { + $key = $root . '\\*'; + $wild = true; + } elseif (isset($this->aliases[$root])) { + $key = $root; + } + + if ($key !== null) { + foreach ($this->aliases[$key] ?? [] as $priority => $alias) { + $append = $inner; + + if ($wild) { + array_pop($append); + } + + if (!empty($append)) { + $alias .= '\\' . implode('\\', $append); + } + + $this->applyMap($alias, $namespaces, $priority); + } + + return $namespaces; + } + + // Shift parts array_unshift($inner, array_pop($parts)); } diff --git a/stubs/DecodeLabs/Archetype.php b/stubs/DecodeLabs/Archetype.php index 24e34ac..9ff1570 100644 --- a/stubs/DecodeLabs/Archetype.php +++ b/stubs/DecodeLabs/Archetype.php @@ -37,7 +37,7 @@ public static function registerCustomNormalizer(string $interface, callable $nor return static::$instance->registerCustomNormalizer(...func_get_args()); } public static function map(string $root, string $namespace, int $priority = 0): void {} - public static function alias(string $interface, string $alias): void {} + public static function alias(string $interface, string $alias, int $priority = 0): void {} public static function resolve(string $interface, array|string|null $names = NULL, callable|string|null $default = NULL): string { return static::$instance->resolve(...func_get_args()); }