From 1d5624d59cb82882f3b2b64078bc1af94a08aeef Mon Sep 17 00:00:00 2001 From: Gautier DELEGLISE Date: Sun, 6 Jul 2025 17:20:33 +0200 Subject: [PATCH 1/5] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20rewrote=20control=20re?= =?UTF-8?q?gistering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Access.php | 148 ++++++++++++++++++ src/AccessServiceProvider.php | 14 ++ src/Console/ControlMakeCommand.php | 82 ++++++++-- src/Console/stubs/control.stub | 6 + src/Controls/Control.php | 77 ++------- src/Controls/HasControl.php | 16 +- src/Policies/ControlledPolicy.php | 5 +- .../Support/Access/Controls/ModelControl.php | 2 + tests/TestCase.php | 7 + tests/Unit/Console/MakeCommandsTest.php | 44 ++++-- 10 files changed, 297 insertions(+), 104 deletions(-) create mode 100644 src/Access.php diff --git a/src/Access.php b/src/Access.php new file mode 100644 index 0000000..a80e03b --- /dev/null +++ b/src/Access.php @@ -0,0 +1,148 @@ +addControl($control); + } + + return $this; + } + + /** + * Add a control to access. + * + * @param Control $control + * + * @return Access + */ + public function addControl(Control $control): self + { + static::$controls[class_basename($control)] = $control; + + return $this; + } + + /** + * Get the control instance for the given model + * + * @param Model|class-string $model + * @return Control|null + */ + public static function controlForModel(Model|string $model): ?Control + { + if (!is_string($model)) { + $model = $model::class; + } + + foreach (static::$controls as $control) { + if ($control->isModel($model)) { + return $control; + } + } + + return null; + } + + /** + * Discover controls for a given path + * + * @var string[] $paths + */ + public function discoverControls(array $paths): self + { + (new Collection($paths)) + ->flatMap(function ($directory) { + return glob($directory, GLOB_ONLYDIR); + }) + ->reject(function ($directory) { + return ! is_dir($directory); + }) + ->each(function ($directory) { + $controls = Finder::create()->files()->in($directory); + + foreach ($controls as $control) { + try { + $control = new ReflectionClass( + static::classFromFile($control, base_path()) + ); + } catch (ReflectionException) { + continue; + } + + if (! $control->isInstantiable()) { + continue; + } + + $this->addControl($control->newInstance()); + } + }); + + return $this; + } + + /** + * Get the control directories that should be used to discover controls. + * + * @return array + */ + public function discoverControlsWithin(): array + { + return static::$controlDiscoveryPaths ?? [ + app()->path('Access/Controls'), + ]; + } + + /** + * Extract the class name from the given file path. + * + * @param SplFileInfo $file + * @param string $basePath + * @return string + */ + protected function classFromFile(SplFileInfo $file, string $basePath) + { + + $class = trim(Str::replaceFirst($basePath, '', $file->getRealPath()), DIRECTORY_SEPARATOR); + + return ucfirst(Str::camel(str_replace( + [DIRECTORY_SEPARATOR, ucfirst(basename(app()->path())).'\\'], + ['\\', app()->getNamespace()], + ucfirst(Str::replaceLast('.php', '', $class)) + ))); + } +} \ No newline at end of file diff --git a/src/AccessServiceProvider.php b/src/AccessServiceProvider.php index 323b40e..6a7b79f 100644 --- a/src/AccessServiceProvider.php +++ b/src/AccessServiceProvider.php @@ -30,6 +30,8 @@ public function register() __DIR__.'/../config/access-control.php', 'access-control' ); + + $this->registerAccessControls(); } /** @@ -92,6 +94,18 @@ protected function registerStubs() }); } + /** + * Register the default paths controls. + * + * @return void + */ + private function registerAccessControls(): void + { + $access = new Access(); + + $access->discoverControls($access->discoverControlsWithin()); + } + /** * Register the package's publishable resources. * diff --git a/src/Console/ControlMakeCommand.php b/src/Console/ControlMakeCommand.php index f49282f..52d38b6 100644 --- a/src/Console/ControlMakeCommand.php +++ b/src/Console/ControlMakeCommand.php @@ -4,13 +4,16 @@ use Illuminate\Console\GeneratorCommand; use Illuminate\Support\Collection; +use InvalidArgumentException; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Finder\Finder; +use function Laravel\Prompts\confirm; use function Laravel\Prompts\multiselect; +use function Laravel\Prompts\select; #[AsCommand(name: 'make:control')] class ControlMakeCommand extends GeneratorCommand @@ -41,7 +44,7 @@ class ControlMakeCommand extends GeneratorCommand * * @return string */ - protected function getStub() + protected function getStub(): string { return $this->resolveStubPath('/stubs/control.stub'); } @@ -53,7 +56,7 @@ protected function getStub() * * @return string */ - protected function resolveStubPath($stub) + protected function resolveStubPath($stub): string { return file_exists($customPath = $this->laravel->basePath(trim($stub, '/'))) ? $customPath @@ -67,7 +70,7 @@ protected function resolveStubPath($stub) * * @return string */ - protected function getDefaultNamespace($rootNamespace) + protected function getDefaultNamespace($rootNamespace): string { return $rootNamespace.'\Access\Controls'; } @@ -79,7 +82,7 @@ protected function getDefaultNamespace($rootNamespace) * * @return string */ - protected function buildClass($name) + protected function buildClass($name): string { $rootNamespace = $this->rootNamespace(); $controlNamespace = $this->getNamespace($name); @@ -90,6 +93,10 @@ protected function buildClass($name) $replace = $this->buildPerimetersReplacements($replace, $this->option('perimeters')); + if ($this->option('model')) { + $replace = $this->buildModelReplacements($replace); + } + if ($baseControlExists) { $replace['use Lomkit\Access\Controls\Control;'] = ''; } else { @@ -103,6 +110,29 @@ protected function buildClass($name) ); } + /** + * Build the model replacement values. + * + * @param array $replace + * @return array + */ + protected function buildModelReplacements(array $replace): array + { + $modelClass = $this->parseModel($this->option('model')); + + return array_merge($replace, [ + 'DummyFullModelClass' => $modelClass, + '{{ namespacedModel }}' => $modelClass, + '{{namespacedModel}}' => $modelClass, + 'DummyModelClass' => class_basename($modelClass), + '{{ model }}' => class_basename($modelClass), + '{{model}}' => class_basename($modelClass), + 'DummyModelVariable' => lcfirst(class_basename($modelClass)), + '{{ modelVariable }}' => lcfirst(class_basename($modelClass)), + '{{modelVariable}}' => lcfirst(class_basename($modelClass)), + ]); + } + /** * Build the model replacement values. * @@ -148,6 +178,7 @@ protected function getOptions() { return [ ['perimeters', 'p', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'The perimeters that the control relies on'], + ['model', 'm', InputOption::VALUE_REQUIRED, 'The model the control relies on'], ]; } @@ -164,13 +195,27 @@ protected function afterPromptingForMissingArguments(InputInterface $input, Outp if ($this->didReceiveOptions($input)) { return; } - $perimeters = multiselect( - 'What perimeters should this control apply to? (Optional)', - $this->possiblePerimeters(), - ); - if ($perimeters) { - $input->setOption('perimeters', $perimeters); + if (!empty($this->possiblePerimeters())) { + $perimeters = multiselect( + 'What perimeters should this control apply to? (Optional)', + $this->possiblePerimeters(), + ); + + if ($perimeters) { + $input->setOption('perimeters', $perimeters); + } + } + + if (!empty($this->possibleModels())) { + $model = select( + 'What model should this control apply to? (Optional)', + $this->possibleModels(), + ); + + if ($model) { + $input->setOption('model', $model); + } } } @@ -189,4 +234,21 @@ protected function possiblePerimeters() ->values() ->all(); } + + /** + * Get the fully-qualified model class name. + * + * @param string $model + * @return string + * + * @throws \InvalidArgumentException + */ + protected function parseModel(string $model): string + { + if (preg_match('([^A-Za-z0-9_/\\\\])', $model)) { + throw new InvalidArgumentException('Model name contains invalid characters.'); + } + + return $this->qualifyModel($model); + } } diff --git a/src/Console/stubs/control.stub b/src/Console/stubs/control.stub index 7cfa2bf..5ace447 100644 --- a/src/Console/stubs/control.stub +++ b/src/Console/stubs/control.stub @@ -9,6 +9,12 @@ use Illuminate\Database\Eloquent\Builder; class {{ class }} extends Control { + /** + * The model the control refers to. + * @var class-string + */ + protected string $model = {{ namespacedModel }}; + /** * Retrieve the list of perimeter definitions for the current control. * diff --git a/src/Controls/Control.php b/src/Controls/Control.php index cf37252..8652d72 100644 --- a/src/Controls/Control.php +++ b/src/Controls/Control.php @@ -12,20 +12,22 @@ class Control { - // @TODO: change readme image /** - * The control name resolver. - * - * @var callable + * The model the control refers to. + * @var class-string */ - protected static $controlNameResolver; + protected string $model; /** - * The default namespace where control reside. + * Does the given model match with the current one * - * @var string + * @param class-string $model + * @return bool */ - public static $namespace = 'App\\Access\\Controls\\'; + public function isModel(string $model): bool + { + return $model === $this->model; + } /** * Retrieve the list of perimeter definitions for the current control. @@ -191,34 +193,6 @@ protected function noResultScoutQuery(\Laravel\Scout\Builder $query): \Laravel\S return $query->where('__NOT_A_VALID_FIELD__', 0); } - /** - * Specify the callback that should be invoked to guess control names. - * - * @param callable(class-string<\Illuminate\Database\Eloquent\Model>): class-string<\Lomkit\Access\Controls\Control> $callback - * - * @return void - */ - public static function guessControlNamesUsing(callable $callback): void - { - static::$controlNameResolver = $callback; - } - - /** - * Get a new control instance for the given model name. - * - * @template TClass of \Illuminate\Database\Eloquent\Model - * - * @param class-string $modelName - * - * @return \Lomkit\Access\Controls\Control - */ - public static function controlForModel(string $modelName): self - { - $control = static::resolveControlName($modelName); - - return $control::new(); - } - /** * Creates a new instance of the control. * @@ -229,37 +203,6 @@ public static function new(): self return new static(); } - /** - * Resolve the control name for a given model. - * - * @template TClass of \Illuminate\Database\Eloquent\Model - * - * @param class-string $modelName The fully qualified model class name. - * - * @return class-string<\Lomkit\Access\Controls\Control> The fully qualified control class name corresponding to the model. - */ - public static function resolveControlName(string $modelName): string - { - // @TODO: The auto guess here is strange, we specify the models / controls everywhere, is there a better way of doing this ? (In policies guess the model as Laravel is doing ?) - // @TODO: Discussed with Lucas G - - if (property_exists($modelName, 'control')) { - return $modelName::control()::class; - } - - $resolver = static::$controlNameResolver ?? function (string $modelName) { - $appNamespace = static::appNamespace(); - - $modelName = Str::startsWith($modelName, $appNamespace.'Models\\') - ? Str::after($modelName, $appNamespace.'Models\\') - : Str::after($modelName, $appNamespace); - - return static::$namespace.$modelName.'Control'; - }; - - return $resolver($modelName); - } - /** * Retrieves the application's namespace. * diff --git a/src/Controls/HasControl.php b/src/Controls/HasControl.php index ee7d64b..fba73a2 100644 --- a/src/Controls/HasControl.php +++ b/src/Controls/HasControl.php @@ -2,6 +2,8 @@ namespace Lomkit\Access\Controls; +use Lomkit\Access\Access; + trait HasControl { /** @@ -14,18 +16,6 @@ public static function bootHasControl() static::addGlobalScope(new HasControlScope()); } - /** - * Retrieves a control instance for the model. - * - * @return Control The control instance for the model. - */ - public static function control() - { - $control = static::newControl() ?? Control::controlForModel(static::class); - - return $control; - } - /** * Attempts to create a new control instance. * @@ -33,6 +23,6 @@ public static function control() */ protected static function newControl(): ?Control { - return property_exists(static::class, 'control') ? static::$control::new() : null; + return Access::controlForModel(static::class); } } diff --git a/src/Policies/ControlledPolicy.php b/src/Policies/ControlledPolicy.php index c5c4556..015e510 100644 --- a/src/Policies/ControlledPolicy.php +++ b/src/Policies/ControlledPolicy.php @@ -3,6 +3,7 @@ namespace Lomkit\Access\Policies; use Illuminate\Database\Eloquent\Model; +use Lomkit\Access\Access; use Lomkit\Access\Controls\Control; class ControlledPolicy @@ -14,7 +15,7 @@ class ControlledPolicy * * @var string */ - protected string $model = ''; + protected string $model; /** * Returns the fully qualified model class name associated with this policy. @@ -33,7 +34,7 @@ protected function getModel(): string */ protected function getControl(): Control { - return Control::controlForModel($this->getModel()); + return Access::controlForModel($this->model); } /** diff --git a/tests/Support/Access/Controls/ModelControl.php b/tests/Support/Access/Controls/ModelControl.php index ff48ff2..cdf40ee 100644 --- a/tests/Support/Access/Controls/ModelControl.php +++ b/tests/Support/Access/Controls/ModelControl.php @@ -12,6 +12,8 @@ class ModelControl extends Control { + protected string $model = \Lomkit\Access\Tests\Support\Models\Model::class; + protected function perimeters(): array { return [ diff --git a/tests/TestCase.php b/tests/TestCase.php index 250dc20..6700641 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -6,7 +6,9 @@ use Illuminate\Contracts\Console\Kernel; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabaseState; +use Lomkit\Access\Access; use Lomkit\Access\AccessServiceProvider; +use Lomkit\Access\Tests\Support\Access\Controls\ModelControl; use Orchestra\Testbench\TestCase as BaseTestCase; class TestCase extends BaseTestCase @@ -16,6 +18,11 @@ class TestCase extends BaseTestCase protected function setUp(): void { parent::setUp(); + + (new Access) + ->addControls([ + new ModelControl + ]); } /** diff --git a/tests/Unit/Console/MakeCommandsTest.php b/tests/Unit/Console/MakeCommandsTest.php index 0d9e9d4..d8d7625 100644 --- a/tests/Unit/Console/MakeCommandsTest.php +++ b/tests/Unit/Console/MakeCommandsTest.php @@ -6,10 +6,21 @@ class MakeCommandsTest extends TestCase { - public function test_make_plain_perimeter_command() + protected function setUp(): void { + parent::setUp(); + + @unlink(app_path('Access/Perimeters/TestPerimeter.php')); + @unlink(app_path('Access/Controls/TestControl.php')); + @unlink(app_path('Access/Controls/Control.php')); @unlink(app_path('Access/Perimeters/TestPerimeter.php')); + @unlink(app_path('Access/Perimeters/SecondTestPerimeter.php')); + @unlink(app_path('Models/User.php')); + @unlink(app_path('Models/Post.php')); + } + public function test_make_plain_perimeter_command() + { $this ->artisan('make:perimeter', ['name' => 'TestPerimeter']) ->assertOk() @@ -23,8 +34,6 @@ public function test_make_plain_perimeter_command() public function test_make_overlay_perimeter_command() { - @unlink(app_path('Access/Perimeters/TestPerimeter.php')); - $this ->artisan('make:perimeter', ['name' => 'TestPerimeter', '--overlay' => true]) ->assertOk() @@ -38,8 +47,6 @@ public function test_make_overlay_perimeter_command() public function test_make_control_command() { - @unlink(app_path('Access/Controls/TestControl.php')); - $this ->artisan('make:control', ['name' => 'TestControl']) ->assertOk() @@ -53,9 +60,6 @@ public function test_make_control_command() public function test_make_control_with_base_control_command() { - @unlink(app_path('Access/Controls/TestControl.php')); - @unlink(app_path('Access/Controls/Control.php')); - file_put_contents(app_path('Access/Controls/Control.php'), ''); $this @@ -72,10 +76,6 @@ public function test_make_control_with_base_control_command() public function test_make_control_with_perimeters_command() { - @unlink(app_path('Access/Controls/TestControl.php')); - @unlink(app_path('Access/Perimeters/TestPerimeter.php')); - @unlink(app_path('Access/Perimeters/SecondTestPerimeter.php')); - file_put_contents(app_path('Access/Perimeters/TestPerimeter.php'), ''); file_put_contents(app_path('Access/Perimeters/SecondTestPerimeter.php'), ''); @@ -94,4 +94,24 @@ public function test_make_control_with_perimeters_command() unlink(app_path('Access/Perimeters/SecondTestPerimeter.php')); unlink(app_path('Access/Controls/TestControl.php')); } + + public function test_make_control_with_model_command() + { + file_put_contents(app_path('Models/User.php'), ''); + file_put_contents(app_path('Models/Post.php'), ''); + + $this + ->artisan('make:control') + ->expectsQuestion('What should the control be named?', 'TestControl') + ->expectsChoice('What model should this control apply to? (Optional)', 'User', ['User', 'Post']) + ->assertOk() + ->run(); + + $this->assertFileExists(app_path('Access/Controls/TestControl.php')); + $this->assertStringContainsString('class TestControl', file_get_contents(app_path('Access/Controls/TestControl.php'))); + $this->assertStringContainsString('protected string $model = App\Models\User;', file_get_contents(app_path('Access/Controls/TestControl.php'))); + + unlink(app_path('Models/User.php')); + unlink(app_path('Models/Post.php')); + } } From 34421d3cf7e81f8dd225526235fc597115c0a50b Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Sun, 6 Jul 2025 15:20:51 +0000 Subject: [PATCH 2/5] Apply fixes from StyleCI --- src/Access.php | 25 +++++++++++++------------ src/Console/ControlMakeCommand.php | 25 +++++++++++++------------ src/Controls/Control.php | 5 +++-- tests/TestCase.php | 4 ++-- 4 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/Access.php b/src/Access.php index a80e03b..42ebc44 100644 --- a/src/Access.php +++ b/src/Access.php @@ -3,7 +3,6 @@ namespace Lomkit\Access; use Illuminate\Database\Eloquent\Model; -use Illuminate\Foundation\Events\DiscoverEvents; use Illuminate\Support\Collection; use Illuminate\Support\Str; use Lomkit\Access\Controls\Control; @@ -14,7 +13,6 @@ class Access { - /** * The default path where control reside. * @@ -23,7 +21,8 @@ class Access public static array $controlDiscoveryPaths; /** - * The registered controls + * The registered controls. + * * @var Control[] */ protected static array $controls; @@ -32,6 +31,7 @@ class Access * Add multiple control to access. * * @param Control[] $controls + * * @return Access */ public function addControls(array $controls): static @@ -58,9 +58,10 @@ public function addControl(Control $control): self } /** - * Get the control instance for the given model + * Get the control instance for the given model. * * @param Model|class-string $model + * * @return Control|null */ public static function controlForModel(Model|string $model): ?Control @@ -79,9 +80,9 @@ public static function controlForModel(Model|string $model): ?Control } /** - * Discover controls for a given path + * Discover controls for a given path. * - * @var string[] $paths + * @var string[] */ public function discoverControls(array $paths): self { @@ -90,7 +91,7 @@ public function discoverControls(array $paths): self return glob($directory, GLOB_ONLYDIR); }) ->reject(function ($directory) { - return ! is_dir($directory); + return !is_dir($directory); }) ->each(function ($directory) { $controls = Finder::create()->files()->in($directory); @@ -104,7 +105,7 @@ public function discoverControls(array $paths): self continue; } - if (! $control->isInstantiable()) { + if (!$control->isInstantiable()) { continue; } @@ -130,13 +131,13 @@ public function discoverControlsWithin(): array /** * Extract the class name from the given file path. * - * @param SplFileInfo $file - * @param string $basePath + * @param SplFileInfo $file + * @param string $basePath + * * @return string */ protected function classFromFile(SplFileInfo $file, string $basePath) { - $class = trim(Str::replaceFirst($basePath, '', $file->getRealPath()), DIRECTORY_SEPARATOR); return ucfirst(Str::camel(str_replace( @@ -145,4 +146,4 @@ protected function classFromFile(SplFileInfo $file, string $basePath) ucfirst(Str::replaceLast('.php', '', $class)) ))); } -} \ No newline at end of file +} diff --git a/src/Console/ControlMakeCommand.php b/src/Console/ControlMakeCommand.php index 52d38b6..9a2e641 100644 --- a/src/Console/ControlMakeCommand.php +++ b/src/Console/ControlMakeCommand.php @@ -11,7 +11,6 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Finder\Finder; -use function Laravel\Prompts\confirm; use function Laravel\Prompts\multiselect; use function Laravel\Prompts\select; @@ -113,7 +112,8 @@ protected function buildClass($name): string /** * Build the model replacement values. * - * @param array $replace + * @param array $replace + * * @return array */ protected function buildModelReplacements(array $replace): array @@ -121,15 +121,15 @@ protected function buildModelReplacements(array $replace): array $modelClass = $this->parseModel($this->option('model')); return array_merge($replace, [ - 'DummyFullModelClass' => $modelClass, + 'DummyFullModelClass' => $modelClass, '{{ namespacedModel }}' => $modelClass, - '{{namespacedModel}}' => $modelClass, - 'DummyModelClass' => class_basename($modelClass), - '{{ model }}' => class_basename($modelClass), - '{{model}}' => class_basename($modelClass), - 'DummyModelVariable' => lcfirst(class_basename($modelClass)), - '{{ modelVariable }}' => lcfirst(class_basename($modelClass)), - '{{modelVariable}}' => lcfirst(class_basename($modelClass)), + '{{namespacedModel}}' => $modelClass, + 'DummyModelClass' => class_basename($modelClass), + '{{ model }}' => class_basename($modelClass), + '{{model}}' => class_basename($modelClass), + 'DummyModelVariable' => lcfirst(class_basename($modelClass)), + '{{ modelVariable }}' => lcfirst(class_basename($modelClass)), + '{{modelVariable}}' => lcfirst(class_basename($modelClass)), ]); } @@ -238,10 +238,11 @@ protected function possiblePerimeters() /** * Get the fully-qualified model class name. * - * @param string $model - * @return string + * @param string $model * * @throws \InvalidArgumentException + * + * @return string */ protected function parseModel(string $model): string { diff --git a/src/Controls/Control.php b/src/Controls/Control.php index 8652d72..a1f93ea 100644 --- a/src/Controls/Control.php +++ b/src/Controls/Control.php @@ -6,7 +6,6 @@ use Illuminate\Contracts\Foundation\Application; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; -use Illuminate\Support\Str; use Lomkit\Access\Perimeters\Perimeter; use Throwable; @@ -14,14 +13,16 @@ class Control { /** * The model the control refers to. + * * @var class-string */ protected string $model; /** - * Does the given model match with the current one + * Does the given model match with the current one. * * @param class-string $model + * * @return bool */ public function isModel(string $model): bool diff --git a/tests/TestCase.php b/tests/TestCase.php index 6700641..2598bcc 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -19,9 +19,9 @@ protected function setUp(): void { parent::setUp(); - (new Access) + (new Access()) ->addControls([ - new ModelControl + new ModelControl(), ]); } From a2815d483229ea4597e34af4c8e7f01cfc52e8a7 Mon Sep 17 00:00:00 2001 From: Gautier DELEGLISE Date: Sun, 6 Jul 2025 17:33:58 +0200 Subject: [PATCH 3/5] =?UTF-8?q?=F0=9F=90=9B=20added=20class=20reference=20?= =?UTF-8?q?to=20controller=20make=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Console/stubs/control.stub | 2 +- tests/Unit/Console/MakeCommandsTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Console/stubs/control.stub b/src/Console/stubs/control.stub index 5ace447..dda0591 100644 --- a/src/Console/stubs/control.stub +++ b/src/Console/stubs/control.stub @@ -13,7 +13,7 @@ class {{ class }} extends Control * The model the control refers to. * @var class-string */ - protected string $model = {{ namespacedModel }}; + protected string $model = {{ namespacedModel }}::class; /** * Retrieve the list of perimeter definitions for the current control. diff --git a/tests/Unit/Console/MakeCommandsTest.php b/tests/Unit/Console/MakeCommandsTest.php index d8d7625..76f3973 100644 --- a/tests/Unit/Console/MakeCommandsTest.php +++ b/tests/Unit/Console/MakeCommandsTest.php @@ -109,7 +109,7 @@ public function test_make_control_with_model_command() $this->assertFileExists(app_path('Access/Controls/TestControl.php')); $this->assertStringContainsString('class TestControl', file_get_contents(app_path('Access/Controls/TestControl.php'))); - $this->assertStringContainsString('protected string $model = App\Models\User;', file_get_contents(app_path('Access/Controls/TestControl.php'))); + $this->assertStringContainsString('protected string $model = App\Models\User::class;', file_get_contents(app_path('Access/Controls/TestControl.php'))); unlink(app_path('Models/User.php')); unlink(app_path('Models/Post.php')); From 1a07202fda35bffe94b53a2fadb525530e02d5be Mon Sep 17 00:00:00 2001 From: Gautier DELEGLISE Date: Sun, 6 Jul 2025 17:51:46 +0200 Subject: [PATCH 4/5] =?UTF-8?q?=E2=9C=A8=20controls=20interactions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Controls/Control.php | 10 ++++++++++ src/Controls/HasControl.php | 2 +- src/Controls/HasControlScope.php | 2 +- src/Policies/ControlledPolicy.php | 20 +++++--------------- tests/Support/Policies/ModelPolicy.php | 3 ++- 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/Controls/Control.php b/src/Controls/Control.php index a1f93ea..bf377aa 100644 --- a/src/Controls/Control.php +++ b/src/Controls/Control.php @@ -30,6 +30,16 @@ public function isModel(string $model): bool return $model === $this->model; } + /** + * Return the control current model + * + * @return string + */ + public function getModel(): string + { + return $this->model; + } + /** * Retrieve the list of perimeter definitions for the current control. * diff --git a/src/Controls/HasControl.php b/src/Controls/HasControl.php index fba73a2..ab6a928 100644 --- a/src/Controls/HasControl.php +++ b/src/Controls/HasControl.php @@ -21,7 +21,7 @@ public static function bootHasControl() * * @return Control|null The newly created control instance, or null if creation was unsuccessful. */ - protected static function newControl(): ?Control + protected function newControl(): ?Control { return Access::controlForModel(static::class); } diff --git a/src/Controls/HasControlScope.php b/src/Controls/HasControlScope.php index 4d00b37..f01c057 100644 --- a/src/Controls/HasControlScope.php +++ b/src/Controls/HasControlScope.php @@ -54,7 +54,7 @@ protected function addControlled(Builder $builder): void { $builder->macro('controlled', function (Builder $builder) { /** @var Control $control */ - $control = $builder->getModel()::control(); + $control = $builder->getModel()->newControl(); return $control->queried($builder, Auth::user()); }); diff --git a/src/Policies/ControlledPolicy.php b/src/Policies/ControlledPolicy.php index 015e510..c59ca25 100644 --- a/src/Policies/ControlledPolicy.php +++ b/src/Policies/ControlledPolicy.php @@ -13,19 +13,9 @@ class ControlledPolicy /** * The model class string. * - * @var string + * @var class-string */ - protected string $model; - - /** - * Returns the fully qualified model class name associated with this policy. - * - * @return string The fully qualified class name of the model. - */ - protected function getModel(): string - { - return $this->model; - } + protected string $control; /** * Retrieves the control instance associated with the current model. @@ -34,7 +24,7 @@ protected function getModel(): string */ protected function getControl(): Control { - return Access::controlForModel($this->model); + return new $this->control; } /** @@ -46,7 +36,7 @@ protected function getControl(): Control */ public function viewAny(Model $user) { - return $this->getControl()->applies($user, __FUNCTION__, new ($this->getModel())); + return $this->getControl()->applies($user, __FUNCTION__, new ($this->getControl()->getModel())); } /** @@ -71,7 +61,7 @@ public function view(Model $user, Model $model) */ public function create(Model $user) { - return $this->getControl()->applies($user, __FUNCTION__, new ($this->getModel())); + return $this->getControl()->applies($user, __FUNCTION__, new ($this->getControl()->getModel())); } /** diff --git a/tests/Support/Policies/ModelPolicy.php b/tests/Support/Policies/ModelPolicy.php index d45916e..68b1847 100644 --- a/tests/Support/Policies/ModelPolicy.php +++ b/tests/Support/Policies/ModelPolicy.php @@ -3,9 +3,10 @@ namespace Lomkit\Access\Tests\Support\Policies; use Lomkit\Access\Policies\ControlledPolicy; +use Lomkit\Access\Tests\Support\Access\Controls\ModelControl; use Lomkit\Access\Tests\Support\Models\Model; class ModelPolicy extends ControlledPolicy { - protected string $model = Model::class; + protected string $control = ModelControl::class; } From 31a995229a52f47ec2707bfabd8e74b17984c0da Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Sun, 6 Jul 2025 15:52:02 +0000 Subject: [PATCH 5/5] Apply fixes from StyleCI --- src/Controls/Control.php | 2 +- src/Policies/ControlledPolicy.php | 3 +-- tests/Support/Policies/ModelPolicy.php | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Controls/Control.php b/src/Controls/Control.php index bf377aa..2439418 100644 --- a/src/Controls/Control.php +++ b/src/Controls/Control.php @@ -31,7 +31,7 @@ public function isModel(string $model): bool } /** - * Return the control current model + * Return the control current model. * * @return string */ diff --git a/src/Policies/ControlledPolicy.php b/src/Policies/ControlledPolicy.php index c59ca25..8a32b3f 100644 --- a/src/Policies/ControlledPolicy.php +++ b/src/Policies/ControlledPolicy.php @@ -3,7 +3,6 @@ namespace Lomkit\Access\Policies; use Illuminate\Database\Eloquent\Model; -use Lomkit\Access\Access; use Lomkit\Access\Controls\Control; class ControlledPolicy @@ -24,7 +23,7 @@ class ControlledPolicy */ protected function getControl(): Control { - return new $this->control; + return new $this->control(); } /** diff --git a/tests/Support/Policies/ModelPolicy.php b/tests/Support/Policies/ModelPolicy.php index 68b1847..762ad09 100644 --- a/tests/Support/Policies/ModelPolicy.php +++ b/tests/Support/Policies/ModelPolicy.php @@ -4,7 +4,6 @@ use Lomkit\Access\Policies\ControlledPolicy; use Lomkit\Access\Tests\Support\Access\Controls\ModelControl; -use Lomkit\Access\Tests\Support\Models\Model; class ModelPolicy extends ControlledPolicy {