edalzell / dead-code-detector-laravel-actions
PHPStan extensions teaching shipmonk/dead-code-detector about lorisleiva/laravel-actions
Package info
github.com/edalzell/dead-code-detector-laravel-actions
pkg:composer/edalzell/dead-code-detector-laravel-actions
Requires
- php: ^8.2
- phpstan/phpstan: ^2.0
- shipmonk/dead-code-detector: ^1.4
Requires (Dev)
- laravel/pint: ^1.14
- lorisleiva/laravel-actions: ^2.12
- pestphp/pest: ^3.0 || ^4.0
Suggests
- lorisleiva/laravel-actions: Needed for LaravelActionsUsageProvider, LaravelActionsRunUsageProvider and ActionListenerUsageExcluder to match anything
Provides
None
Conflicts
None
Replaces
None
README
Two usage providers and one usage excluder for shipmonk/dead-code-detector, covering lorisleiva/laravel-actions.
This is a PHPStan extension, not a Laravel package. There is no service provider and nothing to publish.
For the Eloquent conventions shipmonk misses — trait hooks and #[Scope] attributes — see edalzell/dead-code-detector-laravel.
Installation
composer require --dev edalzell/dead-code-detector-laravel-actions
With phpstan/extension-installer the extension registers itself. Without it, add one line:
includes: - vendor/shipmonk/dead-code-detector/rules.neon - vendor/edalzell/dead-code-detector-laravel-actions/extension.neon
lorisleiva/laravel-actions is a suggestion, not a requirement. All three extensions match on trait names as strings and never reference a class from that package, so they load and do nothing when it is absent.
What each extension does
LaravelActionsUsageProvider — laravel-actions resolves a set of methods by name through its decorators: the four entrypoint adapters, and the validation and response hooks. Nothing calls them, so all of them read as dead. The list is literal rather than a prefix match, so a typo in an Action still fails loudly, and it is scoped to classes actually using one of the package's traits, so a rules() on an unrelated class is still reported.
LaravelActionsRunUsageProvider — Foo::run() reaches Foo::handle() through AsObject's trait body, which lives in vendor and is never analysed, so the detector never sees the call. Every Action used to pass anyway, by accident: shipmonk's Laravel provider marks any public handle* whose first parameter is class-typed as an auto-discovered listener, which every Action taking a model happened to satisfy. One with a scalar first parameter read as dead however many callers it had. This records the call the trait hides, for run, runIf and runUnless.
ActionListenerUsageExcluder — the flip side of that heuristic. Because it marks any class-typed-first-parameter handle as a listener, a genuinely dead Action taking a model was invisible: one shipped with no caller for a whole phase and nothing reported it. This subtracts that one invented usage — matched by the note shipmonk attaches to it, so real calls are untouched — for classes using AsObject but not AsListener. An Action that really is a listener keeps the heuristic, and so does the constructor, which laravel-actions resolves through the container either way.
Testing
composer test
The suite runs PHPStan over tests/Fixtures four times: once with the shipped extension.neon, and once per extension with that one service left out. Each run asserts the exact set of members reported dead, so removing any extension turns a test red with a named fixture rather than quietly changing nothing.
Changelog
Please see CHANGELOG for more information on what has changed recently.
License
The MIT License (MIT). Please see License File for more information.