Skip to content

Laravel support#294

Merged
janedbal merged 16 commits intomasterfrom
laravel-support
Mar 23, 2026
Merged

Laravel support#294
janedbal merged 16 commits intomasterfrom
laravel-support

Conversation

@janedbal
Copy link
Copy Markdown
Member

@janedbal janedbal commented Mar 1, 2026

Supported features

LaravelUsageProvider

  • Route registrationRoute::get(), post(), put(), patch(), delete(), any(), match(), resource(), apiResource()
    • Callable array syntax: [Controller::class, 'method']
    • Legacy string syntax: 'Controller@method'
    • Invokable controllers: Controller::class (marks __invoke + __construct)
  • Event listenersEvent::listen(), Event::subscribe()
  • Auto-discovered event listeners — classes with handle* or __invoke methods whose first parameter has a class type
  • Scheduled jobsSchedule::job()
  • Gate definitionsGate::define() (callable + invokable), Gate::policy()
  • Policy resolution$this->authorize() calls resolve policy class via Laravel's namespace convention (Models\Policies\)
  • Console commands__construct, handle on Illuminate\Console\Command subclasses
  • Jobs__construct, handle, failed, middleware, retryUntil, uniqueId, tags, backoff, uniqueVia, displayName on ShouldQueue / Dispatchable classes
  • Service providers__construct, register, boot
  • Middlewarehandle, terminate, __construct (detected by first parameter type Illuminate\Http\Request)
  • Notificationsvia, toMail, toArray, toDatabase, toBroadcast, toVonage, toSlack
  • Form requestsauthorize, rules, messages, attributes, prepareForValidation, passedValidation, failedValidation, failedAuthorization
  • Policiesbefore, viewAny, view, create, update, delete, restore, forceDelete (heuristic via \Policies\ namespace + Policy suffix)
  • Mailablesbuild, content, envelope, attachments, headers
  • Broadcast eventsbroadcastWith, broadcastAs, broadcastWhen
  • JSON resourcespaginationInformation
  • Notifiable routingrouteNotificationFor* methods

EloquentUsageProvider

  • Model methods — constructor, boot, booted, casts, newFactory
  • Query scopesscope* methods
  • Relationships — methods returning Illuminate\Database\Eloquent\Relations\*
  • Attribute accessors — methods returning Illuminate\Database\Eloquent\Casts\Attribute
  • Legacy accessors/mutatorsget*Attribute() / set*Attribute()
  • ObserversModel::observe() calls + #[ObservedBy] attribute (all observer event methods)
  • Factoriesdefinition, configure
  • Seedersrun
  • Migrationsup, down

@seryak
Copy link
Copy Markdown

seryak commented Mar 21, 2026

I’ve now reviewed and tested it on my side, and it looks like an acceptable integration for Laravel.

janedbal added 15 commits March 23, 2026 14:59
…ler heuristic

Replace the broad reflection heuristic that marked all public controller
methods as used with precise AST-based detection of:

- Route::get/post/put/patch/delete/any/match — extract [Class, 'method'] from action arg
- Route::resource — mark 7 CRUD methods (index/create/store/show/edit/update/destroy)
- Route::apiResource — mark 5 CRUD methods (index/store/show/update/destroy)
- Event::listen — mark handle() + __construct() on listener class
- Event::subscribe — mark subscribe() + __construct() on subscriber class
- Schedule::job — mark handle() + __construct() on job class

All other reflection-based checks (Eloquent, commands, jobs, middleware,
notifications, form requests, factories, seeders, policies, mailables,
broadcast events, JSON resources, validation rules) remain unchanged.
…route __construct, invokable controllers

