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).
ConnectionTransactionReturnTypeExtension——Type 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.