spatie/laravel-auto-discoverer
Fast, cached discovery of PHP classes, interfaces, traits, and enums based on conditions. Scan directories to find structures implementing an interface, using attributes, extending classes, and more—ideal for auto-registration and production-ready performance.
Installation:
composer require spatie/php-structure-discoverer
For Laravel, use the dedicated package:
composer require spatie/laravel-auto-discoverer
First Use Case:
Discover all classes implementing Illuminate\Contracts\Support\Arrayable in your app:
use Spatie\StructureDiscoverer\Discover;
$arrayableClasses = Discover::in(app_path())
->classes()
->implementing(\Illuminate\Contracts\Support\Arrayable::class)
->get();
Where to Look First:
Discover facade/class for API reference.Discovering Classes by Traits/Interfaces:
// Find all classes using a custom trait
Discover::in(base_path('app'))
->classes()
->using(\App\Traits\HasSoftDeletes::class)
->get();
// Find all classes implementing a custom interface
Discover::in(base_path('app'))
->classes()
->implementing(\App\Contracts\Serializable::class)
->get();
Filtering by Namespace:
// Discover only in the 'App\Models' namespace
Discover::in(base_path('app/Models'))
->classes()
->get();
Caching Results (Laravel):
// Cache results for 1 hour
Discover::in(app_path())
->classes()
->implementing(\Arrayable::class)
->cacheFor(now()->addHour())
->get();
Integration with Laravel Service Providers:
// Bootstrapping discovered classes in a provider
public function boot()
{
$listeners = Discover::in(app_path())
->classes()
->using(\Illuminate\Contracts\Event\Dispatcher::class)
->get();
foreach ($listeners as $listener) {
event(new \App\Events\DiscoveredClass($listener));
}
}
Dynamic Discovery in Controllers/Middleware:
// Middleware to inject discovered services
public function handle($request, Closure $next)
{
$services = Discover::in(app_path('Services'))
->classes()
->implementing(\App\Contracts\ServiceContract::class)
->get();
app()->singleton(\App\Contracts\ServiceContract::class, $services[0]);
return $next($request);
}
Discovering Enums/Interfaces:
// Find all enums in the app
Discover::in(base_path('app'))
->enums()
->get();
// Find all interfaces with a specific method
Discover::in(base_path('app'))
->interfaces()
->withMethod('handle')
->get();
Cache Invalidation:
php artisan cache:clear
cacheFor() sparingly in development to avoid stale data.Namespace Scope:
Discover::in() is relative to the current working directory by default. Always use absolute paths (e.g., base_path('app')) for consistency.// ❌ Fails silently if 'app' directory doesn't exist
Discover::in('app')->classes()->get();
Fix: Use base_path('app') or add error handling:
try {
Discover::in('nonexistent')->classes()->get();
} catch (\Spatie\StructureDiscoverer\Exceptions\DirectoryNotFound $e) {
Log::error($e->getMessage());
}
Performance:
// ❌ Slow for monorepos
Discover::in(base_path())->classes()->get();
// ✅ Faster
Discover::in(base_path('app'))->classes()->get();
--optimize in Laravel for faster autoloading to reduce discovery time.False Positives in Interface/Trait Detection:
instanceof checks at runtime:
$classes = Discover::in(app_path())
->classes()
->implementing(\Arrayable::class)
->get();
$validClasses = array_filter($classes, fn($class) => class_implements($class, \Arrayable::class));
Laravel Package Auto-Discovery:
spatie/laravel-auto-discoverer, ensure your composer.json includes:
"extra": {
"laravel": {
"discover": ["spatie/laravel-auto-discoverer"]
}
}
composer dump-autoload after adding new discoverable classes.Leverage for Dynamic Configuration:
// Auto-register all event listeners
$listeners = Discover::in(app_path('Listeners'))
->classes()
->implementing(\Illuminate\Contracts\Queue\ShouldQueue::class)
->get();
foreach ($listeners as $listener) {
event(new \App\Events\QueueListenerRegistered($listener));
}
Combine with Laravel’s ClassLoader:
// Discover and instantiate classes dynamically
$services = Discover::in(app_path('Services'))
->classes()
->implementing(\App\Contracts\ServiceContract::class)
->get();
foreach ($services as $service) {
app()->bind(\App\Contracts\ServiceContract::class, $service);
}
Debugging:
->toArray() to inspect discovery results:
$result = Discover::in(app_path())
->classes()
->implementing(\Arrayable::class)
->toArray();
Discover::setVerbose(true);
Extending Functionality:
Spatie\StructureDiscoverer\Filters\Filter class:
class HasCustomMethodFilter extends Filter
{
public function __invoke($class): bool
{
return in_array('customMethod', get_class_methods($class));
}
}
Usage:
Discover::in(app_path())
->classes()
->filter(new HasCustomMethodFilter())
->get();
CI/CD Optimization:
if (app()->environment('ci')) {
Discover::in(app_path())
->classes()
->cacheFor(now()->addDays(7))
->get();
}
Avoid Over-Discovery:
vendor/, node_modules/, or tests/:
Discover::in(base_path('app'))
->ignoreDirectories(['tests', 'Models/Concerns'])
->classes()
->get();
How can I help you explore Laravel packages today?