alleyinteractive/alley-coding-standards
A Composer package offering Alley Interactive’s PHP_CodeSniffer coding standards for WordPress and PHP projects. It provides rulesets to enforce consistent style and best practices, helping teams standardize reviews and CI checks.
alleyinteractive/alley-coding-standards is a PHP_CodeSniffer ruleset (standard name: Alley-Interactive) — a dev-only linting dependency, not a runtime library. It layers Alley's conventions on top of WordPress Coding Standards and PHPCompatibility, so it's most valuable in Laravel projects that talk to WordPress (Sage/Acorn themes, headless WP APIs, filters/actions in mu-plugins).
First steps:
composer require --dev alleyinteractive/alley-coding-standards
Composer will usually prompt to allow dealerdirect/phpcodesniffer-composer-installer — allow it so phpcs -i auto-discovers the Alley-Interactive standard. Verify:
vendor/bin/phpcs -i # should list Alley-Interactive
vendor/bin/phpcs --standard=Alley-Interactive app/
Add a root phpcs.xml.dist (see patterns below) and wrap it in Composer scripts so nobody types the binary path. Look first at your existing violations summary (--report=summary) before turning this on in CI — a greenfield app/ will pass, a legacy one won't.
First real use case: lint a file that registers WordPress hooks, since that's the domain the standard cares about most:
add_filter( 'myplugin_items', function ( array $items ): array {
return array_filter( $items );
} );
As of v2.5.0, the first parameter of filter callbacks is expected to be typehinted, so this exact pattern is now enforced rather than optional.
Ruleset file (phpcs.xml.dist) — extend, don't copy:
<?xml version="1.0"?>
<ruleset name="Project">
<description>Laravel + WordPress project standards.</description>
<file>app</file>
<file>src</file>
<file>config</file>
<rule ref="Alley-Interactive"/>
<!-- Laravel-shaped code the WP standard doesn't understand -->
<exclude-pattern>*/migrations/*</exclude-pattern>
<exclude-pattern>*/vendor/*</exclude-pattern>
<exclude-pattern>*.blade.php</exclude-pattern>
<config name="minimum_supported_wp_version" value="6.4"/>
</ruleset>
Composer scripts (the day-to-day interface):
{
"scripts": {
"lint": "phpcs --parallel=8",
"lint:fix": "phpcbf --parallel=8",
"lint:summary": "phpcs --report=summary --parallel=8"
}
}
CI integration — run composer lint as its own job so a style failure doesn't mask a test failure, and cache vendor:
- run: composer install --prefer-dist --no-progress
- run: composer lint
Incremental adoption. Don't flip the whole repo red at once. Either scope <file> to directories you've cleaned, or add a baseline with a third-party tool and ratchet down over time. --report=source is the fastest way to see which sniffs dominate your debt.
Editor feedback loops. VS Code (squizlabs.phpcs / shepherd), PHPStorm's built-in PHPCS inspection, and Neovim (none-ls) all just want the phpcs binary plus --standard=phpcs.xml.dist from the project root. Configure once per repo, not per machine.
Pre-commit gating on changed files only (CaptainHook, lint-staged, or a pre-commit shell hook running phpcs --report=emacs on staged paths). This is where the standard actually earns its keep — CI-only linting gets ignored.
Suppressing deliberately, with a reason:
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize -- cache payload, not user input
$payload = serialize( $data );
Always resolve the real sniff code first with phpcs -s — the codes are not guessable.
New in v2.5.0 — filter callback typehints. When you touch a hook registration, typehint the first parameter and keep the return type aligned with what the filter contract promises:
add_filter( 'myplugin_total', fn( float $total ): float => $total * 1.2 );
add_filter( 'myplugin_items', 'myplugin_filter_items' );
function myplugin_filter_items( array $items ): array { /* ... */ }
Because typehint sniffs are additive, treat a bump to v2.5.0 as "CI may go red" rather than a drop-in patch bump — pin the version in composer.json ("^2.4" vs "^2.5") if you need to control when it lands.
composer lint:summary, and budget the cleanup.phpcbf will not fix this one. Adding a typehint is a semantic decision (what type does this filter actually pass?), so typehint violations must be fixed by hand or suppressed. Don't assume lint:fix cleans the v2.5.0 fallout.null or a scalar will now fatal against function ( array $items ). Typehint the callbacks you control, and use the nullable/union form (?array, array|string) where the contract is genuinely loose.installed_paths breakage. If dealerdirect/phpcodesniffer-composer-installer was blocked (Composer 2.2+ allow-plugins), PHPCS won't find the standard and errors with "Referenced sniff ... does not exist". Fix with an explicit --runtime-set installed_paths vendor/wp-coding-standards/wpcs,vendor/alleyinteractive/alley-coding-standards.--standard=Alley-Interactive, not alley-interactive or the package name.env(), short array syntax, PSR-4 autoloading, and Blade. Exclude config/, resources/views/, database/migrations/ from <file> rather than suppressing sniff-by-sniff.phpcs -s shows sniff codes; phpcs -e lists everything the standard enables; phpcs -vvv shows ruleset resolution order when an exclusion silently doesn't apply.<exclude name="..."> requires the full sniff code (e.g. WordPress.Arrays.ArrayDeclarationSpacing), and excludes must live inside the <rule ref="Alley-Interactive"> block to apply to the inherited standard.--parallel=8 (CI runners often have 2 cores — tune it), and never lint vendor/, node_modules/, or storage/.^2.0 constraint means a teammate's composer update can turn CI red mid-sprint. Lock the exact minor in composer.json and bump intentionally so new sniffs (like the v2.5.0 filter-callback rule) arrive as a reviewed change.phpcs/ directory and register them with <rule ref="./phpcs/MySniff.php"/> — cheaper than suppressing Alley's rules everywhere.How can I help you explore Laravel packages today?