spatie/laravel-auto-discoverer
Fast, cached discovery of PHP structures in your codebase. Find classes, interfaces, traits, and enums by conditions like “implements interface” or “uses trait,” and get rich metadata. Ideal for automation, registration, and scanning in production.
Installation:
composer require spatie/php-structure-discoverer
For Laravel, publish the config:
php artisan vendor:publish --tag="structure-discoverer-config"
First Use Case:
Discover all classes implementing Arrayable in app/:
use Spatie\StructureDiscoverer\Discover;
$arrayableClasses = Discover::in(app_path())->classes()->implementing(\Illuminate\Contracts\Support\Arrayable::class)->get();
Discover::in(__DIR__)->get() to retrieve all structures (classes, interfaces, enums, traits).config/structure-discoverer.php (default: LaravelDiscoverCacheDriver).Discover::in(__DIR__)->classes()->get()Discover::in(__DIR__)->interfaces()->get()Discover::in(__DIR__)->enums()->get()Discover::in(__DIR__)->traits()->get()Discover::in(__DIR__)->extending(\Illuminate\Database\Eloquent\Model::class)->get();
Discover::in(__DIR__)->implementing(\JsonSerializable::class)->get();
Discover::in(__DIR__)->withAttribute(\Spatie\LaravelActivitylog\Traits\LogsActivity::class)->get();
Discover::in(__DIR__)->custom(fn($structure) => str_contains($structure->namespace, 'App\\Policies'))->get();
Discover::in(__DIR__)->classes()->implementing(\Arrayable::class)->get();
use Spatie\StructureDiscoverer\ConditionBuilder;
Discover::in(__DIR__)
->any(
ConditionBuilder::create()->classes(),
ConditionBuilder::create()->enums()
)
->get();
class PolicyScout extends \Spatie\StructureDiscoverer\StructureScout
{
protected function definition(): \Spatie\StructureDiscoverer\Discover
{
return Discover::in(app_path('Policies'))->classes();
}
}
$policies = PolicyScout::create()->get(); // Cached after first run
php artisan structure-scouts:cache
Discover::in(base_path())->parallel(100)->get(); // Scan 100 files per process
boot():
public function boot()
{
\Spatie\StructureDiscoverer\StructureScoutManager::add(\App\Scouts\PolicyScout::class);
}
$directories = [app_path('Models'), app_path('Policies')];
Discover::in(...$directories)->get();
NullDiscoverCacheDriver to bypass caching:
Discover::in(__DIR__)->withCache('test', new \Spatie\StructureDiscoverer\Cache\NullDiscoverCacheDriver())->get();
Performance in Development:
Discover::in(__DIR__)->withCache('dev', new \Spatie\StructureDiscoverer\Cache\NullDiscoverCacheDriver())->get();
NullDiscoverCacheDriver or clear cache frequently:
php artisan structure-scouts:clear
Chains Overhead:
extendsChain, implementsChain) is resource-intensive.Discover::in(__DIR__)->withoutChains()->extending(\Model::class)->get();
Case Sensitivity:
Sort::Name) may behave unexpectedly across OSes.Sort::CaseInsensitiveName for consistent results.Ignored Files:
vendor/, node_modules/) are not configurable via the package.DiscoverCondition.Parallel Scanning Limitations:
amphp/parallel and may not work in all environments (e.g., shared hosting).Discover::in(__DIR__)->get(); // No ->parallel()
full() to debug DiscoveredStructure objects:
$structures = Discover::in(__DIR__)->full()->get();
dd($structures[0]->extendsChain); // Example: Inspect inheritance chain
$cacheDriver = app(\Spatie\StructureDiscoverer\Cache\LaravelDiscoverCacheDriver::class);
$cacheDriver->has('your_scout_id'); // Verify cache existence
Custom Cache Drivers:
Implement DiscoverCacheDriver for specialized storage (e.g., Redis):
class RedisDiscoverCacheDriver implements \Spatie\StructureDiscoverer\Cache\DiscoverCacheDriver
{
public function has(string $id): bool { /* ... */ }
// Implement other methods...
}
Custom Conditions:
Extend DiscoverCondition for reusable logic:
class HasAttributeCondition extends \Spatie\StructureDiscoverer\DiscoverCondition
{
public function __construct(private string $attributeFqcn) {}
public function satisfies(DiscoveredStructure $structure): bool {
return in_array($this->attributeFqcn, $structure->attributes, true);
}
}
Laravel Service Provider Hooks:
Override scout directories in register():
$this->app->singleton('structure-discoverer.directories', fn() => [
app_path(),
database_path('Scouts'),
]);
Artisan Commands:
Extend StructureScoutsCommand to add custom cache warming logic:
class CustomScoutsCommand extends \Spatie\StructureDiscoverer\Console\StructureScoutsCommand
{
protected function getScoutClasses(): array {
return [\App\Scouts\CustomScout::class];
}
}
Cache Store:
The store key in config/structure-discoverer.php must match a Laravel cache driver (e.g., redis, database).
Fix: Set to null to use the default store:
'cache' => [
'driver' => \Spatie\StructureDiscoverer\Cache\LaravelDiscoverCacheDriver::class,
'store' => null, // Uses default Laravel cache
]
Directory Traversal:
Avoid traversing into vendor/ or node_modules/ to prevent performance issues.
Fix: Exclude directories in custom conditions:
Discover::in(__DIR__)->custom(fn($structure) => !str_contains($structure->file, 'vendor'))->get();
Namespace Conflicts:
Ensure scout classes are in a unique namespace to avoid collisions.
Fix: Use a dedicated namespace (e.g., App\Scouts).
How can I help you explore Laravel packages today?