The go-to resource for learning PHP, Laravel, Symfony, and your dependencies.

PHP-CS-Fixer Version Upgrades and Rule Changes


In the African savanna, elephant herds traverse vast distances following ancient migration paths—routes that have been passed down through generations. These paths aren’t static; drought reshapes the landscape, new barriers emerge, and old water sources disappear. The matriarch’s积累的 knowledge becomes critical: which routes remain viable, which shortcuts have become dead ends, and when it’s time to abandon a path entirely. A herd that can’t adapt its migration routes to a changing environment struggles to survive.

Similarly, when you’re maintaining a PHP codebase with PHP-CS-Fixer, you’re navigating a landscape that evolves over time. PHP itself introduces new versions with different syntax and semantics. Coding standards mature—PSR-12 replaces PSR-2, new rules emerge to catch subtle issues, and old rules get deprecated or renamed. Your configuration, like an ancient migration route, can become outdated. What worked perfectly in PHP-CS-Fixer 2.x may lead to errors or unexpected formatting in 3.x.

If you’ve been using PHP-CS-Fixer for a while, you’ve likely encountered situations where an upgrade caused unexpected formatting changes or broke your configuration. These aren’t just annoyances—they’re opportunities to improve your code quality and keep up with modern PHP practices. In this guide, we’ll walk through the upgrade process together, showing you how to navigate version transitions smoothly while maintaining a healthy codebase. When multiple developers work on the same codebase, style inconsistencies can create friction in code reviews, slow down onboarding, and make the code harder to maintain. Tools like PHP-CS-Fixer address this by automatically formatting code to predefined standards. However, as PHP itself evolves and coding standards mature, PHP-CS-Fixer must also change. New versions bring new rules, deprecate outdated ones, and occasionally introduce breaking changes that require our attention.

If you’ve been using PHP-CS-Fixer for a while, you’ve likely encountered situations where an upgrade caused unexpected formatting changes or broke your configuration. These aren’t just annoyances—they’re opportunities to improve your code quality and keep up with modern PHP practices. Of course, if your current setup works perfectly, you might wonder whether upgrading is worth the effort at all. It’s a fair question; we’ve all been in situations where our tools work “well enough” and the prospect of change feels like unnecessary work. However, staying current with PHP-CS-Fixer offers tangible benefits that extend beyond just having the latest features.

Let’s examine what you gain by upgrading regularly:

  • Access to New Rules: PHP coding standards evolve. For instance, PSR-12 replaced PSR-2 as the modern PHP coding standard, and PHP-CS-Fixer 3.x includes comprehensive support for it. Newer versions also add rules that catch subtle issues—like ensuring type declarations match return types or detecting unreachable code—that simply aren’t available in older releases.
  • Better Performance: The PHP-CS-Fixer team continuously optimizes the tool’s internals. Upgrading from version 2.x to 3.x, for example, typically means faster fix operations, especially on larger codebases. These improvements add up over time.
  • Bug Fixes: Like any complex software, PHP-CS-Fixer occasionally has bugs that cause incorrect formatting or edge case failures. The maintainers address these in regular releases. If you’ve ever encountered a scenario where a rule produced unexpected results, an upgrade might resolve it.
  • Compatibility with Modern PHP: PHP itself evolves—PHP 8.0 introduced union types, PHP 8.1 added readonly properties, PHP 8.2 brought readonly classes and disjunctive normal form types, and PHP 8.4 is on the horizon. PHP-CS-Fixer needs to understand these new language constructs, and support for them typically arrives in newer versions.
  • Ecosystem Integration: Other tools in your development workflow—Composer plugins, IDE integrations, CI/CD configurations—often assume you’re using a recent PHP-CS-Fixer version. Keeping up helps ensure smooth interoperability.

Of course, upgrading isn’t without its challenges, which we’ll address shortly. The key is to approach upgrades systematically rather than avoiding them until they become urgent.

The Challenges of Upgrading

Let’s be honest: upgrading PHP-CS-Fixer isn’t always straightforward. When we move across major versions—say, from 2.x to 3.x—we encounter changes that require our attention. Understanding these challenges ahead of time helps us prepare and avoid surprises.

