PHPackages                             innocenzi/discovery-for-laravel - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. innocenzi/discovery-for-laravel

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

innocenzi/discovery-for-laravel
===============================

Discover code anywhere in your Laravel codebase without relying on path conventions

v0.3.2(2mo ago)7805↓18.9%MITPHPPHP ~8.5

Since Apr 1Pushed 2mo agoCompare

[ Source](https://github.com/innocenzi/discovery-for-laravel)[ Packagist](https://packagist.org/packages/innocenzi/discovery-for-laravel)[ GitHub Sponsors](https://github.com/innocenzi)[ RSS](/packages/innocenzi-discovery-for-laravel/feed)WikiDiscussions main Synced 4w ago

READMEChangelog (7)Dependencies (11)Versions (8)Used By (0)

Discovery for Laravel
=====================

[](#discovery-for-laravel)

Automatically locate controller actions, console commands, configuration files and other components of your application without relying on filesystem-based conventions nor manual configuration.

```
composer require innocenzi/discovery-for-laravel
```

What it does
------------

[](#what-it-does)

If you ever wanted to architecture your application however you want, for instance by organizing your code in modules or in vertical slices, you probably had to give up on some of Laravel's conveniences, such as automatic Artisan command registration.

This package brings [Tempest's discovery](https://tempestphp.com/3.x/essentials/discovery) into Laravel applications, which allows for:

- Registering routes from controller method attributes,
- Registering artisan commands by discovering command classes,
- Loading modular config files ending with `.config.php`,
- Registering container bindings through dedicated initializer classes,
- Registering global middleware using attributes.

Of course, each of these features can easily be [disabled individually](#discoveries) if you don't want them.

Installation
------------

[](#installation)

Install via Composer:

```
composer require innocenzi/discovery-for-laravel

# Optional, if you need to customize behavior
php artisan vendor:publish --provider="Discovery\DiscoveryServiceProvider"
```

That's it, you don't need to do anything else. You can read what the built-in [discoveries](#discoveries) do to understand how you can benefit from them.

Production
----------

[](#production)

To avoid any performance overhead in production, it is important to generate the discovery cache. The cache will store all discovered items, preventing the need to scan the filesystem.

You may do so by adding `php artisan discovery:generate` to your deployment script, typically before the `optimize` step.

```
php artisan discovery:generate
php artisan optimize
```

Discoveries
-----------

[](#discoveries)

By default, we provide the ability to discover [artisan commands](#artisan-commands), [event handlers](#event-handlers), [controller actions](#routing), [configuration files](#configuration-files), [schedules](#scheduling), [global middleware](#global-middleware) and [dependency initializers](#dependency-initializers).

Of course, each of these can be disabled individually by updating the `skip_classes` option in `config/discovery.php`.

You can also [create your own discoveries](https://tempestphp.com/3.x/essentials/discovery#implementing-your-own-discovery) if you have another use case for it.

### Artisan commands

[](#artisan-commands)

Any class extending `Illuminate\Console\Command` is discovered and registered, no matter where it's placed. It does not have to be in the `app/Console/Commands` directory.

```
namespace Module\Reports;

use Illuminate\Console\Attributes\Signature;
use Illuminate\Console\Command;

#[Signature('reports:prune')]
final class PruneReportsCommand extends Command
{
    public function handle(): int
    {
        // ...

        return self::SUCCESS;
    }
}
```

### Event handlers

[](#event-handlers)

Methods annotated with the `Discovery\Events\EventHandler` attribute will be discovered and registered as event listeners. The method must accept a single parameter, which is the event class to listen to:

```
namespace Modules\Billing;

use Discovery\Events\EventHandler;

final class InvoiceEventHandler
{
    #[EventHandler]
    public function onInvoicePaid(InvoicePaid $event): void
    {
        // ...
    }
}
```

### Scheduling

[](#scheduling)

Methods annotated with `Discovery\Scheduling\Schedule` are discovered and registered as scheduled tasks.

You can use either one of the built-in `Discovery\Scheduling\Every` values or a raw cron expression:

```
namespace Modules\Billing;

use Discovery\Scheduling\Every;
use Discovery\Scheduling\Schedule;

final class BillingScheduler
{
    #[Schedule(Every::SECOND)]
    public function billEverySecond(): void
    {
        // ...
    }

    #[Schedule(Every::DAY, time: '13:30')]
    public function billDaily(): void
    {
        // Runs every day at 13:30.
    }
}
```

### Routing

[](#routing)

One of the best features of discovery is the ability to register routes from attributes on controller methods.

This is something that Spatie [implemented](https://github.com/spatie/laravel-route-attributes) when PHP attributes were initially released, but for some reason, this practice was never really popular in the Laravel ecosystem.

If you want to see the routes your application has, you can use `php artisan route:list` as usual. With attribute-based routes, you no longer need to go back-and-forth between your `routes/web.php` and your controllers—and for large applications, you no longer have to deal with gigantic route files.

This example declares two routes: `GET /billing/invoices` and `POST /billing/invoices`:

```
namespace Module\Billing;

use Discovery\Routing\Api;
use Discovery\Routing\Get;
use Discovery\Routing\Post;
use Discovery\Routing\Prefix;
use Discovery\Routing\Web;

#[Prefix(name: 'billing', uri: 'billing')]
final class InvoiceController
{
    #[Get(uri: 'invoices', name: 'index'), Web]
    public function index()
    {
        // ...
    }

    #[Post(uri: 'invoices', name: 'store'), Web]
    public function store()
    {
        // ...
    }
}
```

#### Decorators

[](#decorators)

The `#[Prefix]` and `#[Web]` attributes in the example above are route decorators—another concept [borrowed from Tempest](https://tempestphp.com/3.x/essentials/routing#route-decorators).

A decorator is a class that modifies the underlying route definition in some way. In this case, the `Web` decorator pushes the route into the `web` middleware group, and the `Prefix` decorator adds a URI prefix and a name prefix to all routes in the controller.

They are a great alternative to the `group` method of Laravel's router. This is how the `Web` one is implemented:

```
use Attribute;

#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
final class Web implements RouteDecorator
{
    public function decorate(Route $route): Route
    {
        $route->middleware[] = 'web';

        return $route;
    }
}
```

### Configuration files

[](#configuration-files)

Any file ending in `.config.php` will be registered under the `` namespace. This is useful to co-locate configuration files with the components they configure.

```
// src/Modules/Billing/billing.config.php
return [
    'enabled' => true,
    'retry_attempts' => 3,
];
```

The example above becomes available as `config('billing.enabled')`.

### Dependency initializers

[](#dependency-initializers)

This package provides [dependency initializers](https://tempestphp.com/3.x/essentials/container#dependency-initializers), a concept borrowed from Tempest. Initializers are simple classes responsible for initializing and configuring a specific dependency.

In a typical Laravel application, this is done in service providers. Using this package, you can create a class implementing `Discovery\Container\Initializer` anywhere (preferably, near related code), and it will be automatically discovered and registered in the container.

```
namespace Modules\Strip;

use Discovery\Container\Initializer;
use Illuminate\Container\Attributes\Singleton;
use Psr\Container\ContainerInterface;

#[Singleton]
final class StripeClientInitializer implements Initializer
{
    public function initialize(ContainerInterface $container): StripeClient
    {
        return new StripeClient(
            key: config('services.stripe.key'),
        );
    }
}
```

To register the initialized service as a singleton, add the built-in `Illuminate\Container\Attributes\Singleton` attribute on the initializer class.

### Global middleware

[](#global-middleware)

Classes annotated with `Discovery\Routing\Middleware` are pushed into the selected middleware group. Again, this class may be located anywhere in the application, and it will be automatically discovered and registered.

```
namespace Infrastructure;

use Discovery\Routing\Middleware;

#[Middleware('api')]
final class TraceRequestId
{
    public function __invoke(mixed $request, \Closure $next): mixed
    {
        // ...

        return $next($request);
    }
}
```

Troubleshooting
---------------

[](#troubleshooting)

- Discovery relies on reflection and filesystem scanning. If you don't want some files to be loaded at discovery time, you may adapt `skip_matches` in `config/discovery.php` to exclude them.

###  Health Score

43

—

FairBetter than 90% of packages

Maintenance86

Actively maintained with recent releases

Popularity24

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity46

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~3 days

Total

7

Last Release

72d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/16060559?v=4)[Enzo Innocenzi](/maintainers/innocenzi)[@innocenzi](https://github.com/innocenzi)

---

Top Contributors

[![innocenzi](https://avatars.githubusercontent.com/u/16060559?v=4)](https://github.com/innocenzi "innocenzi (43 commits)")

---

Tags

discoverylaravelphptempest

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/innocenzi-discovery-for-laravel/health.svg)

```
[![Health](https://phpackages.com/badges/innocenzi-discovery-for-laravel/health.svg)](https://phpackages.com/packages/innocenzi-discovery-for-laravel)
```

###  Alternatives

[illuminate/support

The Illuminate Support package.

630113.0M40.5k](/packages/illuminate-support)[psalm/plugin-laravel

Psalm plugin for Laravel

3355.3M341](/packages/psalm-plugin-laravel)[illuminate/container

The Illuminate Container package.

31182.0M2.3k](/packages/illuminate-container)[illuminate/collections

The Illuminate Collections package.

27078.0M1.1k](/packages/illuminate-collections)[illuminate/events

The Illuminate Events package.

13557.0M2.1k](/packages/illuminate-events)[illuminate/config

The Illuminate Config package.

10944.5M2.5k](/packages/illuminate-config)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
