diff --git a/src/PSR4/PSR4Finder.php b/src/PSR4/PSR4Finder.php index 811ecb7..447f749 100644 --- a/src/PSR4/PSR4Finder.php +++ b/src/PSR4/PSR4Finder.php @@ -20,16 +20,7 @@ class PSR4Finder implements FinderInterface */ public function findClasses($namespace) { - $composerNamespaces = $this->factory->getPSR4Namespaces(); - - /** @var PSR4Namespace $bestNamespace */ - $bestNamespace = array_reduce($composerNamespaces, function($carry, PSR4Namespace $potentialNamespace) use ($namespace) { - if ($potentialNamespace->matches($namespace)) { - return $potentialNamespace; - } else { - return $carry; - } - }, null); + $bestNamespace = $this->findBestPSR4Namespace($namespace); if ($bestNamespace instanceof PSR4Namespace) { return $bestNamespace->findClasses($namespace); @@ -40,4 +31,31 @@ class PSR4Finder implements FinderInterface )); } } + + /** + * @param $namespace + * @return PSR4Namespace + */ + private function findBestPSR4Namespace($namespace) + { + $composerNamespaces = $this->factory->getPSR4Namespaces(); + + $carry = new \stdClass(); + $carry->highestMatchingSegments = 0; + $carry->bestNamespace = null; + + /** @var PSR4Namespace $bestNamespace */ + $bestNamespace = array_reduce($composerNamespaces, function ($carry, PSR4Namespace $potentialNamespace) use ($namespace) { + $matchingSegments = $potentialNamespace->countMatchingNamespaceSegments($namespace); + + if ($matchingSegments > $carry->highestMatchingSegments) { + $carry->highestMatchingSegments = $matchingSegments; + $carry->bestNamespace = $potentialNamespace; + } + + return $carry; + }, $carry); + + return $bestNamespace->bestNamespace; + } } diff --git a/src/PSR4/PSR4Namespace.php b/src/PSR4/PSR4Namespace.php index 8ce0041..a12503e 100644 --- a/src/PSR4/PSR4Namespace.php +++ b/src/PSR4/PSR4Namespace.php @@ -14,7 +14,28 @@ class PSR4Namespace $this->directories = $directories; } - public function matches($namespace) + /** + * Determines how many namespace segments match the internal namespace. This is useful because multiple namespaces + * may technically match a registered namespace root, but one of the matches may be a better match. Namespaces that + * match, but are not _the best_ match are incorrect matches. TestApp1\\ is **not** the best match when searching for + * namespace TestApp1\\Multi\\Foo if TestApp1\\Multi was explicitly registered. + * + * PSR4Namespace $a; + * $a->namespace = "TestApp1\\"; + * $a->countMatchingNamespaceSegments("TestApp1\\Multi") -> 1, TestApp1 matches. + * + * PSR4Namespace $b; + * $b->namespace = "TestApp1\\Multi"; + * $b->countMatchingNamespaceSegments("TestApp1\\Multi") -> 2, TestApp1\\Multi matches + * + * PSR4Namespace $c; + * $c->namespace = "HaydenPierce\\Foo\\Bar"; + * $c->countMatchingNamespaceSegments("TestApp1\\Multi") -> 0, No matches. + * + * @param $namespace + * @return int + */ + public function countMatchingNamespaceSegments($namespace) { $namespaceFragments = explode('\\', $namespace); $undefinedNamespaceFragments = []; @@ -23,13 +44,13 @@ class PSR4Namespace $possibleNamespace = implode('\\', $namespaceFragments) . '\\'; if($this->namespace === $possibleNamespace){ - return true; + return count(explode('\\', $possibleNamespace)); } array_unshift($undefinedNamespaceFragments, array_pop($namespaceFragments)); } - return false; + return 0; } public function findClasses($namespace)