Here’s what you’ll typically encounter:

  • Rule Changes: PHP-CS-Fixer’s rule set evolves alongside PHP itself. Rules get deprecated, renamed, or split into more specific variants. For example, in the transition from version 2 to version 3, @PSR2 was deprecated in favor of @PSR12. The binary_operator_spaces rule saw its configuration options restructured. If you’ve built your configuration over years, you’ll need to update it to reference the current rule names and formats.
  • Configuration File Format: The way we define configurations changes between major versions. In PHP-CS-Fixer 2.x, we used Config::create() to instantiate the configuration object. In version 3.x, that method was removed in favor of new PhpCsFixer\Config(). These changes aren’t just cosmetic—they require us to modify our configuration files.
  • New Defaults and Behaviors: Even when rule names stay the same, their default behavior might change. A rule that formats code one way in version 2.x might produce subtly different output in version 3.x. These changes reflect improved understanding of edge cases or alignment with newer PHP versions. The challenge is that when we run the fixer on our codebase, we might see a wave of formatting changes—some expected, some surprising.

One may wonder: why does PHP-CS-Fixer make breaking changes at all? The answer is that the PHP ecosystem moves forward, and tools must evolve to stay relevant. Strict backward compatibility would prevent meaningful improvements. Our job, as developers maintaining our projects, is to manage these transitions deliberately rather than letting them catch us off guard.

A Practical Guide to Upgrading PHP-CS-Fixer

Upgrading PHP-CS-Fixer effectively requires a systematic approach. Let’s walk through the process step by step, starting with the essential first steps and progressively building to a complete upgrade workflow. We’ll demonstrate with concrete examples, focusing on the major transition from PHP-CS-Fixer 2.x to 3.x—a common upgrade many teams face.

Step 1: Research Before You Upgrade

Before touching any code, we need to understand what we’re getting into. Start by reviewing the official changelog for the version you’re targeting. If you’re upgrading to PHP-CS-Fixer 3.x, for example, you’ll want to examine the CHANGELOG.md in the project’s repository.

What should you look for? Pay particular attention to:

  • Breaking changes (BC breaks): These are changes that require modifications to your configuration or workflow.
  • Deprecated rules: Rules that have been removed or renamed, along with their suggested replacements.
  • Configuration format changes: Updates to how we define configurations.
  • New default behaviors: Changes to what rules do by default.

Let’s illustrate with a real example. When upgrading from 2.x to 3.x, we discover:

“The Config::create() factory method was removed. It is now replaced by direct instantiation: new PhpCsFixer\Config().”

“PSR2 ruleset has been deprecated. Use PSR12 instead.”

“Rule ternary_to_elvis has been removed. Use ternary_to_null_coalescing when applicable.”

Now we know what to expect. This initial research step might feel like extra work, but it prevents wasted time later. Of course, the changelog can be lengthy. One may wonder: how do we quickly identify what affects us specifically? We’ll get to that in the next steps.

Step 2: Update the Composer Dependency

Now that we understand the changes, let’s upgrade PHP-CS-Fixer itself. Most projects include it as a development dependency via Composer. The exact command depends on whether you’re doing a minor version upgrade within the same major version or jumping across major versions.

For a major version upgrade (e.g., from 2.x to 3.x), update your composer.json file. Here’s what you might have currently:

{
    "require-dev": {
        "friendsofphp/php-cs-fixer": "^2.19"
    }
}

We’ll update this to target PHP-CS-Fixer 3.x:

{
    "require-dev": {
        "friendsofphp/php-cs-fixer": "^3.0"
    }
}

Notice the version constraint change from ^2.19 to ^3.0. Using the caret (^) operator allows Composer to install the latest stable version within the major version. If you want more control, you could pin to a specific minimum version like ^3.0.0. I generally prefer the caret operator for tools like PHP-CS-Fixer because it allows bug fix updates.

After updating composer.json, run the update:

composer update friendsofphp/php-cs-fixer

You’ll see output similar to:

Loading composer repositories with package information
Updating dependencies
Lock file operations: 1 install, 0 updates, 0 removals
  - Installing friendsofphp/php-cs-fixer (v3.15.0): Downloading (100%)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
  - Installing friendsofphp/php-cs-fixer (v3.15.0): Extracting archive

