almacareer/coding-standard
A shared coding standard for Laravel and PHP projects, providing consistent style rules and configuration to keep codebases clean, readable, and easy to maintain. Simple to install and integrate into existing workflows, it helps enforce conventions automatically and reduce style debates.
1. Install the standard (dev only) plus the runner.
composer require --dev almacareer/coding-standard
The package ships a PHP_CodeSniffer ruleset rather than a standalone binary, so verify phpcs/phpcbf are available after install (vendor/bin/phpcs -i) — if not, add squizlabs/php_codesniffer explicitly so the version is under your control instead of riding along as a transitive dependency.
2. Find the ruleset name before guessing at --standard.
head -5 vendor/almacareer/coding-standard/ruleset.xml # look at <ruleset name="...">
composer show almacareer/coding-standard # version + dependencies (Slevomat, etc.)
That <ruleset name="..."> value is what you pass to --standard= once the standards are registered. Referencing the XML path directly (--standard=vendor/almacareer/coding-standard/ruleset.xml) always works and is the safer first step.
3. Wire it into the project once, not into every command.
Create phpcs.xml.dist at the repo root so plain vendor/bin/phpcs picks it up with no arguments:
<?xml version="1.0"?>
<ruleset name="MyProject">
<description>Project rules: Alma Career standard + local tweaks.</description>
<file>src</file>
<file>tests</file>
<exclude-pattern>*/vendor/*</exclude-pattern>
<exclude-pattern>*/var/*</exclude-pattern>
<rule ref="vendor/almacareer/coding-standard/ruleset.xml"/>
</ruleset>
Then add scripts so the team never types the long form:
"scripts": {
"lint": "phpcs -s --cache",
"lint:fix": "phpcbf --cache"
}
4. First real use case: run it on src/ before you run it on tests/.
vendor/bin/phpcs -s src # -s prints the sniff code behind every message
vendor/bin/phpcbf src # auto-fix what is fixable
vendor/bin/phpcs -s src # re-run; the remainder needs human decisions
The -s flag is the single most important habit here. Without the sniff code, every message looks like an opinion; with it, you can search the Slevomat/PHPCS docs and decide whether to comply or exclude. Start with src/ because test files almost always need a relaxed ruleset (see below), and starting with them produces a wall of noise that makes the standard look unusable.
Layer, don't fork. Wrap the package ruleset in your own phpcs.xml.dist and put every project-specific decision there. That way upgrades touch one composer.json line and your exceptions survive.
<rule ref="vendor/almacareer/coding-standard/ruleset.xml"/>
<!-- tests: allow long methods, no strict types requirement, etc. -->
<rule ref="vendor/almacareer/coding-standard/ruleset.xml">
<exclude name="SlevomatCodingStandard.Files.TypeNameMatchesFileName"/>
</rule>
Better still, keep the child ruleset intact and only exclude the handful of sniffs you can justify:
<rule ref="vendor/almacareer/coding-standard/ruleset.xml">
<exclude name="Generic.Files.LineLength"/>
</rule>
<!-- re-enable a specific sniff with a softer config instead of dropping it -->
<rule ref="Generic.Files.LineLength">
<properties>
<property name="lineLimit" value="150"/>
</properties>
</rule>
Per-directory overrides via <rule ref="..."><exclude-pattern> inside a <rule> block, or a separate <ruleset> in tests/phpcs.xml, keep test friction low without weakening src/.
CI as a blocking check, with cache. A typical GitHub Actions step:
- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
tools: cs2pr # or rely on the composer-installed phpcs
- run: composer install --no-interaction --prefer-dist
- run: vendor/bin/phpcs -q --report=checkstyle --report-file=checkstyle.xml
- run: cs2pr checkstyle.xml
cs2pr turns findings into inline PR annotations, which is the difference between a useful standard and a red X nobody opens. Cache the phpcs cache file keyed on composer.lock + the PHP version.
Incremental adoption on a legacy codebase. Three escalating options:
--generate-baseline, 3.11+) — snapshot existing violations, fail only on new ones.<file> entries to phpcs.xml.dist one directory at a time as each gets cleaned.The big-bang phpcbf across a legacy app is a merge-conflict magnet; reserve it for a quiet window and land it as a single dedicated commit with no other changes.
Editor integration (do this on day one).
phpcs path to vendor/bin/phpcs, then enable the "PHP Code Sniffer validation" inspection and point it at your phpcs.xml.dist. Findings show inline as you type.phpcs extension (shevaua.phpcs or ikappas.phpcs) with phpcs.standard set to the project phpcs.xml.dist..editorconfig matching the standard's indentation/line-ending rules. Half of the "the standard is annoying" complaints are really "my editor keeps inserting something the sniffer then reverts."Pre-commit gate. Either captainhook/captainhook or grumphp, or a plain .git/hooks/pre-commit:
#!/bin/sh
vendor/bin/phpcs -q --cache --stdin-path="$(git diff --cached --name-only --diff-filter=ACM | grep '\.php$')"
In practice hook tooling that passes only staged files (e.g. phpcs-changed) is far more pleasant than linting the whole tree on every commit, which pushes people to --no-verify.
Coexistence with PHP-CS-Fixer. They overlap. Pick one as the authority for formatting and make the other defer — usually: PHPCS/Slevomat owns the semantic and structural sniffs, and either drop PHP-CS-Fixer entirely or restrict it to a ruleset that doesn't touch anything the sniffer also fixes. Two tools fighting over the same whitespace produces a stable-but-ugly "each tool undoes the other" loop that people blame on the standard.
The composer plugin will silently not register the standard. If the package (or its dependency dealerdirect/phpcodesniffer-composer-installer) is blocked by Composer's plugin allow-list, --standard=AlmaCareer fails with "the coding standard ... is not installed" while the package is clearly in vendor/. Fix:
"config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true } }
Diagnose with vendor/bin/phpcs -i — if your standard isn't listed, it's a registration problem, not a ruleset problem. This is the single most common first-hour stumble.
Pin the version. A coding standard is a linter with opinions; maintainers add and tighten sniffs in minor releases. With 16 stars and zero dependents there's no ecosystem pressure cushioning those changes — treat upgrades as a deliberate task, not a composer update side effect. Use an exact constraint or a narrow caret and read the diff of ruleset.xml before bumping.
PHPCS exit codes are not just pass/fail. Non-zero means findings or an internal processing error (bad ruleset path, unparsable file, OOM). A CI step that only prints "exit 1" hides a broken ruleset path behind what looks like a style failure. Always show the report, and use -q plus an explicit --report in CI so the failure mode is readable.
phpcs.xml beats phpcs.xml.dist. If someone has a local phpcs.xml lying around, it overrides yours entirely and the CI/local results diverge — a classic "works on my machine." Commit .dist only, .gitignore the non-dist variant, and put the ruleset path in both files (or in the composer script) so a stray local file can't disable the standard.
Always exclude vendor/ and generated code. PHPCS ignores paths passed as <file> reasonably well but <exclude-pattern>*/vendor/*</exclude-pattern> prevents a phpcs . invocation from grinding through dependencies. Add --cache and gitignore .phpcs.cache; on a large repo the uncached run is the difference between 4 seconds and 90.
Auto-fix is partial. phpcbf handles whitespace, import order, and many Slevomat sniffs, but not things like "method too long," "missing declare(strict_types=1)" in files it can't safely edit, or type-hint changes. Expect a meaningful residue after a big phpcbf run. Always run phpcbf then phpcs, and review the diff — phpcbf occasionally mangles heredocs and inline HTML.
testVersion / PHP target mismatches. Slevomat sniffs keyed on language level will flag perfectly valid code if PHPCS doesn't know your minimum PHP version. Set it explicitly:
<config name="php_version" value="80300"/>
and in the ruleset ensure testVersion on relevant sniffs isn't narrower than your actual support matrix.
Debugging a sniff you disagree with. In order:
vendor/bin/phpcs -e --standard=... # list every sniff the standard enables
vendor/bin/phpcs -s path/to/File.php # message + sniff code
vendor/bin/phpcs --report=source src # which sniffs fire most often — good for prioritising exclusions
When a sniff is wrong for your codebase, exclude it by sniff code in your wrapper ruleset with a one-line comment explaining why. Silent unexplained exclusions rot; annotated ones survive review.
Extension point is the wrapper ruleset, not the package. Don't edit vendor/. If you need an org-wide variant (say, an internal acme/coding-standard that refs this one and adds a few sniffs), that's the intended composition pattern — PHPCS rulesets nest cleanly, and your team's quirks stay out of upstream.
Low adoption means low bus factor. With Dependents: 0, you are effectively an early adopter: expect the issue tracker to be quiet, expect occasional releases that assume a newer PHPCS/Slevomat than you have, and be prepared to pin, or vendor the ruleset into your own repo if it ever goes stale. Budget a short review of ruleset.xml on each upgrade — it's a small file and reading it is faster than reverse-engineering a fresh 400-error CI run.
How can I help you explore Laravel packages today?