Larastan, Rector and Pint: Laravel Code Quality Setup (Configs + CI)

Larastan, Rector and Pint: Laravel Code Quality Setup (Configs + CI)

Reading Time: 6 minutes

Series: Every Laravel Project Should Have These Building Blocks
Part: 25 of 35 · Level: Intermediate · Prerequisites: GitHub Actions CI

Short answer: Pint fixes code style, Rector rewrites outdated code, and Larastan finds bugs without running your code. Run them in this order: Rector → Pint → Larastan. Rector changes code, Pint formats what Rector wrote, and Larastan checks the final result. Copy the three configs below, install the pre-commit hook, and run composer quality before every push.

What You’ll Learn

  • What Larastan, Rector and Pint each do
  • How to configure each tool for a real Laravel project
  • How to add them to an existing codebase without drowning in errors
  • The pre-commit hook that runs them automatically
  • The composer scripts and CI check that keep everyone honest

The Three Tools and What They Do

These three tools are the quality pipeline every project in this series runs:

ToolWhat It DoesWhen to Run
PintAuto-formats PHP code using the Laravel preset (built on PSR-12)Every commit (pre-commit hook)
RectorRefactors code automatically: newer PHP features, deprecated patterns, dead codeEvery commit (pre-commit hook)
LarastanStatic analysis: catches type errors, missing methods, wrong return typesCI (quality.yml) and before pushing

Think of them as: Pint is the formatter, Rector is the refactorer, Larastan is the reviewer.

Install:

composer require --dev larastan/larastan rector/rector

Pint ships with every new Laravel app, so there’s nothing to install.

Laravel Pint

Pint is Laravel’s official PHP formatter, built on PHP-CS-Fixer. It enforces Laravel’s code style automatically:

// pint.json
{
    "preset": "laravel",
    "rules": {
        "ordered_imports": {
            "sort_algorithm": "alpha"
        },
        "no_unused_imports": true,
        "blank_line_after_namespace": true,
        "array_syntax": {
            "syntax": "short"
        },
        "not_operator_with_successor_space": false,
        "single_trait_insert_per_statement": true
    }
}

Run it:

# Format all files
vendor/bin/pint

# Format only uncommitted changes (fast)
vendor/bin/pint --dirty

# Check only, for CI: fails if any file needs formatting
vendor/bin/pint --test

# Format a specific file
vendor/bin/pint app/Models/User.php

You never debate tabs vs spaces or brace placement again. Pint makes those decisions for you, consistently.

Rector

Rector automates refactoring. It knows about PHP upgrades, deprecated patterns and code-quality improvements. When you upgrade PHP, Rector rewrites your code to use the new features:

// rector.php
<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;

return RectorConfig::configure()
    ->withPaths([
        __DIR__ . '/app',
        __DIR__ . '/tests',
    ])
    ->withPhpSets()             // Uses the PHP version from your composer.json
    ->withPreparedSets(
        deadCode: true,         // Remove dead code
        codeQuality: true,      // General code quality improvements
        earlyReturn: true,      // Replace else with early return
    )
    ->withImportNames()
    ->withSkip([
        // Skip specific files or rules if needed:
        // \Rector\Php81\Rector\Property\ReadOnlyPropertyRector::class => [
        //     __DIR__ . '/app/Models',
        // ],
    ]);

withPhpSets() reads the PHP version from your composer.json, so Rector never suggests syntax your production server can’t run.

Optional Laravel rules: composer require --dev driftingly/rector-laravel adds Laravel-specific refactors. Enable them with ->withSets([\RectorLaravel\Set\LaravelSetList::LARAVEL_CODE_QUALITY]).

Run it:

# Preview what would change (always do this first)
vendor/bin/rector --dry-run

# Apply changes
vendor/bin/rector

# Preview changes for staged files only
vendor/bin/rector --dry-run $(git diff --name-only --cached | grep '\.php$')

