PHP Quality Toolchain - Rector → ECS → PHPStan → phpinsights
A pragmatic, end-to-end workflow that keeps your PHP codebase up-to-date, consistently formatted, statically safe, and objectively measurable-from local development in VS Code all the way to CI/CD.
0. Tool Roles at a Glance
| Layer | Core Question | Tool |
|---|---|---|
| Automated refactoring / upgrade | "Can we transform the code to modern syntax & APIs?" | Rector |
| Coding-standard enforcement & auto-fixing | "Does the code follow our PSR-12 / custom style?" | Easy Coding Standard (ECS) |
| Static analysis (type & logic errors) | "Will this blow up at runtime or break contracts?" | PHPStan (level 6-9) |
| Quality & architecture metrics | "How complex/cohesive/maintainable is the codebase?" | phpinsights |
Run them in the order transform → style → static analysis → metrics, so each sees the final state of the code.
Installation (Composer)
Install all CLI tools as dev-dependencies:
composer require --dev \
rector/rector \
symplify/easy-coding-standard \
phpstan/phpstan \
nunomaduro/phpinsights \
phpro/grumphp
Keep them in require-dev so production deployments stay lean.
1. Local Developer Experience (VS Code)
1.1 Extension lineup
| Purpose | VS Code extension |
|---|---|
| Language features & IntelliSense | bmewburn.vscode-intelephense-client |
| Live static analysis | swordev.phpstan |
| On-save formatting (ECS) | azdanov.vscode-easy-coding-standard |
1.2 VS Code Settings
{
// --- Formatter ---
"[php]": {
"editor.defaultFormatter": "azdanov.vscode-easy-coding-standard",
"editor.formatOnSave": true
},
"easyCodingStandard.executablePath": "./vendor/bin/ecs",
// --- PHPStan live analysis ---
"phpstan.executablePath": "./vendor/bin/phpstan",
"phpstan.level": 6,
"phpstan.autoAnalysis": "onSave",
"phpstan.memoryLimit": "1G"
}
1.3 Composer scripts (local shortcuts)
{
"scripts": {
"rector": "rector process --dry-run",
"ecs": "ecs check src tests",
"ecs-fix": "ecs fix",
"phpstan": "phpstan analyse --error-format=table",
"phpstan-baseline": "phpstan analyse --generate-baseline",
"insights": "phpinsights --no-interaction --ansi --min-quality=85",
"insights-baseline": "phpinsights --baseline",
"rector-upgrade": "rector process --config rector.php",
"quality": "composer rector && composer ecs && composer phpstan && composer insights"
}
}
With these aliases you can run composer quality (or any individual task) from any shell and reuse the same commands inside CI and VS Code tasks.
1.4 VS Code Tasks
{
"version": "2.0.0",
"tasks": [
{
"label": "rector-dry-run",
"type": "shell",
"command": "composer" ,
"args": ["rector"],,
"args": ["--dry-run"],
"problemMatcher": []
},
{
"label": "ecs-check",
"type": "shell",
"command": "composer",
"args": ["ecs"],
"problemMatcher": []
},
{
"label": "phpstan",
"type": "shell",
"command": "composer",
"args": ["phpstan"],
"problemMatcher": []
},
{
"label": "phpinsights",
"type": "shell",
"command": "composer",
"args": ["insights"],
"problemMatcher": []
},
{
"label": "quality-all",
"dependsOn": [
"rector-dry-run",
"ecs-check",
"phpstan",
"phpinsights"
],
"dependsOrder": "sequence",
"group": { "kind": "build", "isDefault": true }
}
]
}
}
Daily Local Flow
Save file → ECS formats, PHPStan errors appear live.
Ctrl+Shift+B (Run Build Task) → runs full quality gate in terminal.
2. Git Hooks with GrumPHP
composer require --dev phpro/grumphp
vendor/bin/grumphp configure # creates grumphp.yml
auto grumphp git:init # installs pre-commit hook
tasks:
rector: ~
ecs: ~
phpstan:
configuration: phpstan.neon
phpinsights:
min_quality: 85
The commit is rejected if any task fails-guaranteed team-wide consistency.
3. CI Pipeline (GitHub Actions)
ame: Quality-Gate
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/composer-install
- name: Rector
run: vendor/bin/rector --dry-run
- name: ECS
run: vendor/bin/ecs check src tests
- name: PHPStan
run: vendor/bin/phpstan analyse --error-format=github
- name: PHP Insights
run: |
vendor/bin/phpinsights \
--min-quality=85 \
--min-complexity=90 \
--min-architecture=80 \
--format=json > build/phpinsights.json
- uses: actions/upload-artifact@v4
with:
name: insights-${{ github.sha }}
path: build/phpinsights.json
- Order: Rector → ECS → PHPStan → phpinsights.
- Fail-fast: each step exits non-zero on violations, turning the PR status red.
- Annotations:
--error-format=githubshows inline comments.
4. Tracking Quality over Time
- Store
build/phpinsights.jsonfrom every CI run. - Append the four top-level scores to
quality-history.csv:
jq -r '.metrics | [.quality,.complexity,.architecture,.style] | @csv' \
build/phpinsights.json \
| awk -v sha=$GITHUB_SHA -v d=$(date +%F) '{print sha","d","$0}' >> quality-history.csv
- Feed the CSV into Grafana or render a README badge via Shields.io.
- Ratchet up
--min-quality(and friends) 1-2 points each sprint.
5. Baselines & Progressive Tightening
- PHPStan -
vendor/bin/phpstan analyse --generate-baseline→ commitphpstan-baseline.neon. Remove a few entries each week. - phpinsights - keep a baseline file or rely solely on the min-score flags.
- ECS / Rector - inherently auto-fix or fail on diff; no baseline needed.
6. Visual Overview
flowchart TD A[Save file] --> F(ECS format) F --> S(PHPStan live) A -->|Commit| H[GrumPHP hook] H --> R1[Rector] R1 --> E1[ECS] E1 --> P1[PHPStan] P1 --> I[phpinsights] H -->|Push| CI CI --> R2[Rector] R2 --> E2[ECS] E2 --> P2[PHPStan] P2 --> I2[phpinsights] I2 --> DB[(Quality history)] DB --> Grafana
Quick-reference commands
| Task | Command |
|---|---|
| Run full chain locally | composer quality (defined in composer.json scripts) |
| Generate PHPStan baseline | vendor/bin/phpstan analyse --generate-baseline |
| Generate phpinsights baseline | vendor/bin/phpinsights --baseline |
| Fix style only | vendor/bin/ecs fix |
| Rector upgrade to PHP 8.3 | vendor/bin/rector process --config rector.php |
Gist (private): https://gist.github.com/ottsch/8542f41f61c173c03f94e5153039c9de