Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 23 additions & 9 deletions src/Support/ModuleRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ class ModuleRegistry
{
protected ?Collection $modules = null;

protected ?array $namespace_index = null;

public function __construct(
protected string $modules_path,
protected Closure $modules_loader,
Expand Down Expand Up @@ -44,18 +46,16 @@ public function moduleForPathOrFail(string $path): ModuleConfig

public function moduleForClass(string $fqcn): ?ModuleConfig
{
return $this->modules()->first(function(ModuleConfig $module) use ($fqcn) {
foreach ($module->namespaces as $namespace) {
if (Str::startsWith($fqcn, $namespace)) {
return true;
}
foreach ($this->namespaceIndex() as $namespace => $module) {
if (Str::startsWith($fqcn, $namespace)) {
return $module;
}
return false;
});
}

return null;
}

/** @return Collection<int, \InterNACHI\Modular\Support\ModuleConfig> */
/** @return Collection<int, ModuleConfig> */
public function modules(): Collection
{
return $this->modules ??= call_user_func($this->modules_loader);
Expand All @@ -64,9 +64,23 @@ public function modules(): Collection
public function reload(): Collection
{
$this->modules = null;
$this->namespace_index = null;

return $this->modules();
}

/**
* Namespaces sorted descending so the most-specific prefix wins on first match.
*
* @return array<string, ModuleConfig>
*/
protected function namespaceIndex(): array
{
return $this->namespace_index ??= $this->modules()
->flatMap(fn(ModuleConfig $module) => $module->namespaces->mapWithKeys(fn(string $namespace) => [$namespace => $module]))
->sortKeysDesc()
->all();
}

protected function extractModuleNameFromPath(string $path): string
{
Expand Down
62 changes: 61 additions & 1 deletion tests/ModuleRegistryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,74 @@

namespace InterNACHI\Modular\Tests;

use Illuminate\Support\Collection;
use InterNACHI\Modular\Support\ModuleConfig;
use InterNACHI\Modular\Support\ModuleRegistry;
use InterNACHI\Modular\Tests\Concerns\WritesToAppFilesystem;

class ModuleRegistryTest extends TestCase
{
use WritesToAppFilesystem;


public function test_module_for_class_prefers_longest_matching_namespace(): void
{
$general = new ModuleConfig('general', '/tmp/general', new Collection([
'/tmp/general/src' => 'App\\General\\',
'/tmp/general/tests' => 'App\\General\\Tests',
]));

$specific = new ModuleConfig('specific', '/tmp/specific', new Collection([
'/tmp/general-specific/src' => 'App\\General\\Specific\\',
'/tmp/general-specific/tests' => 'App\\General\\Specific\\Tests',
]));

$loader_orders = [
[$general, $specific],
[$specific, $general],
];

$specific_model = 'App\\General\\Specific\\Models\\Thing';
$specific_test = 'App\\General\\Specific\\Tests\\ThingTest';
$general_model = 'App\\General\\Models\\Thing';
$general_test = 'App\\General\\Tests\\ThingTest';

foreach ($loader_orders as $modules) {
$registry = new ModuleRegistry(
modules_path: '/tmp',
modules_loader: fn() => Collection::make($modules)->keyBy('name'),
);

$this->assertEquals(
$specific,
$registry->moduleForClass($specific_model),
'Expected the more-specific module to win regardless of registration order (primary namespace)',
);

$this->assertEquals(
$specific,
$registry->moduleForClass($specific_test),
'Expected the more-specific module to win regardless of registration order (secondary namespace)',
);

$this->assertEquals(
$general,
$registry->moduleForClass($general_model),
'Expected the general module to return when a more-specific namespace does not match (primary namespace)',
);

$this->assertEquals(
$general,
$registry->moduleForClass($general_test),
'Expected the general module to return when a more-specific namespace does not match (secondary namespace)',
);

$this->assertNull(
$registry->moduleForClass('Unrelated\\Thing'),
'Expected unrelated class not to match any module regardless of order'
);
}
}

public function test_it_resolves_modules(): void
{
$this->makeModule('test-module');
Expand Down
Loading