- Move routeNotificationFor* detection from Notification to Notifiable/RoutesNotifications trait users
- Replace deprecated HandlesAuthorization trait check with *Policy naming convention for policy detection
- Mark __construct as used for controllers referenced in Route:: registrations
- Support invokable controllers (Route::get('/path', Controller::class) marking __invoke + __construct)
- Detect migration classes (up/down methods)
- Detect observers via Model::observe() calls and #[ObservedBy] attribute
- Mark middleware __construct as used alongside handle/terminate
- Detect $this->authorize('ability', $model) in controllers using AuthorizesRequests trait
- Resolve model → policy class using Laravel naming convention
- Support Gate::define() for explicit ability registration
- Support Gate::policy() to mark all public methods on registered policy classes
- Convert kebab-case ability names to camelCase (e.g. 'force-download' → 'forceDownload')
Other providers (Symfony, Doctrine, Twig, Nette) install real packages
as dev dependencies. Laravel was the only one using ~210 lines of inline
stubs. Install laravel/framework and use real Illuminate classes instead.
Laravel 11+ requires PHP ^8.2, which breaks CI on PHP 8.1.
Laravel 10 requires PHP ^8.1 and all tests pass with it.
Eloquent (illuminate/database) can be used standalone without
laravel/framework. The new EloquentUsageProvider auto-enables when
illuminate/database is installed, covering Model methods, Factory,
Seeder, Migration, and Observer detection.
Methods declared in vendor interfaces/parent classes are already handled
by VendorUsageProvider, so the Laravel provider doesn't need to mark them:

- broadcastOn: declared in ShouldBroadcast interface
- toArray, with, additional: declared in JsonResource parent class
- validate: declared in ValidationRule interface
- passes, message: declared in Rule interface

The remaining checks cover methods called via method_exists() magic
(broadcastWith, broadcastAs, broadcastWhen, paginationInformation)
which VendorUsageProvider cannot detect.
The isPolicyMethod() fallback previously matched any class ending in
"Policy" (e.g. RetentionPolicy, CachePolicy), which could suppress
legitimate dead code warnings on non-authorization classes that happen
to have methods named view, create, update, delete, etc.

Laravel's own guessPolicyName() always generates candidates within a
\Policies\ namespace segment. Policies registered outside that
convention (via Gate::policy() or #[UsePolicy]) are already handled
by the precise detection layer. So we can safely require \Policies\
in the class name for the heuristic fallback.
- Route::match with invokable controller (class string at arg index 2)
- Job methods uniqueVia and displayName
- FormRequest methods failedValidation and failedAuthorization
- #[ObservedBy] with array syntax
The previous implementation only checked {rootNamespace}\Policies\{Model}Policy,
which missed models in sub-namespaces (e.g. App\Models\Admin\User would only
try App\Policies\UserPolicy, missing App\Policies\Admin\UserPolicy).

Now mirrors Laravel's Gate::guessPolicyName() candidate list:
- \Models\ → \Models\Policies\ replacement
- \Models\ → \Policies\ replacement
- Segment-based candidates from most to least specific

All existing candidates where the class exists are emitted as usages,
rather than just the first match.
Laravel supports 'Controller@method' strings in route definitions.
Parse these alongside array callables and class-string invokables
in Route::get/post/put/patch/delete/any and Route::match calls.
Laravel automatically discovers event listeners by scanning the
Listeners directory for classes with public handle*/__invoke methods
that have a class-typed first parameter (the event). This happens
without any explicit Event::listen() registration.

Previously, only explicitly registered listeners (via Event::listen
AST calls) had their handle() marked as used. Auto-discovered
listeners would be falsely reported as dead code.

The detection mirrors Laravel's DiscoverEvents logic:
- Public methods matching handle* or __invoke
- First parameter must have a non-builtin class type-hint
- Union types (e.g. EventA|EventB) are supported
- Constructor is also marked when the class qualifies

Placed last in the shouldMarkAsUsed chain so it only triggers for
classes not already identified as Commands, Jobs, Middleware, etc.
@janedbal janedbal marked this pull request as ready for review March 23, 2026 14:25
@janedbal janedbal merged commit 8fe7099 into master Mar 23, 2026
32 checks passed
@janedbal janedbal deleted the laravel-support branch March 23, 2026 14:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants