fix(permissions): resolve module policy paths via composer PSR-4 prefixes
Modules do not share a single layout: some map their namespace to an app/ subdirectory while others fall back to the Modules\ => modules root. The previous modules/<class> hardcode dropped the app/ segment and wrote policies to the module root. Resolve the file path through the registered Composer PSR-4 prefixes, honouring the longest match.
This commit is contained in:
parent
0827b15345
commit
49d348aed8
@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\PermissionRegistry;
|
||||
use Composer\Autoload\ClassLoader;
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
@ -72,12 +73,45 @@ class GeneratePolicies extends Command
|
||||
}
|
||||
|
||||
if (str_starts_with($class, 'Modules\\')) {
|
||||
return base_path('modules/'.str_replace('\\', '/', Str::after($class, 'Modules\\')).'.php');
|
||||
return $this->modulePathForClass($class);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a module class to its file path via the registered Composer
|
||||
* PSR-4 prefixes, honouring the most specific (longest) match. Modules do
|
||||
* not share a single layout: some map Modules\Foo\ to modules/Foo/app,
|
||||
* while others fall back to the Modules\ => modules root. Hardcoding
|
||||
* modules/<class> would drop the app/ segment for the former and write the
|
||||
* policy to the module root instead of app/Policies.
|
||||
*/
|
||||
protected function modulePathForClass(string $class): ?string
|
||||
{
|
||||
/** @var ClassLoader $loader */
|
||||
$loader = require base_path('vendor/autoload.php');
|
||||
|
||||
$bestPrefix = null;
|
||||
$bestDir = null;
|
||||
|
||||
foreach ($loader->getPrefixesPsr4() as $prefix => $directories) {
|
||||
if (str_starts_with($class, $prefix) && ($bestPrefix === null || strlen($prefix) > strlen($bestPrefix))) {
|
||||
$bestPrefix = $prefix;
|
||||
$bestDir = $directories[0];
|
||||
}
|
||||
}
|
||||
|
||||
if ($bestPrefix === null || $bestDir === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$dir = realpath($bestDir) ?: $bestDir;
|
||||
$relative = str_replace('\\', '/', Str::after($class, $bestPrefix));
|
||||
|
||||
return rtrim($dir, '/').'/'.$relative.'.php';
|
||||
}
|
||||
|
||||
protected function stub(string $policyClass, string $subject): string
|
||||
{
|
||||
$namespace = Str::beforeLast($policyClass, '\\');
|
||||
|
||||
@ -2,8 +2,21 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Console\Commands\GeneratePolicies;
|
||||
use Composer\Autoload\ClassLoader;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
/**
|
||||
* Invoke the protected modulePathForClass resolver on a fresh command.
|
||||
*/
|
||||
function resolveModulePath(string $class): ?string
|
||||
{
|
||||
$command = new GeneratePolicies();
|
||||
$method = (new ReflectionMethod($command, 'modulePathForClass'));
|
||||
|
||||
return $method->invoke($command, $class);
|
||||
}
|
||||
|
||||
it('generates a thin policy for a resource model that lacks one', function (): void {
|
||||
$path = app_path('Policies/Filament/FlightBundlePolicy.php');
|
||||
$original = File::exists($path) ? File::get($path) : null;
|
||||
@ -40,3 +53,20 @@ it('regenerates a valid stub with --force', function (): void {
|
||||
expect($content)->toContain('extends BasePolicy');
|
||||
expect($content)->toContain("\$subject = 'award'");
|
||||
});
|
||||
|
||||
it('resolves a module policy path via the most specific PSR-4 prefix', function (): void {
|
||||
/** @var ClassLoader $loader */
|
||||
$loader = require base_path('vendor/autoload.php');
|
||||
|
||||
// A module that maps its namespace to an app/ subdirectory must keep the
|
||||
// app/ segment; the generic Modules\ => modules fallback must not win.
|
||||
$loader->addPsr4('Modules\\Foo\\', base_path('modules/Foo/app'));
|
||||
|
||||
try {
|
||||
$path = resolveModulePath('Modules\\Foo\\Policies\\Filament\\BarPolicy');
|
||||
|
||||
expect($path)->toEndWith('modules/Foo/app/Policies/Filament/BarPolicy.php');
|
||||
} finally {
|
||||
$loader->setPsr4('Modules\\Foo\\', []);
|
||||
}
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user