Skip to content

Conversation

@GautierDele
Copy link
Member

@GautierDele GautierDele commented Apr 9, 2025

Summary by CodeRabbit

  • New Features

    • Introduced new command-line tools that simplify generating control and perimeter classes with customizable options.
    • Enhanced the code scaffolding system by improving stub publishing and interactive configuration.
    • Expanded authorization capabilities by adding restore and permanent removal actions for controlled resources.
    • Added new stubs for control and perimeter classes to streamline development.
  • Bug Fixes

    • Improved organization of test classes to prevent naming conflicts and enhance clarity.
  • Tests

    • Added new test classes to validate the functionality of artisan commands and stub registrations.
    • Introduced tests to ensure correct registration of stubs and validate command functionality.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Apr 9, 2025

Walkthrough

This update extends a Laravel-based access management system by enhancing the service provider and introducing new console commands. The service provider now registers additional commands and listens for stub publishing events. New command classes for generating control and perimeter classes have been added, alongside corresponding stub templates for scaffolding. Policy methods for authorization checks have been expanded, and the test suite has been updated with new tests and namespace adjustments to support these changes.

Changes

File(s) Change Summary
src/AccessServiceProvider.php Added protected $devCommands; introduced registerCommands, registerStubs, and provides methods; updated register and boot methods for command and stub registration.
src/Console/ControlMakeCommand.php
src/Console/PerimeterMakeCommand.php
New console command classes for generating control and perimeter classes; implement stub resolution, namespace determination, interactive prompting, and options processing.
src/Console/stubs/control.stub
src/Console/stubs/perimeter.overlay.stub
src/Console/stubs/perimeter.plain.stub
Added new stub templates defining blueprints for control and perimeter classes (overlay and plain) with appropriate class extensions.
src/Policies/ControlledPolicy.php Added public restore and forceDelete methods with corresponding docblocks and authorization checks via getControl()->should().
tests/Feature/ControlsQueryTest.php
tests/Feature/ControlsShouldTest.php
Updated namespace declarations to Lomkit\Access\Tests\Feature to improve code organization.
tests/Unit/Console/MakeCommandsTest.php
tests/Unit/StubsTest.php
tests/Unit/TestCase.php
Introduced new test classes to validate console command behaviors and stub registrations; updated namespace in TestCase for the unit tests.

Sequence Diagram(s)

sequenceDiagram
    participant ASP as AccessServiceProvider
    participant App as Application Container
    participant Event as Stub Publishing Listener

    ASP->>App: registerCommands($devCommands)
    loop For each command in $devCommands
        App-->>ASP: Command registered (singleton/method)
    end
    ASP->>Event: registerStubs() listens for PublishingStubs event
    Event-->>ASP: Receives stub mappings for registration
Loading
sequenceDiagram
    participant U as User
    participant CMD as ControlMakeCommand
    participant FS as FileSystem

    U->>CMD: Execute "make:control" command
    CMD->>CMD: Call getStub() and resolveStubPath() to retrieve stub
    CMD->>CMD: Build class via buildClass() and buildPerimetersReplacements()
    CMD->>U: Prompt for missing arguments (if any)
    CMD->>FS: Write generated control class file
Loading

Poem

I'm a happy rabbit on a coding spree,
Hopping through stubs and commands with glee.
New methods and tests in lines so neat,
Each change a nibble, each feature a treat.
With a twitch of my nose, I celebrate code complete! 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🔭 Outside diff range comments (1)
tests/Feature/ControlsShouldTest.php (1)

143-153: ⚠️ Potential issue

Test Expectation Mismatch
The test method test_control_should_not_delete_global_using_shared_overlayed_perimeter implies that the delete action should be disallowed; however, the assertion currently expects a truthy response. Please verify whether the intended behavior is to have deletion rejected (in which case the assertion should be assertFalse) or if the test name should be revised to accurately describe the expected outcome.

🧹 Nitpick comments (8)
src/Console/stubs/control.stub (1)

15-15: Update Perimeter return type hint

The docblock needs the full namespace for the Perimeter class to be properly documented.

- * @return array<Perimeter> An array of Perimeter objects.
+ * @return array<\Lomkit\Access\Perimeters\Perimeter> An array of Perimeter objects.
src/Policies/ControlledPolicy.php (2)

10-10: Update TODO comment to reflect implemented methods

The TODO comment needs to be updated since the restore and force_delete methods have now been implemented.

