Skip to content

phpstan-warroom-rules — The Inspector General's Office

Single Composer package distributing canonical PHPStan rules that enforce war-room doctrine across script-development Laravel territories. Sister to fs-packages on the PHP side — fs-packages equips territories, phpstan-warroom-rules audits them.

Tech Stack

  • Language: PHP 8.3+ (uses private const string syntax)
  • Target framework: PHPStan 2.x (the package extends it)
  • Test: PHPUnit 11 with PHPStan\Testing\RuleTestCase fixtures
  • Format: Pint (canonical config from war-room templates/pint.json)
  • Publish: Packagist via OIDC Trusted Publishing — no stored tokens
  • CI: test → phpstan-self → format-check → release-on-tag

Architecture Overview

The package ships rules that sit at Level 2 of the war-room enforcement ladder (static analysis). Each rule maps to a doctrine source — an ADR or a war-room principle — and the class-level docblock names that source. When a rule is added, the docblock is the contract.

  • Namespace. ScriptDevelopment\PhpstanWarroomRules\ (PSR-4, src/).
  • Action namespace assumption. Rules that scope to Actions match App\Actions\* — Laravel convention used by every consuming territory. Onboarding a territory with a different namespace lifts this into a parameter rather than forks the rule.
  • No territory-specific exceptions in rule code. Per-territory false positives are suppressed via consumer phpstan.neon ignoreErrors blocks. The donor patterns from emmie that hardcoded class names (Terminology::class) were dropped during promotion.
  • Type extensions ride alongside rules. ConnectionTransactionReturnTypeExtension resolves $connection->transaction(fn () => $foo) to the closure's return type, enabling strict typing of transaction call sites.

Rules Shipped (Phase 1)

RuleIdentifierDoctrineDetects / Forbids
EnforceActionTransactionsRuleenforceActionTransactions.missingTransactionADR-0011Action execute() with ≥2 write operations not wrapped in ->transaction().
ForbidDatabaseManagerInActionsRuleforbidDatabaseManager.inActionADR-0021Action constructors injecting DatabaseManager. Inject ConnectionInterface instead.
ForbidAbortHelperRuleforbidAbortHelper.abortUsedWar-room "Explicit over implicit"abort() / abort_if() / abort_unless() anywhere in App\*. Throw an explicit HttpException subclass.
LogRulelogRule.logModificationADR-0001 §append-onlyupdate() / delete() calls on classes whose name contains "Log" / "logs" (case-insensitive).
ConnectionTransactionReturnTypeExtensionType extension; resolves transaction() closure return types.

Phase 2 will add EnforceExplicitHydrationRule for ADR-0019.

Key Decisions

DecisionStatusImpact
Canonical PHPStan Rules PackageAcceptedThis territory is the canonical home of the package — origin doctrine.
Action Class ArchitectureAcceptedEnforceActionTransactionsRule and ForbidDatabaseManagerInActionsRule enforce ADR-0011 at Level 2.
Audit Logging SystemAcceptedLogRule codifies ADR-0001 §append-only across all territories.
ADR GovernanceAcceptedGovernance applies; projections live in this territory's CLAUDE.md.

Versioning

Semantic versioning per ADR-0021:

  • Major — a rule changes in a way that surfaces new errors in code that previously passed (e.g., expanding the write-method list, tightening LogRule's match).
  • Minor — a new rule is added, or a rule gains an option that does not change defaults.
  • Patch — bug fixes, false-positive suppression, performance.

Consuming territories pin ^1.0. Any rule that would surface new errors in already-clean code waits for a major bump.

Publishing

  • Packagist under script-development/phpstan-warroom-rules.
  • Trusted Publishing via Packagist's GitHub integration — no stored tokens.
  • Branch protection on main: required PR + 1 approval, dismiss stale reviews, enforce on admins.
  • main is always release-ready. Release PRs move [Unreleased] in CHANGELOG.md to a versioned heading and tag the merge commit (v1.x.y).
  • Tag push triggers the Packagist publish pipeline.

Documentation

Public documentation lives in the package README.md on Packagist. The doctrine source for every rule is the corresponding ADR at adrs.script.nl. This territory does not maintain a separate documentation site — the rules are too few and the audience too narrow to justify one.

What This Territory Does Not Do

  • Does not enforce its rules on its own source. The rules target Laravel application code (App\Actions, App\*), not a static-analysis package.
  • Does not ship operational PHP code or services. It is a static-analysis library only.
  • Does not own doctrine. Doctrine lives in ADRs; this territory is the enforcement vehicle.

Architecture documentation for contributors and collaborators.