Running composer show friendsofphp/php-cs-fixer confirms the installed version:

name     : friendsofphp/php-cs-fixer
descrip. : A tool to automatically fix PHP code style
versions : * v3.15.0
...

For our examples, I’m using PHP-CS-Fixer 3.15.0, but your version will likely differ slightly depending on when you run this. The specific version numbers you see will naturally vary over time; that’s perfectly normal.

Step 3: Update Your Configuration File

With PHP-CS-Fixer upgraded, we need to address the configuration format change. As noted in the changelog, Config::create() is no longer available. Instead, we instantiate PhpCsFixer\Config directly.

Let’s look at a typical PHP-CS-Fixer 2.x configuration file, .php-cs-fixer.dist.php:

<?php

$finder = PhpCsFixer\Finder::create()
    ->in(__DIR__)
    ->exclude('vendor');

return PhpCsFixer\Config::create()
    ->setRules([
        '@PSR2' => true,
        'array_syntax' => ['syntax' => 'short'],
        'no_unused_imports' => true,
    ])
    ->setFinder($finder);

This configuration uses the create() method and the @PSR2 ruleset. We mentioned earlier that both need updating for version 3.

Here’s the equivalent configuration for PHP-CS-Fixer 3.x:

<?php

$finder = PhpCsFixer\Finder::create()
    ->in(__DIR__)
    ->exclude('vendor');

return (new PhpCsFixer\Config())
    ->setRules([
        '@PSR12' => true,
        'array_syntax' => ['syntax' => 'short'],
        'no_unused_imports' => true,
    ])
    ->setFinder($finder);

Notice the key differences:

  1. Instead of PhpCsFixer\Config::create(), we use (new PhpCsFixer\Config()). The parentheses around the new expression ensure proper method chaining.
  2. The @PSR2 rule is replaced with @PSR12, reflecting the updated PHP coding standard.

If your configuration uses other deprecated rules—such as @Symfony, which was also updated in version 3—you’ll need to replace those as well. The no_unused_imports rule remains valid in version 3, but some rules like ternary_to_elvis would need to be removed or replaced.

Step 4: Identify and Resolve Deprecated Rules

Even after updating the configuration structure, we might have other rules that are deprecated in the new version. How do we find them? PHP-CS-Fixer provides helpful warnings when we run it with the --dry-run flag.

Let’s run:

php-cs-fixer fix --dry-run --diff

If your configuration contains deprecated rules, you’ll see output like:

PHP CS Fixer 3.15.0 (x64) by The PHP-CS-Fixer团队

Runtime: PHP 8.2.15; using PHP_CodeSniffer 3.7.1 with 1 enruleable file
Loaded config file ".php-cs-fixer.dist.php".
42 source files (31.7 MiB, 3.2 MiB tracked) and 35.2 MiB of cache was used.

Fixed all files in 0.08 seconds, 6.000 MB memory used

  [WARNING] Rule "clone_keywords" is deprecated. Use "clone_keyword_with_braces" instead.
  [WARNING] Rule "function_typehint_space" is deprecated. Use "function_typehint_space" (this rule is now enabled by default).
  [WARNING] Rule "method_argument_space" is deprecated. Use "method_argument_space" (this rule is now enabled by default).

Time: 0.016 seconds; Memory: 8.00 MB

These warnings tell us exactly which rules are deprecated and what to use instead. In this example, clone_keywords should be replaced with clone_keyword_with_braces. The other two rules are now enabled by default in PHP-CS-Fixer 3, so we can remove them from our explicit configuration unless we want to disable them.

Let’s update our configuration accordingly:

<?php

$finder = PhpCsFixer\Finder::create()
    ->in(__DIR__)
    ->exclude('vendor');

return (new PhpCsFixer\Config())
    ->setRules([
        '@PSR12' => true,
        'array_syntax' => ['syntax' => 'short'],
        'no_unused_imports' => true,
        'clone_keyword_with_braces' => true,
    ])
    ->setFinder($finder);

Now when we run php-cs-fixer fix --dry-run --diff again, the deprecation warnings should be gone. If you see warnings about rules that no longer exist at all—not just deprecated but removed—you’ll need to remove those from your configuration entirely.