-    //@TODO: what to do for other methods like attach / restore / force_delete ? It only has view / etc basic methods
+    //@TODO: what to do for other methods like attach? Basic methods (view, create, update, delete, restore, forceDelete) are implemented

108-108: Fix docblock comment for restore method

The docblock description for the restore method contains a copy-paste error. It references deletion instead of restoration.

- * @return bool True if deletion is permitted, false otherwise.
+ * @return bool True if restoration is permitted, false otherwise.
tests/Unit/Console/MakeCommandsTest.php (2)

11-11: Use filesystem operations more safely

The tests use @unlink() to suppress warnings if files don't exist, which isn't ideal for test maintainability. Consider using a more robust approach for file management in tests.

- @unlink(app_path('Access/Perimeters/Perimeter.php'));
+ if (file_exists(app_path('Access/Perimeters/Perimeter.php'))) {
+     unlink(app_path('Access/Perimeters/Perimeter.php'));
+ }

Or create a helper method to handle this operation:

private function safelyDeleteFile(string $path): void
{
    if (file_exists($path)) {
        unlink($path);
    }
}

And then use it throughout the test class.

Also applies to: 26-26, 41-41, 56-58, 75-77


1-97: Enhance test coverage with additional assertions

The test methods could be enhanced with more specific assertions about the structure and content of the generated files.

For example, in addition to checking for the class name and inheritance, you could:

  1. Verify that the namespace is correct
  2. Check for the presence of method definitions
  3. Ensure imports are correctly added
  4. Validate the formatting of the generated file

This would provide more comprehensive test coverage for the command functionality.

src/Console/PerimeterMakeCommand.php (2)

40-51: Good stub selection logic with improvement opportunity.

The stub selection logic works well with the nullish coalescing operator. However, consider adding support for different stub types based on a wider range of options or command context.


119-122: Empty implementation of afterPromptingForMissingArguments.

This method is currently empty with just a comment placeholder. Consider implementing interactive prompting similar to the ControlMakeCommand class or remove the method if it's not needed.

For example, you could prompt the user to confirm whether they want to create an overlay perimeter:

protected function afterPromptingForMissingArguments(InputInterface $input, OutputInterface $output)
{
-   //
+   if ($this->didReceiveOptions($input)) {
+       return;
+   }
+   
+   if (confirm('Should this perimeter be an overlay?', false)) {
+       $input->setOption('overlay', true);
+   }
}
src/Console/ControlMakeCommand.php (1)

84-107: Check for base control could be more robust.

The current approach checks for the existence of a base Control class and adjusts namespace imports accordingly. This is a good pattern but could be made more resilient.

Consider enhancing the check:

