Laravel Pint Now Replaces Fully Qualified Class Names with Imports

under

If you’ve ever opened a PR review and gotten dinged for using \Illuminate\Support\Collection inline instead of importing it at the top, you already know why this feature matters.

Why Laravel Pint Now Replaces Fully Qualified Class Names with Imports

Laravel Pint — Laravel’s zero-config opinionated code style fixer built on top of PHP-CS-Fixer — shipped a behavior change that a lot of PHP developers have been quietly wanting for years. As of this update covered by Laravel News, Pint now replaces fully qualified class names with imports automatically, transforming verbose inline class references into clean use statements at the top of your file.

This isn’t just cosmetic. It directly improves readability, enforces consistency across a codebase, and eliminates a whole category of code style arguments in PRs. If you’ve worked on a team where half the devs write new \Carbon\Carbon() and the other half write use Carbon\Carbon at the top, you know exactly what I mean.

The underlying rule driving this is the fully_qualified_strict_types fixer from PHP-CS-Fixer, which Pint now applies as part of its default ruleset behavior. Let’s dig into what exactly changes, how to configure it, and when you might want to tweak the defaults.

What Changes in Your Code: Before and After

To make this concrete, here’s what Pint will now transform automatically.

Before: Fully Qualified Class Names Inline

<?php

namespace App\Services;

class OrderProcessor
{
    public function process(): \Illuminate\Support\Collection
    {
        $items = new \Illuminate\Support\Collection();

        return \Illuminate\Support\Facades\DB::transaction(function () use ($items) {
            return $items->map(function ($item) {
                return new \App\Models\Order($item);
            });
        });
    }
}

After: Clean Imports at the Top

<?php

namespace App\Services;

use App\Models\Order;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

class OrderProcessor
{
    public function process(): Collection
    {
        $items = new Collection();

        return DB::transaction(function () use ($items) {
            return $items->map(function ($item) {
                return new Order($item);
            });
        });
    }
}

The difference is immediately obvious. The second version is what every senior Laravel developer expects to see — and now Pint enforces it without you having to think about it.

This transformation handles multiple use cases at once: type hints in method signatures, new instantiation expressions, static method calls, and catch blocks. That’s comprehensive coverage of every place a fully qualified class name tends to sneak in.

How Pint Replaces Fully Qualified Class Names Under the Hood

Pint’s a wrapper, not a standalone tool. It delegates the actual fixing to PHP-CS-Fixer and manages opinionated rule presets on top. The specific fixers involved here include:

  • fully_qualified_strict_types — Converts FQCNs in strict type declarations and return types to short names with imports
  • no_leading_import_slash — Strips the leading backslash from use statements
  • ordered_imports — Keeps your import block sorted alphabetically (grouped by type if configured)
  • no_unused_imports — Removes use statements that aren’t referenced anywhere in the file

These fixers work together. When Pint converts an inline \Illuminate\Support\Collection to Collection, it also adds the use Illuminate\Support\Collection; statement and runs the import sorter in the same pass. It’s genuinely atomic — one command, everything cleaned up together.

Running Pint

If you’re not already running Pint in your project, setup is a single Composer command:

composer require laravel/pint --dev

Then run it:

./vendor/bin/pint

To preview what it would change without actually writing files, use the --test flag:

./vendor/bin/pint --test

For CI pipelines — and you should absolutely be running this in CI — --test exits with a non-zero status code if any files need fixing, which fails the build and blocks the merge. That’s exactly the behavior you want.

Configuring the Rules in pint.json

Pint reads from a pint.json file in your project root. If you’re using a custom preset and want to explicitly enable the import-replacing behavior, here’s how to configure it:

{
    "preset": "laravel",
    "rules": {
        "fully_qualified_strict_types": true,
        "global_namespace_import": {
            "import_classes": true,
            "import_constants": false,
            "import_functions": false
        },
        "no_unused_imports": true,
        "ordered_imports": {
            "sort_algorithm": "alpha"
        }
    }
}

The global_namespace_import rule deserves a specific callout. Setting import_classes to true ensures that even globally namespaced classes — think \DateTime, \Exception, \Throwable — get imported rather than referenced with a leading backslash. This is a team preference thing. Some codebases keep the leading backslash for global PHP classes to distinguish them visually, and honestly that’s a reasonable stance. Configure it to match your team’s standard.

Practical Implications for Teams and CI Pipelines

Running Pint in GitHub Actions

Here’s a minimal GitHub Actions workflow that enforces this on every PR:

name: Code Style

on: [push, pull_request]

jobs:
  pint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.3'

      - name: Install Dependencies
        run: composer install --no-interaction --prefer-dist

      - name: Run Pint
        run: ./vendor/bin/pint --test

This blocks any PR that introduces inline FQCNs. Once you’ve got this check enabled, it’s self-enforcing — the pipeline catches it before a human has to, which is the whole point.

Migrating an Existing Codebase

If you’re enabling this on an existing project with hundreds of files, do not run Pint across the entire codebase in a single commit. That creates a massive diff that’s nearly impossible to review, and it absolutely destroys your git blame history for those files.

The better approach:

  1. Run ./vendor/bin/pint --dirty to only fix files that have uncommitted changes
  2. Fix files incrementally as you touch them in feature work
  3. Or, if you want to do it all at once, do it in an isolated “style cleanup” commit with a clear message so git blame stays useful

The --dirty flag is particularly handy in a pre-commit hook setup with tools like Husky or the PHP equivalent GrumPHP.

When You Might Want to Disable This Behavior

Most of the time you want this on. But there are edge cases worth knowing about.

Conditional class existence checks using class_exists('Some\Class\Name') don’t get touched — those are strings, not actual references, so Pint won’t break anything there.

Generated files like migrations or compiled views might have inline FQCNs for specific reasons. If you have directories you want Pint to skip entirely, configure exclusions in pint.json:

{
    "exclude": [
        "database/migrations",
        "bootstrap/cache"
    ]
}

Packages you’re developing might deliberately use FQCNs in specific places for clarity in documentation-facing code. In those cases you can configure rule exclusions per file using PHP-CS-Fixer’s @ annotations in the file itself — though I’d treat that as a last resort. If you’re reaching for per-file overrides constantly, something’s off with your config.

Conclusion: Let Pint Do the Work

Pint replacing fully qualified class names with imports automatically is a net positive for virtually every Laravel project. It removes a tedious category of review comments, enforces a pattern that experienced PHP developers already follow manually, and requires zero ongoing effort once it’s wired into your CI pipeline. Why spend mental energy on import formatting when a tool can handle it for you?

The opinionated defaults in the Laravel preset are sensible for most projects, and pint.json gives you enough control for the edge cases where you need to deviate. Add it to your CI pipeline today, run it once across your codebase in a dedicated commit, and move on.

Your reviewers will notice the difference — mostly because they’ll stop having anything to comment on.

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.