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 the app directory:
use Spatie\StructureDiscoverer\Discover;
$arrayableClasses = Discover::in(app_path())->classes()->implementing(\Illuminate\Contracts\Support\Arrayable::class)->get();
Where to Look First:
config/structure-discoverer.php for ignored files/directories and cache settings.structure-scouts:cache and structure-scouts:clear for production optimization.Basic Discovery:
// Discover all classes in a directory
Discover::in(app_path('Models'))->classes()->get();
// Discover enums with a specific namespace
Discover::in(app_path('Enums'))->enums()->custom(fn($structure) => str_starts_with($structure->namespace, 'App\\Enums\\'))->get();
Conditional Discovery:
// Classes extending a base model
Discover::in(app_path('Models'))->extending(\Illuminate\Database\Eloquent\Model::class)->get();
// Classes using a specific attribute
Discover::in(app_path())->withAttribute(\Spatie\LaravelActivitylog\Traits\LogsActivity::class)->get();
Combining Conditions:
// Classes OR enums implementing interfaces
Discover::in(app_path())
->any(
ConditionBuilder::create()->classes()->implementing(\JsonSerializable::class),
ConditionBuilder::create()->enums()->implementing(\Stringable::class)
)
->get();
Caching with Scouts:
// Define a scout in `app/Scouts/`
class UserModelScout extends StructureScout {
protected function definition(): Discover {
return Discover::in(app_path('Models'))->classes()->extending(\Illuminate\Database\Eloquent\Model::class);
}
}
// Use the scout (cached after first run)
$models = UserModelScout::create()->get();
Parallel Discovery (for large codebases):
Discover::in(base_path())->parallel(100)->get();
Laravel Service Providers:
Register scouts in register():
StructureScoutManager::add(\App\Scouts\UserModelScout::class);
Dynamic Directory Scanning:
Use app_path() or base_path() dynamically:
$directories = [app_path('Models'), app_path('Policies')];
Discover::in(...$directories)->classes()->get();
Metadata Utilization: Fetch full structure details for introspection:
$structures = Discover::in(app_path())->full()->get();
foreach ($structures as $structure) {
if ($structure instanceof DiscoveredClass) {
dump($structure->extendsChain); // Full inheritance chain
}
}
Testing:
Use NullDiscoverCacheDriver to bypass caching:
Discover::in(__DIR__)->withCache('test', new NullDiscoverCacheDriver())->get();
Cache Invalidation:
php artisan structure-scouts:cache after adding new scouts or code changes.php artisan structure-scouts:clear && php artisan structure-scouts:cache
Performance Spikes:
->parallel()) may overload systems with many small files.ignored_files in config.Chains Overhead:
->withoutChains()) speeds up queries but may miss indirect relationships.->extendingWithoutChain() or ->implementingWithoutChain() selectively.Namespace Conflicts:
App\ vs app\).->custom(fn($structure) => str_contains(strtolower($structure->namespace), 'app\\'))
Attribute Detection:
withAttribute() may fail if attributes are not fully loaded (e.g., in compiled code).Verify Discovery Scope:
// Log directories being scanned
Discover::in(app_path())->debug()->get();
Inspect Cached Data:
// Check cache keys (Laravel)
\Spatie\StructureDiscoverer\Cache\LaravelDiscoverCacheDriver::getPrefix() . '*';
Slow Queries:
File Permissions:
storage/framework/cache) are writable:
chmod -R 755 storage/
Custom Cache Drivers:
Extend DiscoverCacheDriver for database-backed caching:
class DatabaseDiscoverCacheDriver implements DiscoverCacheDriver {
public function has(string $id): bool { /* ... */ }
// Implement other methods
}
Dynamic Scout Registration: Auto-discover scouts in a directory:
$scouts = app()->tag('structure-scout');
foreach ($scouts as $scout) {
StructureScoutManager::add($scout);
}
Metadata Enrichment:
Add custom properties to DiscoveredStructure by extending the class:
class CustomDiscoveredClass extends DiscoveredClass {
public function isTestable(): bool {
return str_contains($this->name, 'Test');
}
}
Conditional Logic: Create reusable conditions:
class HasCastAttribute extends DiscoverCondition {
public function satisfies(DiscoveredStructure $structure): bool {
return in_array(\Spatie\LaravelActivitylog\Traits\LogsActivity::class, $structure->attributes);
}
}
Sorting Customization:
Add custom sort options by extending the Sort enum:
enum CustomSort implements Sort {
case LastModified;
// Implement comparison logic
}
Config Overrides:
Override cache settings in config/structure-discoverer.php:
'cache' => [
'driver' => \Spatie\StructureDiscoverer\Cache\DatabaseDiscoverCacheDriver::class,
'store' => 'redis',
],
Artisan Command Hooks:
Extend StructureScoutsCommand to add custom logic:
protected $signature = 'structure-scouts:cache {--directory=*}';
Package Development:
Use StructureScoutManager::add() in the package’s service provider to register scouts globally.
Testing:
Mock the Discover facade or use NullDiscoverCacheDriver in tests:
$this->app->singleton(Discover::class, function () {
return Discover::in(__DIR__)->withCache('test', new NullDiscoverCacheDriver());
});
How can I help you explore Laravel packages today?