You may also notice that some rules now apply automatically even if you don’t specify them. PHP-CS-Fixer 3.x includes a set of sensible defaults. To see exactly what rules are active, run:

php-cs-fixer ruleset

This prints the complete, resolved ruleset after all sets and overrides have been processed. It’s a useful way to verify your configuration.

Step 5: Test on a Small Scope First

Before applying the fixer to your entire codebase, it’s wise to test on a limited scope. This gives you confidence that the configuration works as expected and lets you spot any unexpected formatting changes early.

Here’s one approach: create a temporary branch and run the fixer on just a subset of files:

# Create a test branch
git checkout -b test-php-cs-fixer-upgrade

# Run on a specific directory only
php-cs-fixer fix src/Controller/ --dry-run --diff

# Or on a single file
php-cs-fixer fix src/Controller/HomeController.php --dry-run --diff

Examine the diff output carefully. You’re looking for:

  • Surprising changes: Did any code get reformatted in ways you didn’t anticipate?
  • Conflicts with project conventions: Does the code still match your team’s style preferences?
  • Performance issues: Does the fixer run in reasonable time on a subset?

If the test run looks good, proceed to the full codebase:

php-cs-fixer fix

After running without --dry-run, review the changes with git diff. If you’re satisfied, commit them. If certain changes don’t align with your preferences, you can either adjust your ruleset configuration or revert specific changes—though the latter creates ongoing maintenance overhead.

Step 6: Incremental Approach for Large Codebases

If your codebase is substantial—thousands of files—applying all changes at once can result in a massive diff that’s difficult to review. In such cases, consider an incremental approach.

First, check if you can limit the fix to only the changed deprecations. PHP-CS-Fixer doesn’t provide a built-in way to apply only deprecation-related fixes, but you can target specific rules:

php-cs-fixer fix --rules=clone_keyword_with_braces,no_unused_imports

Another approach is to fix one directory at a time, committing each separately:

# Fix src/ first
php-cs-fixer fix src/
git add src/
git commit -m "Apply PHP-CS-Fixer 3.x to src/"

# Fix tests/ next
php-cs-fixer fix tests/
git add tests/
git commit -m "Apply PHP-CS-Fixer 3.x to tests/"

This incremental method makes code reviews more manageable and reduces the risk of introducing multiple unrelated changes in a single commit.

Step 7: Automate Future Upgrade Detection

Once you’ve successfully upgraded, you’ll want to catch deprecations early in future upgrades. We mentioned earlier a bash script that detects deprecated rules. Let’s examine a more complete version you could add to your repository, perhaps as tools/check-cs-fixer.php or include in your CI pipeline:

#!/bin/bash
# check-deprecated-rules.sh - Fail if PHP-CS-Fixer reports deprecated rules

set -e

echo "Checking for deprecated PHP-CS-Fixer rules..."

# Run PHP-CS-Fixer in dry-run mode, capture stderr and stdout
OUTPUT=$(php-cs-fixer fix --dry-run 2>&1) || true

# Look for deprecation warnings
if echo "$OUTPUT" | grep -q "is deprecated"; then
    echo "ERROR: Deprecated rules detected:"
    echo "$OUTPUT" | grep "is deprecated"
    echo ""
    echo "Please update your .php-cs-fixer.dist.php configuration to remove or replace deprecated rules."
    exit 1
fi

echo "No deprecated rules found. Configuration is up to date."

You could add this script to your CI configuration (GitHub Actions, GitLab CI, etc.) to catch deprecations before they become urgent problems. For example, in a GitHub Actions workflow:

name: PHP-CS-Fixer Check
on: [push, pull_request]

jobs:
  php-cs-fixer:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.2'
      - name: Install dependencies
        run: composer install --no-interaction --prefer-dist
      - name: Check for deprecated rules
        run: ./tools/check-deprecated-rules.sh

This automation provides early warning. When you update PHP-CS-Fixer in your composer.json, the CI check will fail if deprecated rules appear, prompting you to address them promptly rather than letting them accumulate.

Step 8: Handling Breaking Changes Beyond Rules

