A tree with everything in it
An AST throws away whitespace and comments. DressCode is built on a lossless syntax tree, PhpSyntax: printed back, it reproduces the original file byte for byte. So only what a rule touched changes.
Same code.
Just better.
DressCode checks and fixes code against your coding standard, rewrites it for PHP 8.4, 8.5 and 8.6, and migrates it to the latest APIs of Nette, Symfony and Laravel. It works on a lossless syntax tree, so whatever it does not fix stays exactly as you wrote it, byte for byte. One tool, one configuration, one diff.
composer global require dresscode/dresscode
dresscode init
dresscode fix
class Cart {
function add( Item $item ){
if($item->price==null) return;
$this->items[]=array("name"=>$item->name,"price"=>$item->price);
}
}
class Cart
{
public function add(Item $item)
{
if ($item->price == null) {
return;
}
$this->items[] = ["name" => $item->name, "price" => $item->price];
}
}
dresscode fix with the perCs standard, PER Coding Style 3.1
$isExternal = strpos($url, '://') !== false;
$author = $article === null ? null : $article->getAuthor();
$slug = trim(strtolower(strip_tags($title)));
$rating = max(0, min(5, $votes));
$isExternal = str_contains($url, '://');
$author = $article?->getAuthor();
$slug = $title |> strip_tags(...) |> strtolower(...) |> trim(...);
$rating = clamp($votes, 0, 5);
dresscode fix with the modernizations set, after the project moved to PHP 8.6
class ReportController extends AbstractController
{
/** @Security("is_granted('ROLE_ADMIN')") */
public function show(): Response
{
return new Response($this->getUser()->getUsername());
}
}
class ReportController extends AbstractController
{
#[IsGranted('ROLE_ADMIN')]
public function show(): Response
{
return new Response($this->getUser()->getUserIdentifier());
}
}
Moving to Symfony 7 with dresscode upgrade, in the project's own style
class Post extends Model
{
protected $table = 'blog_posts';
public $timestamps = false;
protected $fillable = ['title', 'body'];
protected $casts = ['published_at' => 'datetime'];
}
#[WithoutTimestamps]
#[Table(name: 'blog_posts')]
#[Fillable(['title', 'body'])]
class Post extends Model
{
protected function casts(): array
{
return ['published_at' => 'datetime'];
}
}
Moving to Laravel 13 with dresscode upgrade, in the project's own style
$ dresscode upgrade plan nette/utils 4.0 --save
DRESS|CODE 1.4.0
Upgrade nette/utils 4.0 over 3 files
Must change, 2 topics in 12 places
nette/utils 10 places in 3 files needs ^4.0, automatic
nette/utils 2 places in 1 file works on ^3.2, automatic
Today 2 places of `deprecations` are rewritten on the versions allowed now ...
Composer nette/utils 4.0: nothing in the lock refuses it
Moves nette/utils v3.2.10 => v4.0.0
Plan nette/utils 4.0
1 fix works on the versions allowed today nette/utils
2 composer raise nette/utils to ^4.0
3 fix needs the targets nette/utils
Saved dresscode-upgrade.neon
What moving a Nette 3 application to nette/utils 4.0 takes, measured before anything changes
PER Coding Style, PSR-12, Nette, Symfony and Laravel
To 8.4, 8.5 and 8.6
Nette, Symfony and Laravel
Style, PHP and libraries in one file
How it works
Start from PER Coding Style 3.1, PSR-12, Nette, Symfony or Laravel, and tailor anything you want changed, key by key.
if($user==null) return;
if ($user == null) {
return;
}
Standards and sets →
Raise the version in composer.json, run fix, and the diff is the code you would have written for the new version yourself.
return trim(strtolower(strip_tags($title)));
return $title |> strip_tags(...) |> strtolower(...) |> trim(...);
Upgrading to a newer PHP →
dresscode upgrade measures what a move to a newer Nette, Symfony or Laravel takes, plans it and carries it out with Composer and your tests, in the style your code already has.
$request = $this->requestStack->getMasterRequest();
$request = $this->requestStack->getMainRequest();
Upgrade step by step →
$ dresscode init
DRESS|CODE 1.4.0
Project 29 PHP files in src, tracy/tracy
PHP 8.2 and newer, from `composer.json`
Standard nette, the nearest to your code, …
Measured your code departs from it in 17 decisions, …
Checked a fix changes 1 file, …
Written `dresscode.neon`
$ dresscode check
src/Tracy/Dumper/Exposer.php
error 120:7 The variable `this_` must be written in camelCase.
…
FOUND 7 violations, a fix leaves 2 in 3 of 29 files
$ dresscode fix
FIXED 7 violations found, 2 remaining in 3 of 29 files
The first run on the Tracy codebase, 29 files: init measures the code and writes down the style it already has, so the first check finds only 7 violations.
Configuration
use:
- perCs
- modernizations
braces:
position:
class: nextLine
spacing:
concatenation: '$a.$b'
qualification:
function:
global: bare
optimized: imported
You can read it out loud: the brace of a class goes on the next line, the concatenation dot has no spaces around it, global functions are written as bare names, and the ones PHP optimizes are imported.
PHP CS Fixer has no single setting for what the last two lines say. The result comes from two rules acting together, and you have to work it out:
'native_function_invocation' => [
'include' => ['@compiler_optimized'],
'scope' => 'namespaced',
'strict' => true,
],
'global_namespace_import' => ['import_functions' => true],
The standard says what the code looks like. The sets say what you want from it besides its looks. Anything else is one key.
Under the hood
An AST throws away whitespace and comments. DressCode is built on a lossless syntax tree, PhpSyntax: printed back, it reproduces the original file byte for byte. So only what a rule touched changes.
The heart of the rule that shortens $a ? $a : $b to $a ?: $b. Its whole file has 96 lines and works both ways: it can also write the ternary in full. The fixer in PHP CS Fixer that only shortens it has 223 lines and is marked risky.
public function enter(Node|Token $node, RuleContext $context): void
{
if (
!$node instanceof TernaryNode
|| $node->then === null
|| !$node->condition->isRepeatableRead()
|| !$node->condition->matches($node->then)
|| $node->question->getCurrentLine() !== $node->colon->getCurrentLine()
|| $node->question->hasCommentUpTo($node->colon)
|| !$context->report($node, 'The ternary repeating its condition must be written `?:`.')
) {
return;
}
$node->then = null;
$node->question->setTrailingTrivia([]);
}
Write your own rule →
All rules run in one pass, again and again until the code stops changing. When two of them disagree, the engine notices and says which.
switch becomes match where the types show that integers or enum cases are compared; elsewhere it is only reported. The types come from your project's PHPStan.
Speed
| What | DressCode | Counterpart |
|---|---|---|
| One small file, checked after every save | 118 ms | PHP CS Fixer 323 ms |
| Symfony with its own configuration, 7,704 files | 14.2 s | PHP CS Fixer 25.3 s |
| Rewrite for a newer PHP, Symfony 3.4 | 1.6 s | Rector 128.5 s |
| PER Coding Style fix of Laravel, 1,696 files: places the other tool still finds | 9 | PHP CS Fixer 1,201 |
For reporting alone, phpcs is faster: dresscode check computes the fixes in memory even when it only checks. Measured in October 2026 on PHP 8.5 against PHP CS Fixer 3.95, PHP_CodeSniffer 4.0 and Rector 2.6, median of three runs. How DressCode works →
AI agents
Install the php-fixer plugin in Claude Code, and after every edit to a PHP file DressCode fixes it and tells the agent in plain sentences what changed.
/plugin marketplace add nette/agent-plugins
/plugin install php-fixer@nette
For a clean file it prints nothing, so the check costs the agent no context.
DressCode changed `src/Cart.php`; the changes below are already in the file:
- removed the import of `Acme\Shop\Payment\Gateway`: nothing used it; if you meant to use it, add the import in the same edit as the use.
@@ -12,7 +10,7 @@
public function add(string $name, int $price): void
{
- $this->items[] = ["name" => $name, "price" => $price];
+ $this->items[] = ['name' => $name, 'price' => $price];
}
Get going
Install, let init measure your code, and run the first fix.
dresscode migrate config turns your configuration into keys.
Your phpcs:ignore comments keep working.
8.4, 8.5 and 8.6, from code written for 8.0.
Code from 5.4 on, every change since 6.0, annotations to attributes.
Laravel 13 attributes and renamed APIs.
The story
One rule in my coding standard had grown to 947 lines over an array of tokens, and it needed four fixes in one summer. I wanted to know why a rule has to be that complicated. The answer grew into two projects: a PHP parser that keeps every byte, and a tool built on it.
David Grudl, author of Nette
Checks the code of
One tool, one configuration, one diff.