Examples of what Rector rewrites automatically:

  • array_push($arr, $item) → $arr[] = $item
  • Old-style constructors → constructor property promotion
  • strpos($haystack, $needle) !== false → str_contains($haystack, $needle)
  • if/else blocks → early returns
  • Unused private methods and unreachable code → removed

Larastan

Larastan is PHPStan configured for Laravel. It understands Eloquent relationships, facades, helpers and the container, which plain PHPStan doesn’t.

# phpstan.neon
includes:
    - vendor/larastan/larastan/extension.neon

parameters:
    paths:
        - app/

    level: 5

    # Laravel-specific ignores
    ignoreErrors:
        # Dynamic properties on stdClass (from JSON decodes)
        - '#Access to an undefined property stdClass::#'

    excludePaths:
        - bootstrap/cache
        - storage

Run it:

vendor/bin/phpstan analyse
vendor/bin/phpstan analyse --memory-limit=512M  # for large codebases

What Larastan Catches

  • Return type mismatches: a method says it returns User but sometimes returns null
  • Undefined methods: calling a method that doesn’t exist on an Eloquent model
  • Wrong argument types: passing a string where an int is expected
  • Dead code: code after a return statement
  • Missing properties: accessing $user->fullName when there’s no accessor or property

Level 5 Is the Right Starting Point

Larastan has levels 0–10. Level 5 catches the most useful errors without excessive noise. Below 5, important type errors slip through. Above 5, you need thorough PHPDoc and type annotations everywhere, which is a goal to work towards, not a starting point.

Adding This to an Existing Project (The Baseline)

Running Larastan on an existing codebase for the first time can report hundreds of errors. Don’t try to fix them all at once. Record them as a baseline, and only new code has to pass:

vendor/bin/phpstan analyse --generate-baseline

This creates phpstan-baseline.neon. Include it in your config:

includes:
    - vendor/larastan/larastan/extension.neon
    - phpstan-baseline.neon

Then improve gradually: fix a few baseline entries whenever you touch a file, regenerate the baseline, and raise the level one step at a time once it’s small.

Rector has no baseline. Introduce it gradually instead: start with withPhpSets() alone, review the --dry-run diff, commit, then enable one prepared set at a time.

The Pre-Commit Hook

The pre-commit hook runs Rector and Pint automatically on every git commit, and only on the files you’ve changed:

#!/usr/bin/env bash
# .hooks/pre-commit

set -e

# Get list of staged PHP files
PHP_FILES=$(git diff --cached --name-only --diff-filter=ACMR | grep '\.php$' || true)

if [ -z "$PHP_FILES" ]; then
    exit 0
fi

echo "Running Rector on staged files..."
echo "$PHP_FILES" | xargs vendor/bin/rector --clear-cache 2>/dev/null || true

echo "Running Pint on staged files..."
echo "$PHP_FILES" | xargs vendor/bin/pint 2>/dev/null || true

# Re-add any files that Rector/Pint modified
echo "$PHP_FILES" | xargs git add

Install the hook automatically with composer:

"scripts": {
    "post-install-cmd": [
        "[ -d .git ] && chmod +x .hooks/pre-commit && git config core.hooksPath .hooks || true"
    ],
    "post-update-cmd": [
        "[ -d .git ] && chmod +x .hooks/pre-commit && git config core.hooksPath .hooks || true"
    ]
}

The [ -d .git ] check matters. Without it, composer install fails on servers and in Docker builds where there’s no Git repository. After composer install, the hook is active: any PHP file you commit is refactored by Rector and formatted by Pint before the commit is created.

For a deeper look at Git hooks in Laravel, see Laravel Git Pre-Commit Hook: Auto-Format PHP Before Every Commit.

Composer Scripts for Easy Running

Add shortcuts to composer.json:

