lts / php-qa-ci
Simple PHP QA pipeline and scripts. Largely just a collection of dependencies with configuration and scripts to run them together
Requires
- php: ^8.3
- composer-plugin-api: ^2.0
- ext-filter: *
- ext-json: *
- ext-openssl: *
- ext-tokenizer: *
- ext-xml: *
- ergebnis/composer-normalize: ^2.42
- funkyproject/reflection-file: @stable
- nikic/php-parser: ^5.0
- php-parallel-lint/php-console-color: @stable
- php-parallel-lint/php-parallel-lint: @stable
- phpstan/extension-installer: @stable
- phpstan/phpstan-phpunit: ^2.0
- phpstan/phpstan-strict-rules: @stable
- phpunit/phpunit: @stable
- sqlftw/sqlftw: ^0.1.17
- thecodingmachine/safe: ^3.3.0
Requires (Dev)
- roave/security-advisories: dev-master
Suggests
- nunomaduro/larastan: Laravel specific checks (PHPStan Wrapper)
- phploc/phploc: get some stats, not currently compatible with latest symfony
- phpstan/phpstan-deprecation-rules: Checks for deprecated functions and methods
- phpstan/phpstan-doctrine: Adds checks for Doctrine
- phpstan/phpstan-mockery: Prevents Mockery from failing on Stan
- phpstan/phpstan-phpunit: Adds checks for PHPUnit tests
- phpstan/phpstan-symfony: Symfony specific checks
- sllh/composer-versions-check: To be brought back in when there is a PHP8 version...
Replaces
This package is auto-updated.
Last update: 2026-06-04 13:06:22 UTC
README
A comprehensive quality assurance and continuous integration pipeline for PHP 8.3+ projects, written in Bash. Runs tools in a logical order designed to fail as quickly as possible, suitable for both local development and CI.
This package is written for and tested on Linux.
Install
composer require --dev lts/php-qa-ci:dev-php8.4@dev
The qa script will be installed in your project's bin directory. By default, Composer uses vendor/bin, but you can configure a custom location in your composer.json:
"config": { "bin-dir": "bin" }
For Symfony projects, you can accept the prompts to run recipes, but you will then need to decide whether to stick with Symfony defaults or the php-qa-ci defaults (which are more extensive). If you decide to keep the php-qa-ci defaults, remove the config files created by the Symfony recipe:
# Revert to php-qa-ci PHPUnit configs (compare files first)
rm phpunit.xml.dist
ln -s vendor/lts/php-qa-ci/configDefaults/generic/phpunit.xml
Required Composer Configuration
Your project's composer.json must allow the required plugins:
{
"config": {
"allow-plugins": {
"ergebnis/composer-normalize": true,
"lts/php-qa-ci": true,
"phpstan/extension-installer": true
}
}
}
Disabling Config Push
This project will push config updates direclty into the main repo
If this is not desired eg in production,staging,CI deployments then
export PHP_QA_CI_DISABLE_CONFIG_PUSH=true
What It Does
PHP-QA-CI orchestrates multiple PHP quality tools across four phases:
Phase 1 -- Code Modification:
- Rector (safe functions, PHPUnit, PHP 8.4 upgrades)
- PHP CS Fixer
Phase 2 -- Linting and Validation: 3. PSR-4 Validation 4. Composer Checks 5. Strict Types Enforcement 6. PHP Lint 7. Composer Require Checker 8. Markdown Links Checker
Phase 3 -- Static Analysis: 9. PHPStan (level max)
Phase 4 -- Testing: 10. PHPUnit 11. Infection (mutation testing, optional, requires Xdebug)
Post-Success: PHPLoc (stats only, cannot fail)
See Pipeline Architecture for full details.
Tool Delivery
PHP-QA-CI uses a hybrid approach to tool delivery:
- PHARs (via PHIVE): PHPStan, PHP CS Fixer, Infection, Composer Require Checker -- delivered in
vendor-phar/ - Composer dependencies: PHPUnit, phpstan-strict-rules, phpstan-phpunit, parallel-lint
- Isolated Composer project: Rector -- in
tools/rector/with its owncomposer.jsonto prevent dependency conflicts
The phpstan/phpstan package is in the replace section of composer.json since PHPStan is provided via PHAR. This prevents version conflicts when consuming projects also require PHPStan extensions.
Custom PHPStan Rules
Always-on rules (auto-loaded)
These rules are active automatically in every project that uses php-qa-ci — no configuration needed:
- ForbidMockingFinalClassRule -- Prevents mocking of final classes
- ForbidAllowMockWithoutExpectationsRule -- Bans
#[AllowMockObjectsWithoutExpectations] - ForbidDangerousFunctionsRule -- Bans exec/eval/unserialize and similar
- ForbidEmptyCatchBlockRule -- Requires catch blocks to have a body
- RequireDeclareStrictTypesRule -- Requires
declare(strict_types=1)in all PHP files - RequireSensitiveParameterAttributeRule -- Requires
#[\SensitiveParameter]on plaintext credential parameters (names matchingpassword,secret,privateKey, … with astring/?string/untyped/mixedtype). Object-typed params and already-hashed/encoded names ($hashedPassword,$passwordHash) are ignored. Keeps credentials out of stack traces.
The codebase-wide "is
#[\SensitiveParameter]used anywhere?" coverage check is NOT a PHPStan rule — PHPStan rules are opt-in (a consumer must include this library's rules neon), so they cannot be relied on estate-wide. That check ships as an always-on pipeline tool instead. See SensitiveParameter usage check.
Configuring RequireSensitiveParameterAttributeRule
The rule is wired in rules-default.neon from a phpqaciSensitiveParameter parameters block.
Override any key in your qaConfig/phpstan.neon (deep-merged over the defaults):
parameters: phpqaciSensitiveParameter: # Case-insensitive substrings that mark a parameter NAME as a credential. namePatterns: - password - passwd - pwd - passphrase - secret - apiSecret - privateKey - credential - credentials # Case-insensitive substrings that mark a name as already hashed/encoded # (and therefore NOT plaintext sensitive). ignoreSubstrings: [hash, hashed, encoded, encrypted]
Optional rules (opt-in)
Ten additional rules ship as opt-in, split across two files:
rules-optional.neon— 6 generic rules suitable for any PHP projectrules-optional-symfony.neon— all generic rules + 4 Symfony/Doctrine-specific rules
To enable them, add an includes entry to your qaConfig/phpstan.neon.
Symfony projects — include the full Symfony set (automatically picks up new rules on upgrade):
includes: - ../vendor/lts/php-qa-ci/configDefaults/generic/phpstan.neon - ../vendor/lts/php-qa-ci/rules-optional-symfony.neon
Generic PHP projects — include the generic set only:
includes: - ../vendor/lts/php-qa-ci/configDefaults/generic/phpstan.neon - ../vendor/lts/php-qa-ci/rules-optional.neon
Cherry-pick individual rules (full control, manual updates required):
includes: - ../vendor/lts/php-qa-ci/configDefaults/generic/phpstan.neon rules: - LTS\PHPQA\PHPStan\Rules\ForbidNullCoalescingEmptyStringRule - LTS\PHPQA\PHPStan\Rules\ForbidSilentCatchRule # ... add only what you want
See docs/tools/phpstan.md for the full list of optional rules and descriptions.
SensitiveParameter usage check (always-on)
Unlike the PHPStan rules above (which are opt-in), php-qa-ci ships an always-on
pipeline tool that asserts the native #[\SensitiveParameter] attribute is used at
least once in your project's src/. PHP 8.2+ redacts a so-marked argument from
stack traces, keeping passwords / tokens / secrets out of logs and error reporters.
The check runs automatically as part of bin/qa for every consumer — no neon
include required. The scan is AST-based, so the attribute is never false-matched in
strings or comments.
- Run standalone:
vendor/bin/qa -t sensitiveParameterUsage(aliases:spu,sensitiveparameter). - Passes when ≥1
#[\SensitiveParameter]is found; fails (exit 1) when none is found.
Escape hatch (opt-out, on by default) — for projects that genuinely never handle
a sensitive parameter (e.g. pure tooling libraries). Add to qaConfig/qaConfig.inc.bash:
export useSensitiveParameterCheck=0
php-qa-ci itself is the canonical example: it handles no secrets, so it sets this flag
in its own qaConfig/qaConfig.inc.bash.
Estate-wide impact: because this is always on, every consumer's
bin/qanow requires either at least one#[\SensitiveParameter]annotation or the opt-out flag above. Most projects should add the annotation rather than opt out.
Full details: docs/tools/sensitiveParameterUsage.md.
Quick Setup Scripts
GitHub Actions Setup
Automatically install the GitHub Actions workflow for continuous integration:
vendor/lts/php-qa-ci/scripts/install-github-actions.bash
This will:
- Create
.github/workflows/qa.ymlwith an optimized QA pipeline - Auto-detect your PHP version from
composer.json - Configure smart caching for faster builds
- Set up artifact storage for test results
Branch Protection Setup
Configure GitHub branch protection rules with sensible defaults:
# Standard protection (admins can bypass) vendor/lts/php-qa-ci/scripts/setup-branch-protection.bash # Hardened protection (CI enforced for everyone) vendor/lts/php-qa-ci/scripts/setup-branch-protection.bash --harden
Prerequisites: Requires GitHub CLI (gh) installed and authenticated.
CI/CD Workflows
PHP-QA-CI includes three GitHub Actions workflows in .github/workflows/:
ci.yml-- Runs on push/PR tophp8.4, executesbash ci.bashqa.yml-- Template workflow for consuming projects (copy to your project)update-deps.yml-- Weekly scheduled workflow that updates all dependencies (Composer, PHARs via PHIVE, isolated Rector), runs the full QA pipeline, and creates an auto-merge PR if green
See GitHub Actions Integration for setup details.
Claude Code Integration
PHP-QA-CI integrates with Claude Code to provide development guardrails and automation.
Deployment
Deploy skills and hooks to your project:
vendor/lts/php-qa-ci/scripts/deploy-skills.bash vendor/lts/php-qa-ci .
This will:
- Copy hooks to
.claude/hooks/ - Register them in
.claude/settings.json - Detect and configure hooks-daemon if present (see hooks-daemon documentation for installation)
- Migrate from legacy classic hooks if found
Included Hooks
php-qa-ci__auto-continue.py-- Reduces confirmation promptsphp-qa-ci__prevent-destructive-git.py-- Blocks commands that destroy uncommitted changesphp-qa-ci__discourage-git-stash.py-- Discourages git stash with escape hatchphp-qa-ci__block-plan-time-estimates.py-- Prevents time estimates in plan documentsphp-qa-ci__validate-claude-readme-content.py-- Ensures docs contain instructions, not logsphp-qa-ci__enforce-markdown-organization.py-- Enforces doc organization
See .claude/hooks/README.md for detailed hook documentation after deployment.
Disabling Auto-Deployment (Dev / Staging / CI Hosts)
Skills, agents and hooks are deployed automatically on every composer install
and composer update via the SkillsDeployPlugin. This is intentional --
keeping .claude/ config consistent across projects is a core goal.
On hosts where this is unwanted (dev / staging deploys, build images, CI runners that aren't Claude Code environments) the deployment can leave the working tree dirty. Opt out by exporting:
export PHP_QA_CI_DISABLE_CONFIG_PUSH=true
When set (any truthy value -- true, 1, yes, on), the plugin logs that it
was disabled and exits without touching .claude/. When unset (the default), the
plugin logs the opt-out instructions every time it runs so deploy operators can
discover the flag.
Composer Plugins
PHP-QA-CI registers three Composer plugins:
- PhiveUpdatePlugin -- Manages PHAR installation via PHIVE
- SkillsDeployPlugin -- Deploys Claude Code skills and hooks
- PhpStanGuardPlugin -- Prevents
phpstan/phpstanfrom being installed alongside the PHAR
Docs
Comprehensive documentation is available in the ./docs folder:
- Pipeline Architecture -- Tool execution order and phases
- Tools Overview -- All tools with configuration details
- Configuration -- Customizing tool settings and overrides
- Coding Standards -- PHP CS Fixer and Rector configuration
- GitHub Actions Integration -- CI/CD setup guide
- Continuous Integration -- General CI usage and workflows
- Platform Detection -- Symfony/Laravel specific settings
Tool-specific documentation:
- PHPStan -- Static analysis configuration and custom rules
- PHPUnit -- Test runner configuration and modes
- Infection -- Mutation testing setup
Other Notes
Specify PHP Binary Path
If you are running multiple PHP versions, you can specify which one to use:
export PHP_QA_CI_PHP_EXECUTABLE=/bin/php84 vendor/bin/qa # Or inline: PHP_QA_CI_PHP_EXECUTABLE=/bin/php84 vendor/bin/qa
Running Specific Tools
# Run only PHPStan vendor/bin/qa -t stan # Run only PHP CS Fixer vendor/bin/qa -t fixer # Run on specific path vendor/bin/qa -t stan -p src/Domain
Branches
php8.4-- Default branch, targets PHP 8.4php8.3-- PHP 8.3 support
Long Term Support
This package was brought to you by Long Term Support LTD, a company run and founded by Joseph Edmonds.
You can get in touch with Joseph at https://ltscommerce.dev/
Check out Joseph's recent book The Art of Modern PHP 8