script-development / phpstan-warroom-rules
Canonical PHPStan rules enforcing war-room doctrine across script-development Laravel territories.
Package info
github.com/script-development/phpstan-warroom-rules
Type:phpstan-extension
pkg:composer/script-development/phpstan-warroom-rules
Requires
- php: ^8.3
- illuminate/cache: ^11.0 || ^12.0 || ^13.0
- illuminate/contracts: ^11.0 || ^12.0 || ^13.0
- illuminate/database: ^11.0 || ^12.0 || ^13.0
- illuminate/filesystem: ^11.0 || ^12.0 || ^13.0
- illuminate/log: ^11.0 || ^12.0 || ^13.0
- illuminate/mail: ^11.0 || ^12.0 || ^13.0
- nikic/php-parser: ^5.0
- phpstan/phpstan: ^2.0
- psr/log: ^3.0
Requires (Dev)
- laravel/pint: ^1.18
- phpunit/phpunit: ^11.0
README
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.
Why
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.
Installation
composer require --dev script-development/phpstan-warroom-rules
The 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
Rules
| 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. |
EnforceActionTransactionsRule — write-method list
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.
LogRule — false positives
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.php
Each 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.
Action namespace assumption
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.
Type extension
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.
Versioning
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).
License
MIT — see LICENSE.