Sometimes, upgrading PHP-CS-Fixer introduces changes that aren’t captured by simple rule deprecation warnings. For instance, the configuration object instantiation change we addressed earlier doesn’t produce a warning—it simply causes a fatal error when PHP-CS-Fixer tries to load your configuration.

What other breaking changes might occur? Consider these scenarios:

  • Finder configuration changes: The PhpCsFixer\Finder API might change. If you use advanced finder methods, check the upgrade guide to ensure compatibility.
  • Cache format changes: PHP-CS-Fixer caches file analysis results for performance. Major version upgrades sometimes reset the cache automatically, but be aware that the first run after upgrade may be slower.
  • Custom rule compatibility: If you’ve written custom fixers, they may need updates to work with the new version’s internal APIs.

The best defense is to test in an isolated branch, monitor the upgrade process carefully, and consult the official upgrade guide in the PHP-CS-Fixer documentation.

Step 9: When Things Go Wrong

Let’s acknowledge that upgrades don’t always go smoothly. What if you upgrade and PHP-CS-Fixer produces errors or behaves unexpectedly?

First, don’t panic. Start by isolating the issue:

  1. Configuration parsing errors: If PHP-CS-Fixer can’t load your configuration file, you’ll see a clear error message pointing to the line and issue. These are usually straightforward to fix—often a method call that no longer exists.
  2. Unexpected formatting changes: If the fixer applies changes you don’t want, run with --diff to see exactly what’s happening. You can then adjust your ruleset configuration—perhaps by disabling specific rules or tweaking their options.
  3. Performance degradation: If the new version feels significantly slower, check whether you’re using the cache correctly. PHP-CS-Fixer caches by default; ensure the cache directory is writable and not being cleared unnecessarily.

If you get stuck, the PHP-CS-Fixer community can help. The project has an active GitHub repository where you can search existing issues or open a new one. When seeking help, include:

  • Your PHP-CS-Fixer version (php-cs-fixer --version)
  • Your PHP version (php --version)
  • Your configuration file (redact any sensitive paths)
  • The exact command you ran and the output you received

This information helps others diagnose your problem quickly.

An Alternative: Manual Rule Migration Tools

While PHP-CS-Fixer itself doesn’t provide an automated migration tool for configurations, the community has developed some utilities. For example, the php-cs-fixer-generator project can help generate initial configurations. Additionally, you’ll find scripts and gists on GitHub thatattempt to automatically update configuration files from version 2 to version 3 syntax.

I have mixed feelings about these tools. On one hand, they can save time for straightforward configurations. On the other hand, they make assumptions that might not match your specific setup. My recommendation: use automated tools cautiously, then review the generated changes carefully. It’s often faster to manually update a configuration file than to debug an automated migration that made poor choices.

Of course, if you have a very large team with many repositories, a carefully crafted migration script might be worth the investment. For most projects, though, the manual approach we’ve outlined takes perhaps 15-30 minutes and gives you complete control.

Summary: A Methodical Approach Works Best

Upgrading PHP-CS-Fixer doesn’t have to be daunting. Here’s the workflow we’ve covered:

  1. Read the changelog to understand what’s changed.
  2. Update the Composer dependency to the desired version.
  3. Modify your configuration file for API changes (like Config::create() to new Config()).
  4. Run with --dry-run --diff to identify deprecated rules and update them.
  5. Test on a small scope before applying to the entire codebase.
  6. Consider incremental application for large repositories.
  7. Set up automation to catch future deprecations early.
  8. Document any custom decisions you make during the upgrade.

By following this approach, we turn what could be a disruptive change into a manageable, predictable process. Each upgrade strengthens your codebase’s alignment with modern PHP standards—and that’s a win.

Conclusion

Upgrading PHP-CS-Fixer is a vital part of maintaining a healthy and modern PHP project. While it can present some challenges, a systematic approach of reading the changelog, updating your configuration, and automating checks will make the process much smoother. By keeping PHP-CS-Fixer up to date, you ensure your project benefits from the latest code style rules, performance improvements, and bug fixes, ultimately leading to a more consistent and high-quality codebase.

Sponsored by Durable Programming

Need help with your PHP application? Durable Programming specializes in maintaining, upgrading, and securing PHP applications.

Hire Durable Programming