{
    "scripts": {
        "analyse": "vendor/bin/phpstan analyse --memory-limit=512M",
        "format": "vendor/bin/pint",
        "refactor": "vendor/bin/rector",
        "quality": [
            "@refactor",
            "@format",
            "@analyse"
        ],
        "quality:check": [
            "vendor/bin/rector --dry-run",
            "vendor/bin/pint --test",
            "@analyse"
        ]
    }
}

Usage:

composer quality        # fix everything: Rector → Pint → Larastan
composer quality:check  # check only, changes nothing (use this in CI)
composer analyse        # Larastan only
composer format         # Pint only
composer refactor       # Rector only

Before submitting a PR, run composer quality. If everything passes, you’re ready. More patterns like this are in Laravel Composer Scripts: Replace Your README With One Command.

Running It in CI

Your local hook fixes things before commit; CI makes sure nothing slipped through. In your quality.yml workflow from GitHub Actions CI, run the check-only script after composer install:

- name: Code quality
  run: composer quality:check

quality:check uses --dry-run and --test, so CI fails on any unformatted or un-refactored code without silently changing it. For the test side of your pipeline, see Laravel phpunit.xml Explained.

FAQs

What’s the difference between Larastan and PHPStan?

Larastan is a PHPStan extension that understands Laravel’s “magic”: Eloquent properties, relationships, facades, collections and container bindings. Plain PHPStan reports false errors on all of these.

What level should Larastan start at?

Level 5 on an existing codebase, with a baseline file. Then ratchet up one level at a time. Jumping straight to max produces thousands of errors and gets abandoned.

Do Rector and Pint conflict?

No. Run Rector first (it refactors), then Pint (it formats). CI should verify both produce no diff.

What’s the difference between pint, pint --dirty and pint --test?

pint formats every file. pint --dirty formats only files with uncommitted changes. pint --test changes nothing and fails if anything needs formatting, which is what CI should use.

Will Rector break my code?

It can change behaviour in edge cases, so always run --dry-run, review the diff and run your tests before committing. Rules that misbehave in your codebase can be skipped with withSkip().

Key Takeaways

  • Pint auto-formats code style. Run it on every commit via the pre-commit hook, and never debate formatting again.
  • Rector upgrades code automatically: newer PHP features, deprecated patterns, early returns, dead code. Same pre-commit hook.
  • Larastan catches type errors and undefined methods that would otherwise only surface at runtime. Start at level 5, with a baseline on existing projects.
  • The pre-commit hook runs Rector and Pint only on staged PHP files, so it’s fast: under 2 seconds for most commits.
  • composer quality fixes everything locally; composer quality:check verifies it in CI.

Tips and Gotchas

⚠️ Warning: Don’t set Larastan to level 9 on an existing codebase. You’ll get hundreds of errors and likely give up. Start at level 5 with a baseline, shrink the baseline as you go, then move up one level at a time. Incremental strictness is sustainable; big-bang strictness is not.

⚠️ Warning: The hook’s final git add re-stages whole files. If you staged only part of a file (git add -p), your unstaged changes in that file get committed too. Either stage whole files, or remove the git add line and re-stage manually.

💡 Tip: Introduce Rector one prepared set at a time. A config that enables every set at once will rewrite large parts of an existing codebase in a single, unreviewable diff.

🔥 Expert Note: For formatting, the pre-commit hook is more valuable than CI. CI tells you after the push; the hook fixes it before the commit. Most formatting issues never reach CI because they’re fixed automatically.

Further Reading

  • Larastan: the PHPStan extension for Laravel
  • PHPStan: the underlying static analysis tool
  • Rector: automated PHP refactoring and upgrades
  • Laravel Pint: Laravel’s official code style fixer (wraps PHP-CS-Fixer)
  • PHP-CS-Fixer: the underlying formatter Pint uses

← GitHub Actions CI | Next: AI in Laravel Development →

Larastan, Rector and Pint: Laravel Code Quality Setup (Configs + CI)

Oh hi there 👋
It’s nice to meet you.

Sign up to receive awesome content in your inbox.

We don’t spam! Read our privacy policy for more info.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

2 Comments