Canonical PHPStan rules enforcing war-room doctrine across script-development Laravel territories.
Distributed via Composer as script-development/phpstan-warroom-rules. Doctrine source is ADR-0021.
Several doctrine claims need static-analysis enforcement that out-of-the-box PHPStan + Larastan cannot provide:
- Multi-write Actions must wrap operations in a database transaction.
- Audit log records are append-only.
- The
abort()family of helpers is forbidden in favor of explicit HTTP exception throws. - Action constructors inject
ConnectionInterface, neverDatabaseManager.
These rules originated inside emmie and have been promoted to a shared package so every consuming territory gets the same enforcement on composer require.
composer require --dev script-development/phpstan-warroom-rulesThe package ships with phpstan/extension-installer metadata. If you have the installer, the extension is auto-loaded. Otherwise, add it to your phpstan.neon:
includes:
- vendor/script-development/phpstan-warroom-rules/extension.neon| Rule | Identifier | Detects | Forbids / Requires |
|---|---|---|---|
EnforceActionTransactionsRule |
enforceActionTransactions.missingTransaction |
Action execute() methods |
If ≥2 write operations appear without ->transaction(), error. |
ForbidDatabaseManagerInActionsRule |
forbidDatabaseManager.inAction |
Action constructors | Constructor parameter typed DatabaseManager is an error. Inject ConnectionInterface instead. |
ForbidAbortHelperRule |
forbidAbortHelper.abortUsed |
Function calls | abort(), abort_if(), abort_unless() are errors. Throw an explicit HttpException subclass instead. |
LogRule |
logRule.logModification |
update() / delete() calls |
If the receiver type's class name contains "Log" or "logs" (case-insensitive), error. |
The rule counts the following methods as "writes":
save, saveQuietly, create, update, delete, forceDelete, sync, attach, detach, insert, upsert, updateOrCreate, firstOrCreate, push, restore, toggle, syncWithoutDetaching, syncWithPivotValues.
Calls on properties typed as non-database services (FilesystemManager, Filesystem, Cache\Repository, LogManager, LoggerInterface, Mailer) are excluded — $this->files->delete($path) does not trigger the rule.
The rule uses substring matching on class names. It will fire on classes named Catalog, Blog, Terminology, or any business model containing log as a substring. Suppress per-territory via phpstan.neon:
parameters:
ignoreErrors:
-
identifier: logRule.logModification
path: app/Models/Catalog.phpEach ignore should carry a comment with rationale. Future versions may add an explicit allow-list parameter — file an issue if you have a recurring need.
EnforceActionTransactionsRule and ForbidDatabaseManagerInActionsRule only fire on classes whose namespace starts with App\Actions. This matches the Laravel convention used in every script-development territory. Territories using a different actions namespace should open a PR to make this configurable.
ConnectionTransactionReturnTypeExtension is registered alongside the rules. It resolves the return type of $connection->transaction(fn () => $foo) to the closure's return type instead of mixed, enabling strict typing of transaction call sites.
Semantic versioning:
- Major — a rule's behavior 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 doesn't change defaults.
- Patch — bug fixes, false-positive suppression, performance improvements.
Pin to a major version (^1.0).
MIT — see LICENSE.