Skip to content

Commit 9ce4df9

Browse files
skylerkatzinxilpro
andauthored
Sort module namespaces in decending order (#138)
* sort module namespaces in decending order * php-cs-fixer issues * Revert some unnecessary whitespace changes * Cover a few more test cases --------- Co-authored-by: Chris Morrell <inxilpro@users.noreply.github.com>
1 parent 939a341 commit 9ce4df9

2 files changed

Lines changed: 84 additions & 10 deletions

File tree

src/Support/ModuleRegistry.php

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ class ModuleRegistry
1111
{
1212
protected ?Collection $modules = null;
1313

14+
protected ?array $namespace_index = null;
15+
1416
public function __construct(
1517
protected string $modules_path,
1618
protected Closure $modules_loader,
@@ -44,18 +46,16 @@ public function moduleForPathOrFail(string $path): ModuleConfig
4446

4547
public function moduleForClass(string $fqcn): ?ModuleConfig
4648
{
47-
return $this->modules()->first(function(ModuleConfig $module) use ($fqcn) {
48-
foreach ($module->namespaces as $namespace) {
49-
if (Str::startsWith($fqcn, $namespace)) {
50-
return true;
51-
}
49+
foreach ($this->namespaceIndex() as $namespace => $module) {
50+
if (Str::startsWith($fqcn, $namespace)) {
51+
return $module;
5252
}
53-
54-
return false;
55-
});
53+
}
54+
55+
return null;
5656
}
5757

58-
/** @return Collection<int, \InterNACHI\Modular\Support\ModuleConfig> */
58+
/** @return Collection<int, ModuleConfig> */
5959
public function modules(): Collection
6060
{
6161
return $this->modules ??= call_user_func($this->modules_loader);
@@ -64,9 +64,23 @@ public function modules(): Collection
6464
public function reload(): Collection
6565
{
6666
$this->modules = null;
67+
$this->namespace_index = null;
6768

6869
return $this->modules();
6970
}
71+
72+
/**
73+
* Namespaces sorted descending so the most-specific prefix wins on first match.
74+
*
75+
* @return array<string, ModuleConfig>
76+
*/
77+
protected function namespaceIndex(): array
78+
{
79+
return $this->namespace_index ??= $this->modules()
80+
->flatMap(fn(ModuleConfig $module) => $module->namespaces->mapWithKeys(fn(string $namespace) => [$namespace => $module]))
81+
->sortKeysDesc()
82+
->all();
83+
}
7084

7185
protected function extractModuleNameFromPath(string $path): string
7286
{

tests/ModuleRegistryTest.php

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,74 @@
22

33
namespace InterNACHI\Modular\Tests;
44

5+
use Illuminate\Support\Collection;
56
use InterNACHI\Modular\Support\ModuleConfig;
67
use InterNACHI\Modular\Support\ModuleRegistry;
78
use InterNACHI\Modular\Tests\Concerns\WritesToAppFilesystem;
89

910
class ModuleRegistryTest extends TestCase
1011
{
1112
use WritesToAppFilesystem;
12-
13+
14+
public function test_module_for_class_prefers_longest_matching_namespace(): void
15+
{
16+
$general = new ModuleConfig('general', '/tmp/general', new Collection([
17+
'/tmp/general/src' => 'App\\General\\',
18+
'/tmp/general/tests' => 'App\\General\\Tests',
19+
]));
20+
21+
$specific = new ModuleConfig('specific', '/tmp/specific', new Collection([
22+
'/tmp/general-specific/src' => 'App\\General\\Specific\\',
23+
'/tmp/general-specific/tests' => 'App\\General\\Specific\\Tests',
24+
]));
25+
26+
$loader_orders = [
27+
[$general, $specific],
28+
[$specific, $general],
29+
];
30+
31+
$specific_model = 'App\\General\\Specific\\Models\\Thing';
32+
$specific_test = 'App\\General\\Specific\\Tests\\ThingTest';
33+
$general_model = 'App\\General\\Models\\Thing';
34+
$general_test = 'App\\General\\Tests\\ThingTest';
35+
36+
foreach ($loader_orders as $modules) {
37+
$registry = new ModuleRegistry(
38+
modules_path: '/tmp',
39+
modules_loader: fn() => Collection::make($modules)->keyBy('name'),
40+
);
41+
42+
$this->assertEquals(
43+
$specific,
44+
$registry->moduleForClass($specific_model),
45+
'Expected the more-specific module to win regardless of registration order (primary namespace)',
46+
);
47+
48+
$this->assertEquals(
49+
$specific,
50+
$registry->moduleForClass($specific_test),
51+
'Expected the more-specific module to win regardless of registration order (secondary namespace)',
52+
);
53+
54+
$this->assertEquals(
55+
$general,
56+
$registry->moduleForClass($general_model),
57+
'Expected the general module to return when a more-specific namespace does not match (primary namespace)',
58+
);
59+
60+
$this->assertEquals(
61+
$general,
62+
$registry->moduleForClass($general_test),
63+
'Expected the general module to return when a more-specific namespace does not match (secondary namespace)',
64+
);
65+
66+
$this->assertNull(
67+
$registry->moduleForClass('Unrelated\\Thing'),
68+
'Expected unrelated class not to match any module regardless of order'
69+
);
70+
}
71+
}
72+
1373
public function test_it_resolves_modules(): void
1474
{
1575
$this->makeModule('test-module');

0 commit comments

Comments
 (0)