Profile    Mohammed Shiroz Status   Loading  
Logo
Share This
Back to blog
Filter by:
Tags
//Article title

Laravel Pint, PHPStan and Rector: Automatic Code Quality Without the Arguments

About Post

Code review time is precious, and too much of it goes on things a machine could have caught. "Missing space here." "Unused import." "This can be null." "We stopped writing it this way two versions ago."

None of those comments are wrong. They're just a waste of a human. A reviewer's attention should go to the questions only a person can answer: is this the right design, does it handle the edge cases, will we understand it in a year?

Three tools take the rest off your plate in a Laravel project: Pint formats, PHPStan (with Larastan) finds bugs without running the code, and Rector rewrites code automatically. Here's how to set them up, in the order I'd introduce them to an existing project, and how to make CI enforce them.

Three tools, three different jobs

ToolQuestion it answersChanges your code?
PintDoes it look consistent?Yes, formatting only
PHPStan + LarastanCould this break at runtime?No, it only reports
RectorCould this be written in a newer or simpler way?Yes, real refactors

Step 1: Pint, and the end of formatting debates

Pint is Laravel's code style fixer, built on PHP-CS-Fixer, and new Laravel apps already include it as a dev dependency. With no config at all, it applies the Laravel preset:

./vendor/bin/pint            # fix everything
./vendor/bin/pint --test     # only report, exit with an error if anything is off (for CI)
./vendor/bin/pint --dirty    # only files with uncommitted changes

If you want to adjust the style, add a pint.json to the project root with a preset and any rule overrides. But I'd resist the urge to customise much. The value of Pint isn't that its style is perfect; it's that nobody ever argues about style again.

One tip for an existing project: run Pint across everything in a single, separate commit with no other changes, then add that commit's hash to a .git-blame-ignore-revs file. GitHub's blame view honours that file, and locally you can point git config blame.ignoreRevsFile at it, so git blame skips the formatting commit and still shows who actually wrote each line.

Step 2: PHPStan with Larastan, finding bugs before users do

PHPStan reads your code and reports things that would fail at runtime: calling a method on something that might be null, passing the wrong type, using a property that doesn't exist. Plain PHPStan doesn't understand Laravel's magic (facades, Eloquent attributes, relationships), so install Larastan, which teaches it:

composer require --dev larastan/larastan
# phpstan.neon
includes:
    - vendor/larastan/larastan/extension.neon

parameters:
    paths:
        - app/
    level: 5

Then run ./vendor/bin/phpstan analyse. Once you reach level 8, where PHPStan starts checking nullable types, you'll see classics like this:

$contract = Contract::find($id);

return $contract->tenant->name;
// Cannot access property $tenant on App\Models\Contract|null.

Fair point. find() returns null for a missing ID. That's a 500 error waiting for the first bad link, and PHPStan spotted it without running a single request. (Here, findOrFail() is the honest fix.)

Levels and the baseline trick

PHPStan has rule levels, from 0 (basic checks) up to max (very strict, including how carefully you handle mixed types). On an older project, a high level can produce hundreds of errors on day one, which is how tools get uninstalled.

The fix is the baseline. Run ./vendor/bin/phpstan analyse --generate-baseline and PHPStan writes every current error into phpstan-baseline.neon. Include that file in your config, and from now on only new errors fail the build. Existing debt is frozen, and you can chip away at it whenever you touch a file. Raise the level one step at a time once the baseline shrinks.

Step 3: Rector, the refactoring robot

Rector goes further than reporting. It applies rules that rewrite code: adding type declarations, removing dead code, converting old syntax to newer PHP features, and helping with framework upgrades.

// rector.php
use Rector\Config\RectorConfig;

return RectorConfig::configure()
    ->withPaths([__DIR__ . '/app', __DIR__ . '/tests'])
    ->withPhpSets()                  // reads your PHP version from composer.json
    ->withPreparedSets(deadCode: true, codeQuality: true);

Always start with a dry run, which prints a diff of what it would change:

./vendor/bin/rector process --dry-run

For Laravel-specific rules, there's a community package, driftingly/rector-laravel, with sets for things like moving to newer Laravel conventions. Treat its output like any refactor: review it, run the tests, merge in small batches.

A word of caution: Rector is a power tool. Enable a few rule sets, review the diffs, commit, then add more. Turning on everything at once produces a diff nobody can review, and an unreviewable diff is just a gamble.

The order matters: run Rector first (it changes code), then Pint (it formats whatever Rector wrote), then PHPStan (it checks the final result). Run them in that order locally and in CI.

Step 4: Make CI the bad cop

Tools that run "when someone remembers" don't run. Put them in a Composer script so everyone uses the same command:

"scripts": {
    "quality": [
        "rector process --dry-run",
        "pint --test",
        "phpstan analyse --no-progress"
    ]
}

Then call composer quality in your pipeline on every pull request, before the tests. A minimal GitHub Actions job:

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.5'
      - run: composer install --no-interaction --prefer-dist
      - run: composer quality
      - run: php artisan test

Now the pipeline makes the boring comments, and it makes them before a reviewer even opens the pull request. Nobody takes it personally when a robot points out a missing type.

Bonus: they make AI-written code better too

If you use Claude Code, Cursor or Copilot, these tools become even more valuable. Tell the agent to run composer quality and the tests before it says it's done. It gets fast, objective feedback, fixes its own type errors and formatting, and you review a cleaner diff. Static analysis is one of the best guardrails you can give an AI agent, because it doesn't care how confident the code looks.

The setup in five lines

  • Pint for formatting, applied once in a separate commit, then enforced with --test.
  • PHPStan with Larastan at a sensible level, with a baseline for existing code.
  • Rector with a few rule sets, always reviewed through --dry-run first.
  • One Composer script, run in that order.
  • CI runs it on every pull request, so humans can review what matters.

Which PHPStan level is your main project on right now, and what's stopping you from going one higher?

Comments (0)
Leave your review

Thanks for your valuable comments. Your comments has been updated and appreciate your getting in touch...

01. About Shiroz

Mohammed Shiroz

Hi, I'm Mohammed Shiroz, a software engineer and AI enthusiast from Sri Lanka who turns ideas into intelligent, real-world solutions. With over 9 years of hands-on experience, I currently lead real estate ERP development at Kate Group, a...

03.My Projects

04. Categories

Ready To order Your Project ?

Get in Touch
Close