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

  1. Save file → ECS formats, PHPStan errors appear live.

  2. 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

4. Tracking Quality over Time

  1. Store build/phpinsights.json from every CI run.
  2. 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
  1. Feed the CSV into Grafana or render a README badge via Shields.io.
  2. Ratchet up --min-quality (and friends) 1-2 points each sprint.

5. Baselines & Progressive Tightening

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