Laravel and PHP rules for AI coding agents

Model::create($request->all()) is the line these rules most want to keep out of your codebase. The Laravel rules cover apps on PHP 8 and start with require.php and laravel/framework in Composer, so the agent uses features the PHP version actually supports, such as constructor property promotion, readonly properties and enums.

Validation lives in Form Request classes and authorization in a Policy per model, with a note that in Laravel 11+ the base Controller no longer includes the AuthorizesRequests trait. Eloquent guidance centres on eager loading and turning on preventLazyLoading outside production. Configuration has its own trap: after config:cache, env() outside config/*.php returns null. Slow work such as mail, PDFs and webhooks goes to queued jobs dispatched after commit.

Folder: rules/laravel-php/ · laravel-php.mdc, CLAUDE.md, AGENTS.md

What the Laravel rules tell your agent

First rule of each section in laravel-php.mdc, quoted as shipped.

Mistakes these rules are written to prevent

Source: “Pitfalls to avoid”.

Checks the agent runs before it says done

Example commands from the Laravel file (swap in your repo's own):

Run php artisan test (or vendor/bin/pest), vendor/bin/pint --test and vendor/bin/phpstan analyse, and report each result.

Before and after: Laravel: mass assignment and authorization review

Prompt card 32 · Stack: Laravel, PHP 8.2+ · Type: Security review · Best model: a frontier reasoning model

What the card is for

Find $request->all() writes, $guarded = [] models, missing policies, unscoped route model binding and Blade output that is not escaped, then fix them with Form Requests, $fillable, policies and scoped bindings.

Worked example from the card

Before

// routes/web.php
Route::put('/teams/{team}/projects/{project}', [ProjectController::class, 'update']);

// app/Models/Project.php
class Project extends Model { protected $guarded = []; }

// app/Http/Controllers/ProjectController.php
public function update(Request $request, Team $team, Project $project)
{
    $this->authorize('update', $project);
    $project->update($request->all());
    return back();
}

Expected AI output (abridged)

Critical - $request->all() with $guarded = []: a user can send team_id=9 to move the
project into another team, or owner_id to take ownership.
Critical - unscoped bindings: PUT /teams/1/projects/55 loads project 55 even if it
belongs to team 3. The policy receives only the project, so a policy that checks
team membership through the URL's team is bypassed.
High - Laravel 11: base Controller has no AuthorizesRequests trait; $this->authorize
throws "Call to undefined method" (a 500, not a 403) unless the trait was added.

After

// routes/web.php
Route::put('/teams/{team}/projects/{project}', [ProjectController::class, 'update'])->scopeBindings();

// app/Models/Project.php
class Project extends Model
{
    protected $fillable = ['name', 'description', 'due_on'];
}

// app/Http/Requests/UpdateProjectRequest.php
class UpdateProjectRequest extends FormRequest
{
    public function authorize(): bool
    {
        return $this->user()->can('update', $this->route('project'));
    }

    public function rules(): array
    {
        return [
            'name' => ['required', 'string', 'max:120'],
            'description' => ['nullable', 'string', 'max:5000'],
            'due_on' => ['nullable', 'date'],
        ];
    }
}

// app/Http/Controllers/ProjectController.php
public function update(UpdateProjectRequest $request, Team $team, Project $project): RedirectResponse
{
    $project->update($request->validated());

    return to_route('teams.projects.show', [$team, $project])->with('status', 'Project updated');
}

Excerpt from card 32; the prompt itself is in the pack.

More Laravel prompt cards in the pack

Using these rules in Cursor, Claude Code and AGENTS.md tools

Cursor

Put _base.mdc and laravel-php.mdc in .cursor/rules/. Globs: composer.json, app/**/*.php, routes/*.php, config/*.php, database/**/*.php, resources/views/**/*.blade.php, tests/**/*.php.

Claude Code

Root CLAUDE.md, base first. Path-scoped variant .claude/rules/laravel-php.md for app/**/*.php, routes/*.php, database/**/*.php, resources/views/**/*.blade.php; PostToolUse hook: vendor/bin/pint <file>.

AGENTS.md tools (Codex, Gemini CLI, Aider)

Base then stack in one AGENTS.md. Gemini CLI needs context.fileName; Aider needs --read AGENTS.md. Put this file in the Laravel root. Agents use the AGENTS.md nearest to the file being edited, so a nested copy overrides a root one for that subtree.

Related stacks

← All stacks