protected function buildClass($name)
{
    $rootNamespace = $this->rootNamespace();
    $controlNamespace = $this->getNamespace($name);

    $replace = [];

-   $baseControlExists = file_exists($this->getPath("{$rootNamespace}Access\Controls\Control"));
+   // Check multiple potential locations for the Control class
+   $baseControlExists = false;
+   $controlPaths = [
+       $this->getPath("{$rootNamespace}Access\Controls\Control"),
+       $this->getPath("Lomkit\Access\Controls\Control"),
+   ];
+   
+   foreach ($controlPaths as $path) {
+       if (file_exists($path)) {
+           $baseControlExists = true;
+           break;
+       }
+   }

    $replace = $this->buildPerimetersReplacements($replace, $this->option('perimeters'));

    if ($baseControlExists) {
        $replace["use {$controlNamespace}\Control;\n"] = '';
    } else {
        $replace[' extends Control'] = '';
        $replace["use {$rootNamespace}Access\Controls\Control;\n"] = '';
    }

    return str_replace(
        array_keys($replace),
        array_values($replace),
        parent::buildClass($name)
    );
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f98d76e and 5c67be9.

📒 Files selected for processing (12)
  • src/AccessServiceProvider.php (3 hunks)
  • src/Console/ControlMakeCommand.php (1 hunks)
  • src/Console/PerimeterMakeCommand.php (1 hunks)
  • src/Console/stubs/control.stub (1 hunks)
  • src/Console/stubs/perimeter.overlay.stub (1 hunks)
  • src/Console/stubs/perimeter.plain.stub (1 hunks)
  • src/Policies/ControlledPolicy.php (2 hunks)
  • tests/Feature/ControlsQueryTest.php (1 hunks)
  • tests/Feature/ControlsShouldTest.php (1 hunks)
  • tests/Unit/Console/MakeCommandsTest.php (1 hunks)
  • tests/Unit/StubsTest.php (1 hunks)
  • tests/Unit/TestCase.php (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
tests/Unit/StubsTest.php (1)
tests/Unit/TestCase.php (1)
  • TestCase (8-21)
tests/Unit/Console/MakeCommandsTest.php (1)
tests/Unit/TestCase.php (1)
  • TestCase (8-21)
src/Console/PerimeterMakeCommand.php (1)
src/Console/ControlMakeCommand.php (7)
  • AsCommand (15-195)
  • getStub (44-47)
  • resolveStubPath (56-61)
  • getDefaultNamespace (70-73)
  • buildClass (84-107)
  • getOptions (150-155)
  • afterPromptingForMissingArguments (165-178)
🪛 GitHub Actions: tests
tests/Unit/StubsTest.php

[error] 13-13: Failed asserting that two arrays are equal. Expected paths do not match actual paths in the test.

🔇 Additional comments (10)
tests/Feature/ControlsQueryTest.php (1)

3-4: Namespace Declaration Update
The addition of the namespace Lomkit\Access\Tests\Feature aligns the test class with the project’s organizational structure. Please verify that the autoloader configuration (e.g., in composer.json) correctly maps this namespace to the proper directory.

tests/Unit/TestCase.php (1)

3-4: Namespace Update
The new namespace Lomkit\Access\Tests\Unit appropriately categorizes the unit tests and supports better organization—especially since this class now serves as a base for tests such as those in MakeCommandsTest.

src/Console/stubs/perimeter.plain.stub (1)

1-12: Perimeter Plain Stub File Creation
This stub file is designed for generating plain perimeter classes. It correctly uses placeholder syntax (e.g., {{ namespace }}, {{ rootNamespace }}, and {{ class }}) and extends the Perimeter class. Please ensure that the command responsible for generating perimeter classes (likely the PerimeterMakeCommand) properly replaces these placeholders during scaffolding.

tests/Feature/ControlsShouldTest.php (1)

3-4: Namespace Declaration Update
The inclusion of the Lomkit\Access\Tests\Feature namespace ensures consistency with the organizational structure applied to feature tests.

src/Console/stubs/perimeter.overlay.stub (1)

1-12: Perimeter Overlay Stub File Creation
The new stub file for overlay perimeters is well defined: it uses the appropriate placeholder variables and extends OverlayPerimeter. Ensure that the runtime replacement of placeholders via the PerimeterMakeCommand executes correctly, thereby generating a functional perimeter class.

src/AccessServiceProvider.php (4)

13-16: Well-structured command registration.

The array of developer commands is clearly defined and follows good naming conventions. Using an associative array makes the code more maintainable by mapping command names to their respective classes.


52-65: Good implementation of command registration.

The registerCommands method follows Laravel's best practices by:

  1. Supporting custom registration methods for complex commands
  2. Falling back to singleton registration for simpler commands
  3. Properly registering all commands with the application

This implementation allows for flexible command registration while maintaining clean code.


70-77: Well-implemented stub registration.

Using the Laravel event system to listen for PublishingStubs is an elegant way to extend Laravel's stub publishing feature. The use of realpath() ensures that symbolic links are properly resolved.


98-101: Correctly implemented provides method.

The provides method properly returns the services provided by this service provider, following Laravel's ServiceProvider pattern.

src/Console/PerimeterMakeCommand.php (1)

88-97: Empty replacement array in buildClass.

The buildClass method creates an empty $replace array which has no effect on the string replacement. This suggests the implementation may be incomplete or unnecessary.

If no custom replacements are needed, consider simplifying the method:

-protected function buildClass($name)
-{
-    $replace = [];
-
-    return str_replace(
-        array_keys($replace),
-        array_values($replace),
-        parent::buildClass($name)
-    );
-}
+protected function buildClass($name)
+{
+    return parent::buildClass($name);
+}

Or if replacements are needed, implement them like in the ControlMakeCommand class.

->run();

$this->assertFileExists(app_path('Access/Perimeters/Perimeter.php'));
$this->assertStringContainsString('class Perimeter extends Perimeter', file_get_contents(app_path('Access/Perimeters/Perimeter.php')));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix class inheritance assertion

A class can't extend itself. This assertion appears to be incorrect as it checks for class Perimeter extends Perimeter.

- $this->assertStringContainsString('class Perimeter extends Perimeter', file_get_contents(app_path('Access/Perimeters/Perimeter.php')));
+ $this->assertStringContainsString('class Perimeter extends \Lomkit\Access\Perimeters\Perimeter', file_get_contents(app_path('Access/Perimeters/Perimeter.php')));

Alternatively, check what the actual output is and correct the assertion accordingly.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$this->assertStringContainsString('class Perimeter extends Perimeter', file_get_contents(app_path('Access/Perimeters/Perimeter.php')));
$this->assertStringContainsString('class Perimeter extends \Lomkit\Access\Perimeters\Perimeter', file_get_contents(app_path('Access/Perimeters/Perimeter.php')));

Comment on lines 117 to 143
protected function buildPerimetersReplacements(array $replace, array $perimeters)
{
$perimetersImplementation = '';

foreach ($perimeters as $perimeter) {
$perimeterClass = app_path('Access/Perimeters/'.$perimeter.'.php');

$perimetersImplementation .= <<<PERIMETER
\\n
$perimeterClass::new()
->should(function (Model \$user, string \$method, Model \$model) {
return true;
})
->allowed(function (Model \$user) {
return true;
})
->query(function (Builder \$query, Model \$user) {
return \$query;
}),\\n
PERIMETER;
}

return array_merge($replace, [
'{{ perimeters }}' => $perimetersImplementation,
'{{perimeters}}' => $perimetersImplementation,
]);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Perimeter implementation assumptions need refinement.

The buildPerimetersReplacements method makes several assumptions that could lead to issues:

  1. It hardcodes the path to perimeter classes using app_path('Access/Perimeters/').
  2. It assumes specific method signatures with Model and Builder types without importing them.
  3. It generates stub code with default implementations that always return true or the unmodified query.

Consider these improvements:

protected function buildPerimetersReplacements(array $replace, array $perimeters)
{
    $perimetersImplementation = '';
+   $rootNamespace = $this->rootNamespace();

    foreach ($perimeters as $perimeter) {
-       $perimeterClass = app_path('Access/Perimeters/'.$perimeter.'.php');
+       $perimeterClass = $rootNamespace.'Access\\Perimeters\\'.$perimeter;

        $perimetersImplementation .= <<<PERIMETER
            \n
            $perimeterClass::new()
                ->should(function (\Illuminate\Database\Eloquent\Model \$user, string \$method, \Illuminate\Database\Eloquent\Model \$model) {
                    return true;
                })
                ->allowed(function (\Illuminate\Database\Eloquent\Model \$user) {
                    return true;
                })
                ->query(function (\Illuminate\Database\Eloquent\Builder \$query, \Illuminate\Database\Eloquent\Model \$user) {
                    return \$query;
                }),\n
        PERIMETER;
    }

    return array_merge($replace, [
        '{{ perimeters }}' => $perimetersImplementation,
        '{{perimeters}}'   => $perimetersImplementation,
    ]);
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
protected function buildPerimetersReplacements(array $replace, array $perimeters)
{
$perimetersImplementation = '';
foreach ($perimeters as $perimeter) {
$perimeterClass = app_path('Access/Perimeters/'.$perimeter.'.php');
$perimetersImplementation .= <<<PERIMETER
\\n
$perimeterClass::new()
->should(function (Model \$user, string \$method, Model \$model) {
return true;
})
->allowed(function (Model \$user) {
return true;
})
->query(function (Builder \$query, Model \$user) {
return \$query;
}),\\n
PERIMETER;
}
return array_merge($replace, [
'{{ perimeters }}' => $perimetersImplementation,
'{{perimeters}}' => $perimetersImplementation,
]);
}
protected function buildPerimetersReplacements(array $replace, array $perimeters)
{
$perimetersImplementation = '';
$rootNamespace = $this->rootNamespace();
foreach ($perimeters as $perimeter) {
$perimeterClass = $rootNamespace.'Access\\Perimeters\\'.$perimeter;
$perimetersImplementation .= <<<PERIMETER
\n
$perimeterClass::new()
->should(function (\Illuminate\Database\Eloquent\Model \$user, string \$method, \Illuminate\Database\Eloquent\Model \$model) {
return true;
})
->allowed(function (\Illuminate\Database\Eloquent\Model \$user) {
return true;
})
->query(function (\Illuminate\Database\Eloquent\Builder \$query, \Illuminate\Database\Eloquent\Model \$user) {
return \$query;
}),\n
PERIMETER;
}
return array_merge($replace, [
'{{ perimeters }}' => $perimetersImplementation,
'{{perimeters}}' => $perimetersImplementation,
]);
}

Comment on lines +185 to +194
protected function possiblePerimeters()
{
$perimetersPath = is_dir(app_path('Access/Perimeters')) ? app_path('Access/Perimeters') : app_path();

return (new Collection(Finder::create()->files()->depth(0)->in($perimetersPath)))
->map(fn ($file) => $file->getBasename('.php'))
->sort()
->values()
->all();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Limited approach to finding perimeters.

The possiblePerimeters method only searches for perimeters in a fixed location, which may not work for all project structures.

Consider a more flexible approach:

protected function possiblePerimeters()
{
-   $perimetersPath = is_dir(app_path('Access/Perimeters')) ? app_path('Access/Perimeters') : app_path();
+   // Try multiple potential locations
+   $rootNamespace = $this->rootNamespace();
+   $possiblePaths = [
+       app_path('Access/Perimeters'),
+       app_path('Access'),
+       base_path('src/Access/Perimeters'),
+       base_path('src/Access'),
+   ];
+
+   $perimetersPath = app_path();
+   foreach ($possiblePaths as $path) {
+       if (is_dir($path)) {
+           $perimetersPath = $path;
+           break;
+       }
+   }

    return (new Collection(Finder::create()->files()->depth(0)->in($perimetersPath)))
        ->map(fn ($file) => $file->getBasename('.php'))
+       ->filter(function ($filename) use ($perimetersPath) {
+           // Only include files that are likely to be perimeter classes
+           $content = file_get_contents($perimetersPath.'/'.$filename.'.php');
+           return strpos($content, 'Perimeter') !== false;
+       })
        ->sort()
        ->values()
        ->all();
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
protected function possiblePerimeters()
{
$perimetersPath = is_dir(app_path('Access/Perimeters')) ? app_path('Access/Perimeters') : app_path();
return (new Collection(Finder::create()->files()->depth(0)->in($perimetersPath)))
->map(fn ($file) => $file->getBasename('.php'))
->sort()
->values()
->all();
}
protected function possiblePerimeters()
{
// Try multiple potential locations
$rootNamespace = $this->rootNamespace();
$possiblePaths = [
app_path('Access/Perimeters'),
app_path('Access'),
base_path('src/Access/Perimeters'),
base_path('src/Access'),
];
$perimetersPath = app_path();
foreach ($possiblePaths as $path) {
if (is_dir($path)) {
$perimetersPath = $path;
break;
}
}
return (new Collection(Finder::create()->files()->depth(0)->in($perimetersPath)))
->map(fn ($file) => $file->getBasename('.php'))
->filter(function ($filename) use ($perimetersPath) {
// Only include files that are likely to be perimeter classes
$content = file_get_contents($perimetersPath.'/'.$filename.'.php');
return strpos($content, 'Perimeter') !== false;
})
->sort()
->values()
->all();
}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
src/Console/PerimeterMakeCommand.php (2)

90-99: Consider adding class-specific replacements.
Currently, $replace is empty, resulting in no modifications to the generated class. If future overlays or placeholders are intended, you might enhance this method to apply them.


101-110: Overlay option might be more suitable as a boolean flag.
Since your overlay option is treated purely as a yes/no flag, consider using InputOption::VALUE_NONE for a cleaner approach. That way, you can just check $this->option('overlay') without storing a string or null in some cases.

src/Console/ControlMakeCommand.php (1)

185-194: Consider a more flexible perimeter search.
Current logic returns all .php files from a single directory. This may mislead or overload the user if the project organizes perimeters differently or has non-perimeter PHP files.

src/Policies/ControlledPolicy.php (1)

10-10: Extend coverage for attach/detach and other pending methods.
You have a TODO remark regarding additional model actions. Plan for a consistent approach (like should($user, __FUNCTION__, …)), or remove/resolve the TODO if not needed.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7203efa and 16b4483.

📒 Files selected for processing (7)
  • src/Console/ControlMakeCommand.php (1 hunks)
  • src/Console/PerimeterMakeCommand.php (1 hunks)
  • src/Console/stubs/control.stub (1 hunks)
  • src/Controls/Control.php (1 hunks)
  • src/Policies/ControlledPolicy.php (2 hunks)
  • tests/Feature/ControlsShouldTest.php (2 hunks)
  • tests/Unit/Console/MakeCommandsTest.php (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/Controls/Control.php
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/Console/stubs/control.stub
  • tests/Feature/ControlsShouldTest.php
  • tests/Unit/Console/MakeCommandsTest.php
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/Policies/ControlledPolicy.php (2)
tests/Support/Models/Model.php (1)
  • Model (10-23)
src/Perimeters/Perimeter.php (1)
  • should (83-88)
src/Console/ControlMakeCommand.php (3)
src/Console/PerimeterMakeCommand.php (7)
  • AsCommand (13-131)
  • getStub (42-53)
  • resolveStubPath (62-67)
  • getDefaultNamespace (76-79)
  • buildClass (90-99)
  • getOptions (106-111)
  • afterPromptingForMissingArguments (121-130)
src/Controls/Control.php (1)
  • perimeters (35-38)
src/Policies/ControlledPolicy.php (1)
  • create (71-74)
src/Console/PerimeterMakeCommand.php (1)
src/Console/ControlMakeCommand.php (6)
  • getStub (44-47)
  • resolveStubPath (56-61)
  • getDefaultNamespace (70-73)
  • buildClass (84-107)
  • getOptions (150-155)
  • afterPromptingForMissingArguments (165-178)
🔇 Additional comments (7)
src/Console/PerimeterMakeCommand.php (3)

42-53: Stub determination logic looks good.
The conditional assignment for $stub is clear, and the fallback to the plain stub is appropriate.


62-67: Path resolution logic is consistent with Laravel standards.
No issues found; the fallback to __DIR__ is a common practice.


121-130:

❓ Verification inconclusive

Verify the definition of didReceiveOptions().
This method call isn’t defined here or in the Laravel GeneratorCommand. Ensure it’s implemented or included from a trait so your condition works as intended.


🏁 Script executed:

#!/bin/bash
# Search for didReceiveOptions definition in the codebase
rg "function didReceiveOptions" -A 5

Length of output: 38


Review Verification: Missing Method Implementation for didReceiveOptions

Based on the executed search, no definition of didReceiveOptions was found in the repository. Please verify by manually checking whether this method is provided through inheritance (e.g., via a trait or a parent class) or if it might be dynamically defined. If it turns out that no such definition exists, then an implementation should be added to ensure the condition in afterPromptingForMissingArguments() works as intended.

  • Location to review: src/Console/PerimeterMakeCommand.php (lines 121-130)
  • Action required: Confirm the source of didReceiveOptions—if it is meant to be inherited or included, update the code accordingly. Otherwise, add its definition.
src/Console/ControlMakeCommand.php (3)

46-47: Use of the control stub is appropriate.
The call to resolveStubPath aligns with established Laravel file resolution patterns.


58-61: Stub path resolution confirmed.
No issues found with determining the path to the stub file.


84-107: Conditional replacement logic is well-structured.
You correctly handle the scenario where the base Control class may or may not exist, removing unneeded use statements.

src/Policies/ControlledPolicy.php (1)

101-126: New restore and forceDelete methods align with existing pattern.
These methods are consistent with the existing policy methods, calling getControl()->should() for each action. Implementation looks correct.

Comment on lines 117 to 143
protected function buildPerimetersReplacements(array $replace, array $perimeters)
{
$perimetersImplementation = '';

foreach ($perimeters as $perimeter) {
$perimeterClass = $this->rootNamespace().'Access\\Perimeters\\'.$perimeter.'.php';

$perimetersImplementation .= <<<PERIMETER
\\n
$perimeterClass::new()
->should(function (Model \$user, string \$method, Model \$model) {
return true;
})
->allowed(function (Model \$user) {
return true;
})
->query(function (Builder \$query, Model \$user) {
return \$query;
}),\\n
PERIMETER;
}

return array_merge($replace, [
'{{ perimeters }}' => $perimetersImplementation,
'{{perimeters}}' => $perimetersImplementation,
]);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix class reference for perimeters and import missing classes.
Including .php in $perimeterClass breaks the fully qualified class name, preventing ::new() calls. This is a critical issue. Also ensure that Model and Builder are imported in your generated stub or code, since they’re used as types within your should() and query() closures.

A suggested fix for the FQCN:

- $perimeterClass = $this->rootNamespace().'Access\\Perimeters\\'.$perimeter.'.php';
+ $perimeterClass = $this->rootNamespace().'Access\\Perimeters\\'.$perimeter;

Also, add imports for:

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/Console/ControlMakeCommand.php (1)

109-143: ⚠️ Potential issue

Imports for Model and Builder are missing in generated classes.

References to Model and Builder here will cause a class-not-found error in the generated code unless those classes are imported or fully qualified.

Possible fix inside this method (to ensure the generated file has the needed imports):

+ $perimetersImplementation = "use Illuminate\\Database\\Eloquent\\Model;" . PHP_EOL .
+     "use Illuminate\\Database\\Eloquent\\Builder;" . PHP_EOL .
+     $perimetersImplementation;

... or fully qualify references:
- ->should(function (Model $user, string $method, Model $model) {
+ ->should(function (\Illuminate\Database\Eloquent\Model $user, string $method, \Illuminate\Database\Eloquent\Model $model) {
🧹 Nitpick comments (3)
tests/Unit/Console/MakeCommandsTest.php (2)

9-22: Consider verifying namespace and additional content.

While the test verifies the class name and extension, consider also asserting the generated file’s namespace to ensure it matches the expected structure. This can make the test more robust.


73-96: Optionally verify excluded perimeters.

While the test confirms that TestPerimeter is referenced, it might be helpful to also assert that SecondTestPerimeter is not included when it’s not selected. This would ensure correct handling of multiple user choices.

src/Console/ControlMakeCommand.php (1)

180-195: Docblock mismatch and limited search path.

  1. The docblock still references “model names” though it returns perimeter classes. Update the comment to reflect actual usage.
  2. Searching exclusively in app_path('Access/Perimeters') or app_path() may be too narrow for broader project structures. Consider offering additional search paths or a configuration option for flexibility.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 16b4483 and 83f14ba.

📒 Files selected for processing (2)
  • src/Console/ControlMakeCommand.php (1 hunks)
  • tests/Unit/Console/MakeCommandsTest.php (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
tests/Unit/Console/MakeCommandsTest.php (1)
tests/Unit/TestCase.php (1)
  • TestCase (8-21)
src/Console/ControlMakeCommand.php (3)
src/Console/PerimeterMakeCommand.php (7)
  • AsCommand (13-131)
  • getStub (42-53)
  • resolveStubPath (62-67)
  • getDefaultNamespace (76-79)
  • buildClass (90-99)
  • getOptions (106-111)
  • afterPromptingForMissingArguments (121-130)
src/Controls/Control.php (1)
  • perimeters (35-38)
src/Policies/ControlledPolicy.php (1)
  • create (71-74)
🔇 Additional comments (11)
tests/Unit/Console/MakeCommandsTest.php (4)

1-4: Namespace and class imports look good.

No issues found. The file structure and namespace declaration appear consistent with the rest of the codebase.


24-37: Overlay perimeter test looks valid.

It precisely checks that the generated class extends OverlayPerimeter. No further issues spotted.


39-52: Straightforward test coverage for make:control command.

Validates class generation for TestControl. The approach is consistent with Laravel’s artisan testing patterns.


54-71: Good check for base control usage.

This test ensures that if a Control.php file exists, the newly generated class extends Control. Implementation and assertions are appropriate.

src/Console/ControlMakeCommand.php (7)

15-16: Command attribute usage is correct.

#[AsCommand(name: 'make:control')] cleanly declares the artisan command name. No issues here.


39-48: Stub retrieval matches the existing pattern.

The method pulls the control.stub consistently with the approach in the perimeter command. No concerns.


49-61: Stub path resolution logic is appropriate.

It checks for a custom path before falling back to the default directory. This is a good practice.


63-73: Namespace returning the Controls folder is valid.

The default namespace pointing to \Access\Controls aligns well with the project structure.


75-108: Handles base control presence gracefully.

The logic removes or keeps extends Control based on whether the file exists. Implementation looks sound.


145-155: Perimeter option is registered correctly.

The --perimeters option is well-defined and accommodates multiple values. No issues noted.


157-178: Interactive prompt logic is straightforward and aligns with Laravel patterns.

No problems with how user input is collected and assigned to the command options.

@GautierDele GautierDele merged commit 64303f7 into master Apr 10, 2025
21 checks passed
@GautierDele GautierDele deleted the feature/commands branch April 10, 2025 19:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants