diff --git a/src/LayerResolver/Resolvers/NamespaceLayerResolver.php b/src/LayerResolver/Resolvers/NamespaceLayerResolver.php index 4ec15878..5b8d3506 100644 --- a/src/LayerResolver/Resolvers/NamespaceLayerResolver.php +++ b/src/LayerResolver/Resolvers/NamespaceLayerResolver.php @@ -9,6 +9,7 @@ use function str_starts_with; use function strlen; +use function usort; /** * Resolves a layer by matching the file path against registered layer paths. @@ -22,12 +23,21 @@ /** * Layer paths stored with a trailing '/' so a single str_starts_with() * against the file path (also suffixed with '/') covers both exact and - * descendant matches. + * descendant matches. Kept in declaration order for resolveAll(). * * @var array> */ private array $normalisedLayers; + /** + * The same paths flattened and sorted longest first, so resolve() can + * return on the first match: it is always the most specific layer. + * Equal lengths keep declaration order. + * + * @var list [layerPath, layerName] + */ + private array $layerPathsLongestFirst; + /** * @param array> $layers Map of layer name → path prefixes */ @@ -35,40 +45,35 @@ public function __construct( array $layers, string $basePath, ) { - $normalisedLayers = []; + $normalisedLayers = []; + $layerPathsLongestFirst = []; foreach ($layers as $layerName => $layerPaths) { foreach ((array) $layerPaths as $layerPath) { - $normalisedLayers[$layerName][] = Path::normalise( - Path::resolve($layerPath, $basePath), - canonicalise: true - ) . '/'; + $normalisedPath = Path::normalise(Path::resolve($layerPath, $basePath), canonicalise: true) . '/'; + + $normalisedLayers[$layerName][] = $normalisedPath; + $layerPathsLongestFirst[] = [$normalisedPath, $layerName]; } } - $this->normalisedLayers = $normalisedLayers; + usort($layerPathsLongestFirst, static fn (array $a, array $b): int => strlen($b[0]) <=> strlen($a[0])); + + $this->normalisedLayers = $normalisedLayers; + $this->layerPathsLongestFirst = $layerPathsLongestFirst; } public function resolve(string $className, string $filePath): ?string { $pathWithSlash = Path::normalise($filePath, canonicalise: true) . '/'; - $matchedLayer = null; - $matchedLength = -1; - foreach ($this->normalisedLayers as $layerName => $layerPaths) { - foreach ($layerPaths as $layerPath) { - if (str_starts_with($pathWithSlash, $layerPath)) { - $length = strlen($layerPath); - - if ($length > $matchedLength) { - $matchedLayer = $layerName; - $matchedLength = $length; - } - } + foreach ($this->layerPathsLongestFirst as [$layerPath, $layerName]) { + if (str_starts_with($pathWithSlash, $layerPath)) { + return $layerName; } } - return $matchedLayer; + return null; } /**