hosmelq / php-coding-standard
Opinionated ECS, Rector, and PHPStan presets for PHP and Laravel packages and applications.
Requires
- php: ^8.4
- driftingly/rector-laravel: ^2.7
- friendsofphp/php-cs-fixer: ^3.95
- laravel/pao: ^1.1
- nesbot/carbon: ^3.15
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^2.3
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-strict-rules: ^2.1
- rector/rector: ^2.7
- spaze/phpstan-disallowed-calls: ^4.15
- symplify/easy-coding-standard: ^13.3
- thecodingmachine/phpstan-safe-rule: ^1.4
- thecodingmachine/safe: ^3.4
- ticketswap/phpstan-error-formatter: ^1.3
- tomasvotruba/type-coverage: ^2.5
Requires (Dev)
- ergebnis/composer-normalize: ^2.54
- pestphp/pest: ^5.3
- pestphp/pest-plugin-agent: ^5.0
- pestphp/pest-plugin-rector: ^5.0
- shipmonk/composer-dependency-analyser: ^1.8
- symfony/process: ^8.1
Suggests
- pestphp/pest-plugin-rector: Required by the RECTOR_PEST preset (Pest 5+).
Provides
None
Conflicts
- fakerphp/faker: <1.24
- symfony/event-dispatcher-contracts: <2.5.4 || >=3.0 <3.5
- symfony/translation: <6.4.8 || >=7.0 <7.0.8
- symfony/translation-contracts: <2.5.4 || >=3.0 <3.5
Replaces
None
README
Opinionated ECS, Rector, and PHPStan presets for PHP and Laravel packages and applications, with custom fixers that lay out code by line width.
Requirements
- PHP 8.4+
Installation
Allow the PHPStan extension installer, then install PHP Coding Standard with Composer:
composer config allow-plugins.phpstan/extension-installer true
composer require --dev hosmelq/php-coding-standard
The package includes every tool and extension the presets use, except Larastan. Laravel packages and applications require it themselves:
composer require --dev larastan/larastan
It also includes Laravel PAO, which gives AI
agents compact JSON output from PHPStan, Rector, Pest, PHPUnit, and Paratest.
It only activates when an agent runs the tools. Set PAO_DISABLE=1 to turn it
off.
Choose a preset
Use the same row in every tool:
| Project | ECS | Rector | PHPStan |
|---|---|---|---|
| PHP package or application | ECS_PHP |
RECTOR_PHP |
phpstan-php.neon |
| Laravel package | ECS_LARAVEL_PACKAGE |
RECTOR_LARAVEL_PACKAGE |
phpstan-laravel-package.neon |
| Laravel application | ECS_LARAVEL_APP |
RECTOR_LARAVEL_APP |
phpstan-laravel-app.neon |
ECS and Rector presets are constants on HosmelQ\PhpCodingStandard\CodingStandard.
PHPStan presets are files in the package's config directory. Each Laravel
preset builds on the one above it. Larastan boots Laravel packages with Orchestra
Testbench, which they already require for their tests.
Configure the tools
Create ecs.php:
<?php declare(strict_types=1); use HosmelQ\PhpCodingStandard\CodingStandard; use Symplify\EasyCodingStandard\Config\ECSConfig; return ECSConfig::configure() ->withSets([CodingStandard::ECS_PHP]) ->withPaths([__DIR__.'/src', __DIR__.'/tests']);
Create rector.php. To apply Pest's coding style rules with Pest 5, require
pestphp/pest-plugin-rector and add CodingStandard::RECTOR_PEST:
composer require --dev pestphp/pest-plugin-rector
<?php declare(strict_types=1); use HosmelQ\PhpCodingStandard\CodingStandard; use Rector\Config\RectorConfig; return RectorConfig::configure() ->withSets([CodingStandard::RECTOR_PHP, CodingStandard::RECTOR_PEST]) ->withPaths([__DIR__.'/src', __DIR__.'/tests']);
Create phpstan.neon:
includes: - vendor/hosmelq/php-coding-standard/config/phpstan-php.neon parameters: paths: - src
Run the tools:
vendor/bin/rector process vendor/bin/ecs check --fix vendor/bin/phpstan analyse
Run Rector before ECS so ECS lays out Rector's changes. One ECS run is enough. PHPStan runs at level max with strict, deprecation,
safe function, disallowed call, and full type coverage rules, and requires
#[\Override] on methods that override a parent or interface method. The Laravel
application preset also analyses app, bootstrap, config, database, and
routes. PHPStan caches its results in .cache/phpstan, so add .cache to
.gitignore.
Lay out code
The ECS presets keep code within 100 columns. Chains that fit join onto one line, and long lines break their outermost construct first:
$invoice = Invoice::factory() ->for($customer) ->createOne(); $response = $this->actingAs($user)->post(route('invoices.store'), ['number' => 'INV-001', 'currency' => 'USD', 'paid' => true]); if ($subscription->status === SubscriptionStatus::PastDue && $user->canManageBilling($organization)) {
Becomes:
$invoice = Invoice::factory()->for($customer)->createOne(); $response = $this->actingAs($user)->post(route('invoices.store'), [ 'number' => 'INV-001', 'currency' => 'USD', 'paid' => true, ]); if ( $subscription->status === SubscriptionStatus::PastDue && $user->canManageBilling($organization) ) {
- Long two-call chains keep the first call on the root's line and break before the last call when that continuation fits; otherwise, the last call's arguments wrap. Calls that hug arrays, closures, or arrow functions keep that layout. If the first call's arguments wrap, the last call attaches to its closing parenthesis when that line fits.
- Long method chains with three or more calls put each call on its own line.
- Long chains of three or more calls that were split by hand with several calls
on a line get the same layout as single-line chains. Chains with one call per
line or that form an arrow function's body keep their layout, and long Pest
expect()chains keep their manual->and()grouping. - A property before a call stays on that call's line, as in
->each->delete(). - Arguments, parameters, and arrays put each item on its own line with a trailing comma. A call's last associative array, or its only array, opens inside the call.
- Long conditions break before
&&,||,??,.,?, and:. - Long arrow functions move their body to the next line, starting with
=>. - Lists with
//comments between items put each item on its own line. - Test calls such as
it('title', function () {})keep their title and closure on one line. - Blank lines between consecutive assignments to variables, their properties, or their array items are removed.
- Consecutive statements are grouped by subject: assignments,
newexpressions, statements starting with the same variable, static calls, and function calls. Different subjects are separated by a blank line; assignments are also separated from other statements.
Strings, comments, comparisons, and long test titles can stay longer than the line length.
Change the line length
Configure LineLengthFixer in ecs.php. The line length must be at least 80:
use HosmelQ\PhpCodingStandard\Fixer\LineLengthFixer; return ECSConfig::configure() ->withSets([CodingStandard::ECS_PHP]) ->withConfiguredRule(LineLengthFixer::class, ['line_length' => 120]);
Customize presets
Call withConfiguredRule(), withRules(), or withSkip() after withSets()
in ECS and Rector:
use PhpCsFixer\Fixer\Strict\StrictParamFixer; return ECSConfig::configure() ->withSets([CodingStandard::ECS_PHP]) ->withSkip([StrictParamFixer::class]);
In PHPStan, project parameters override the preset and lists are merged. Add
! after a key, such as paths!, to replace a list.
Review risky changes
Some rules change behavior, such as DeclareStrictTypesFixer,
MbStrFunctionsFixer, StrictComparisonFixer, and StrictParamFixer, and the
Laravel presets convert dates to Carbon. Review these changes, or skip a rule
with withSkip().
Add runtime dependencies
The package is a development dependency. If your code calls Safe\ functions or
uses Carbon, require thecodingmachine/safe or nesbot/carbon directly.
Development
Run the test suite with:
composer test
Changelog
See CHANGELOG.md for a list of changes.
Contributing
Pull requests are welcome. Please run the test suite before submitting changes.
License
The MIT License (MIT). Please see License File for more information.