Introduction
Consistent code style makes projects easier to read, review, and maintain. PHP CS Fixer automatically formats your PHP code to follow coding standards like PSR-12. Instead of debating brace placement in code reviews, you configure the tool once and let it enforce the rules for you.
Key Concepts
- PHP CS Fixer: A tool that automatically fixes PHP code style to conform to a set of rules. Created by Fabien Potencier (Symfony creator) and Dariusz Rumiński.
- PSR-12: The PHP-FIG Extended Coding Style standard. It builds on PSR-1 and defines rules for formatting classes, methods, control structures, and more.
- Coding Standard: A set of conventions for how code should be formatted — indentation, brace placement, spacing, and naming.
- Fixer Rule: An individual formatting rule in PHP CS Fixer (e.g.,
braces_position,single_quote).
Real World Context
Every major PHP framework (Symfony, Laravel, Drupal) enforces a coding standard. On a team, inconsistent formatting creates noisy diffs and wastes time in code reviews arguing about style. PHP CS Fixer eliminates this entirely. You run it before committing, and every file follows the same conventions automatically.
Deep Dive
Installation
Install PHP CS Fixer as a dev dependency.
bashcomposer require --dev friendsofphp/php-cs-fixer
Run it on your source directory to see what it would fix.
bash# Dry run: show what would change without modifying files ./vendor/bin/php-cs-fixer fix src --dry-run --diff # Actually fix the files ./vendor/bin/php-cs-fixer fix src
The --dry-run flag shows proposed changes without applying them, which is useful in CI to fail the build on style violations.
PSR-12 Brace Rules (Critical)
PSR-12 has specific rules about where opening braces go. Getting these right is essential.
Classes, interfaces, and traits: opening brace on a NEW line.
php<?php // CORRECT: class brace on its own line class UserRepository implements RepositoryInterface { // ... } // WRONG: class brace on the same line class UserRepository implements RepositoryInterface { // ... }
Methods: opening brace on a NEW line.
php<?php class OrderService { // CORRECT: method brace on its own line public function calculateTotal(array $items): float { return array_sum($items); } // WRONG: method brace on the same line public function calculateTotal(array $items): float { return array_sum($items); } }
Control structures (if, for, while, switch): opening brace on the SAME line.
php<?php // CORRECT: control structure brace on the SAME line if ($order->isValid()) { $order->process(); } elseif ($order->isPending()) { $order->retry(); } else { throw new InvalidOrderException(); } for ($i = 0; $i < count($items); $i++) { $this->processItem($items[$i]); } while ($queue->hasMessages()) { $message = $queue->dequeue(); $this->handle($message); } // WRONG: control structure brace on a new line if ($order->isValid()) { $order->process(); }
This distinction — new line for classes and methods, same line for control structures — is the most commonly confused PSR-12 rule.
Configuration File
Create a .php-cs-fixer.dist.php file in your project root to define your ruleset.
php<?php use PhpCsFixer\Config; use PhpCsFixer\Finder; $finder = Finder::create() ->in(__DIR__ . '/src') ->in(__DIR__ . '/tests') ->name('*.php'); return (new Config()) ->setRules([ '@PSR12' => true, 'array_syntax' => ['syntax' => 'short'], 'single_quote' => true, 'no_unused_imports' => true, 'ordered_imports' => ['sort_algorithm' => 'alpha'], 'trailing_comma_in_multiline' => true, ]) ->setFinder($finder) ->setRiskyAllowed(false);
The @PSR12 preset applies all PSR-12 rules. The additional rules enforce short array syntax, single quotes, clean imports, and trailing commas in multi-line structures.
Combining with Git Hooks
You can run PHP CS Fixer automatically before every commit using a Git pre-commit hook.
bash#!/bin/sh # .git/hooks/pre-commit ./vendor/bin/php-cs-fixer fix --dry-run --diff if [ $? -ne 0 ]; then echo "Code style violations found. Run: ./vendor/bin/php-cs-fixer fix" exit 1 fi
This prevents poorly formatted code from entering the repository. Developers who forget to run the fixer will be reminded automatically.
Common Pitfalls
- Confusing class braces with control structure braces — PSR-12 puts class and method braces on a new line, but control structure braces on the same line. Mixing these up is the most common PSR-12 violation. Let PHP CS Fixer handle it automatically.
- Not committing the configuration file — If
.php-cs-fixer.dist.phpis not in version control, each developer may use different rules. Always commit the configuration so the entire team shares the same standard.
Best Practices
- Use the @PSR12 preset as your foundation — Start with PSR-12 and add project-specific rules on top. This ensures compatibility with the broader PHP ecosystem.
- Run the fixer in CI as a dry-run — Add
php-cs-fixer fix --dry-run --diffto your CI pipeline. This fails the build when code style violations exist, enforcing consistency without relying on developers to remember.
Summary
- PHP CS Fixer automatically formats PHP code to follow coding standards like PSR-12.
- PSR-12 requires class and method braces on a new line, but control structure braces on the same line.
- Configure rules in
.php-cs-fixer.dist.phpand commit it to version control. - Use
--dry-runin CI to enforce style andfixlocally to auto-correct violations. - Combining PHP CS Fixer with Git hooks prevents style violations from being committed.
Code Examples
<?php
declare(strict_types=1);
namespace App\Service;
use App\Entity\Order;
use App\Repository\OrderRepository;
// PSR-12 compliant class: opening brace on a NEW line
class OrderProcessor
{
public function __construct(
private readonly OrderRepository $orderRepository,
) {
}
// Method opening brace on a NEW line
public function processOrder(Order $order): bool
{
// Control structure braces on the SAME line
if (!$order->isValid()) {
return false;
}
foreach ($order->getItems() as $item) {
if ($item->getQuantity() <= 0) {
continue;
}
$this->fulfillItem($item);
}
return true;
}
// Another method: brace on a NEW line
private function fulfillItem(OrderItem $item): void
{
// while loop: brace on the SAME line
while ($item->hasPendingStock()) {
$item->allocateNextUnit();
}
}
}