style
esc
  • Loading…

Check itLaravel Style Guide lint kit

Conventions for writing, structuring, testing and shipping Laravel 13 apps on PHP 8.5, with attribute-first models and a Livewire or Inertia frontend.

Laravel 13PHP 8.5Livewire · Inertia
Download the kit 115 checks · rule IDs on every finding

Checks a Laravel app against the Laravel Style Guide and reports every finding with its rule ID, linked to the rule:

app/Models/Invoice.php:24:1: MUST LARAVEL-07.3: declare #[Table('...')] on Invoice, even when it matches the convention [chopper]
app/Http/Controllers/InvoiceController.php:31:9: MUST LARAVEL-11.1: validate in a form request, not inline in the controller [chopper]

It runs four kinds of check:

  • Laravel Pint with the guide's pint.json (pint --test, chapter 5). Fixers that enforce a specific rule (declare_strict_types, final_class, strict_comparison, imports, member order, trailing commas) report under that rule.
  • Larastan (PHPStan) at level 8 with the guide's phpstan.neon. Missing types report as LARAVEL-05.8, env() outside config/ as LARAVEL-17.1, everything else as LARAVEL-05.7.
  • Rector in dry-run mode, when the app has a rector.php.
  • The guide's own checks for rules the tools do not express: attribute-first models (#[Table], #[Fillable], #[UseFactory], HasUuids, no $guarded), anonymous migrations with UUID keys and explicit onDelete, form requests, thin controllers, named routes, actions and DTOs, job and command attributes, raw SQL with variables, env() outside config, {!! !!} without a sanitising note, Livewire #[Locked] IDs and per-action authorization, debugging calls, naming and more. checks.json lists them all.

The tools run from the app's own vendor/bin, so their versions are the ones composer.lock pins: run composer install first. The app's own pint.json and phpstan.neon are used when it has them; otherwise the kit's copies (the guide's appendix, verbatim) are.

Run it

Needs Python 3.9+, and PHP 8.5 with the app's Composer dependencies for the tool checks. Run it from the app's folder (where composer.json is), or pass --root:

python3 chopper_lint.py                       # the whole app
python3 chopper_lint.py app/Models routes     # some folders
python3 chopper_lint.py --no-rector           # skip a tool (--no-pint, --no-phpstan)
python3 chopper_lint.py --strict              # fail on SHOULD too
python3 chopper_lint.py --list-checks         # what it checks

Folders named vendor, node_modules, storage, public, bootstrap/cache and build are skipped. Blade views and the TypeScript under resources/js are checked too.

Exit status: 1 when a MUST or MUST NOT rule is broken (any finding with --strict), else 0.

In GitHub Actions

- uses: actions/checkout@v4
- uses: carson-hopper/chopper.dev/content/styleguides/laravel/lint@main
  # with:
  #   working-directory: backend   # the app's folder, if it is not the repository root
  #   php-version: "8.5"          # empty to use the PHP already on the runner

The action installs PHP with shivammathur/setup-php and runs composer install when vendor/ is missing, then lints. Findings appear as annotations on the pull request. Pin @main to a commit SHA for reproducible runs.

Silencing a finding

On the same line or the line above, with a reason:

$totals = DB::select($report->sql()); // chopper-lint: allow LARAVEL-09.9 (vetted report SQL, no input)

For a whole file: // chopper-lint: allow-file LARAVEL-08.2. In Blade: {{-- chopper-lint: allow LARAVEL-19.3 (sanitised by Purifier) --}}. # comments work too; #[...] is an attribute, not a comment.