ibuildings/qa-tools
iBuildings QA Tools bundles common PHP quality checks into a single Composer package, with shared configuration and commands for code style, static analysis, and testing. It helps teams enforce consistent standards across PHP projects and streamline local development and CI workflows.
The package is a Composer-installed meta-tool: rather than teaching every developer to configure PHPCS, PHPMD, PHPUnit, and friends by hand, you install ibuildings/qa-tools once and run its interactive installer, which writes opinionated config files into your project.
Minimal steps:
# pin deliberately — a 3.0.0-beta now exists and Composer will not pick it up by default
composer require --dev "ibuildings/qa-tools:^2.0"
vendor/bin/qa-tools install
The installer (QaTools\Installer / build-tools in this era of the package) asks which tools you want (typically PHP_CodeSniffer, PHPMD, PHPUnit, and optionally php-cs-fixer and a Makefile) and copies the corresponding *.dist configs into your repo. Commit those generated files — they are the contract between the package defaults and your project.
Confirm what you actually got before trusting any guide (including this one) — the 3.x line is in beta and its layout may differ:
composer show ibuildings/qa-tools # version, dependencies, pins
Where to look first:
vendor/ibuildings/qa-tools/ — the installer source and the templates/ (or dist/) directory. Every config the installer can emit lives there; read these before rerunning the installer so you know what will be overwritten. Compare against the 2.x layout if you are evaluating the beta.composer.json — after install you'll have new vendor/bin/phpcs, vendor/bin/phpmd, vendor/bin/phpunit binaries that qa-tools pulls in as dependencies.phpcs.xml.dist, phpmd.xml.dist, phpunit.xml.dist, and (if chosen) a Makefile.First use case: run PHP_CodeSniffer against your application code to get a baseline before you start refactoring or adding CI.
vendor/bin/phpcs --standard=phpcs.xml.dist app/ routes/ config/
Treat the first output as a baseline, not a blocker — the point is to see where you stand, then decide what to fix versus what to exclude.
Composer scripts as the entry point. Wire the tools into composer.json so nobody has to remember raw paths:
"scripts": {
"lint": "phpcs --standard=phpcs.xml.dist app/ config/ routes/",
"lint:fix": "phpcbf --standard=phpcs.xml.dist app/ config/ routes/",
"md": "phpmd app/,config/,routes/ text phpmd.xml.dist",
"test": "phpunit",
"qa": ["@lint", "@md", "@test"]
}
Then composer qa is the single command developers and CI share. Keep the version constraint pinned in composer.json so a later composer update cannot silently drag a beta in.
Makefile-driven workflow. The installer can generate a Makefile. If you take it, use targets like make qa, make fix, make test and let CI call exactly the same targets as developers. This eliminates “works on my machine” drift between local and pipeline.
Layered standards for a Laravel app. Laravel’s own code doesn’t strictly follow PSR-2 in older versions, and generated framework files will always fail. Don’t fight this — keep the base standard (usually PSR-2/PSR-12 as provided) and add project-specific rulesets:
<!-- phpcs.xml.dist -->
<ruleset name="Project">
<rule ref="vendor/ibuildings/qa-tools/phpcs.xml.dist"/>
<exclude-pattern>storage/*</exclude-pattern>
<exclude-pattern>bootstrap/cache/*</exclude-pattern>
<exclude-pattern>public/*</exclude-pattern>
</ruleset>
Use <exclude-pattern> aggressively on anything generated or third-party; the useful signal is your app/, config/, routes/, database/, and tests/ directories.
Incremental adoption via CI. Don’t wire the full composer qa into CI on day one. Start with PHPCS on a narrow path list, then expand directories as you clean them. PHPMD tends to be noisier — enable it after PHPCS is green, and start with only the rulesets that match your team’s tolerance (e.g., codesize,unusedcode before design).
Pre-commit / pre-push hooks. The natural extension point is to run composer lint:fix on staged files only:
vendor/bin/phpcbf --standard=phpcs.xml.dist -- $(git diff --name-only --cached -- '*.php')
Run fix on staged files, not check, so developers don’t get blocked — the check belongs in CI.
IDE integration. Point PHPStorm’s “PHP Code Sniffer” inspection at vendor/bin/phpcs and phpcs.xml.dist; it will highlight the same violations live. Do the same with PHPMD. This is the single biggest day-to-day win because it moves feedback from CI to the editor.
Editor-agnostic baseline. Because configs are .dist files, a developer can create a personal phpcs.xml (without .dist) for local overrides; PHPCS and PHPUnit both prefer the non-.dist file when present. Document this so people don’t edit the committed baseline by mistake.
Evaluating the beta without contaminating the project. If you want to see what 3.0.0-beta changes, don’t bump your app’s composer.json. Spin up a throwaway directory (or a scratch container) with the same PHP version, install the beta there, run the installer, and diff the emitted templates/ against your committed 2.x configs. Only then decide whether a migration is worth it — there is no changelog to read, so the diff is the changelog.
The stable line is dormant; the 3.x line is an unannounced beta. The last stable release is from 2017 and the package was archived for years. A 3.0.0-beta has since appeared whose entire release note is “Internal beta release” — no changelog, no migration guide, no support statement. Treat it as unknown rather than as an upgrade: a major version bump on an installer-driven meta-tool almost certainly means changed template paths, changed tool pins, and changed installer prompts. For new work, install squizlabs/php_codesniffer (or friendsofphp/php-cs-fixer) and phpmd/phpmd directly and hand-write the configs — you lose the installer, but gain maintainability and a real release history.
Don’t reach for the beta to solve the “old pins” problem. The beta may bump PHPCS/PHPMD/PHPUnit, but with no notes you cannot know which versions, whether the pins are compatible with PHP 8, or whether the installer’s output is stable. If the goal is modern tooling, going direct is lower risk than riding a beta meta-package.
Getting the beta installed is its own trap. composer require --dev ibuildings/qa-tools:3.0.0-beta needs an explicit constraint, and people “fix” the resulting stability error by setting minimum-stability: dev project-wide. Don’t — that opens the door to every dev dependency’s alpha. Use an explicit version constraint (3.0.0-beta) with --with-all-dependencies if resolution fights you, ideally in an isolated QA composer.json or Docker image.
The installer overwrites files. Rerunning qa-tools install after you’ve customized phpcs.xml.dist will clobber your changes unless the tool detects/prompts. Before every rerun, commit first, then diff the generated output. Better: adopt the installer’s output once, then vendor-copy the config into your project and stop using the installer. This matters even more if you trial 3.x — assume its templates are not byte-identical to 2.x.
Version conflicts are the most common failure. If composer require fails on dependency resolution, check what PHPCS/PHPMD/PHPUnit versions qa-tools pins and whether your project already requires newer versions. You may need to isolate the tools in a separate composer.json / Docker image dedicated to QA.
Laravel-specific noise. The stock PHPCS standard will flag things Laravel considers idiomatic (long lines in config/, array alignment in migrations, facade-heavy code in PHPMD’s design ruleset). Two options: exclude specific sniffs in your ruleset (<rule ref="..."><exclude name="..."/></rule>) rather than disabling whole categories, or accept and auto-fix with phpcbf in a dedicated cleanup PR.
PHPUnit config drift. The generated phpunit.xml.dist predates modern Laravel’s testing setup (env vars, coverage includes, tests/Unit + tests/Feature split). Don’t let it overwrite Laravel’s own test configuration — either skip PHPUnit in the installer choice and keep Laravel’s phpunit.xml, or merge carefully. This is the most likely point of friction on a Laravel project, and a 3.x template may or may not have caught up; diff before accepting.
PHPMD paths are comma-separated with no spaces. A very common footgun:
# wrong — PHPMD treats "app/ config/" as one path
vendor/bin/phpmd "app/ config/" text phpmd.xml.dist
# right
vendor/bin/phpmd app/,config/ text phpmd.xml.dist
phpcbf is the fastest way to a clean baseline. Run the fixer once across your chosen directories in an isolated commit, review the diff, then re-run the checker. Don’t hand-fix whitespace violations the fixer can handle.
Extension point: the .dist convention. Every tool in this stack honors <tool>.xml.dist as the committed default while <tool>.xml overrides it locally. Lean into this for personal preferences (e.g., a developer who wants stricter PHPCS locally) without touching the shared baseline. Add phpcs.xml, phpmd.xml, and phpunit.xml to .gitignore.
Exit codes matter in CI. phpcs returns 0 clean, 1 violations, 2 fixer changes made, 3 processing error. If you only gate on non-zero, a 2 from a stray phpcbf in CI will fail the build. Run phpcs (check), never phpcbf (fix), in CI.
Debugging a mysterious rule trigger. Use -s with PHPCS to print the sniff codes behind each error — invaluable when deciding what to exclude:
vendor/bin/phpcs -s app/ | head -50
Pin, and treat the beta as a fork. Because qa-tools pins tool versions, treat upgrades of qa-tools itself as the upgrade path for the underlying tools. On the stable line there won’t be one. On the beta line there is no published contract, so if you adopt it, lock the exact beta version in composer.json, commit the generated configs, and budget time to migrate onto directly-required tools on any project you plan to keep.
How can I help you explore Laravel packages today?