spatie/php-structure-discoverer
Discover PHP classes, interfaces, traits, and enums that match conditions (e.g., implement an interface) across your project. Fast scanning with built-in caching and rich metadata—ideal for auto-registration, tooling, and framework integrations.
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 your 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 configuration options (ignored files, cache settings).app/Providers/AppServiceProvider.php (or your package's service provider) for registering structure scouts.Basic Discovery:
// Get all classes in a directory
Discover::in(app_path('Models'))->classes()->get();
// Get enums with a specific namespace
Discover::in(app_path())->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(\Illuminate\Foundation\Testing\RefreshDatabase::class)->get();
Define a Scout:
// app/Scouts/ArrayableScout.php
use Spatie\StructureDiscoverer\StructureScout;
class ArrayableScout extends StructureScout
{
protected function definition(): Discover
{
return Discover::in(app_path())->classes()->implementing(\Illuminate\Contracts\Support\Arrayable::class);
}
}
Register and Use:
// In a ServiceProvider
StructureScoutManager::add(ArrayableScout::class);
// Usage
$arrayableClasses = ArrayableScout::create()->get(); // Cached after first run
// Classes OR enums implementing Arrayable/Stringable
Discover::in(app_path())
->any(
ConditionBuilder::create()->exact(
ConditionBuilder::create()->classes()->implementing(\Illuminate\Contracts\Support\Arrayable::class)
),
ConditionBuilder::create()->exact(
ConditionBuilder::create()->enums()->implementing(\Stringable::class)
)
)
->get();
// Scan 100 files in parallel
Discover::in(app_path())->parallel(100)->get();
Requires amphp/parallel:
composer require amphp/parallel
$structures = Discover::in(app_path())->full()->get();
foreach ($structures as $structure) {
if ($structure instanceof \Spatie\StructureDiscoverer\DiscoveredClass) {
dump($structure->extendsChain); // Full inheritance chain
}
}
Service Providers:
Register scouts in AppServiceProvider@boot():
public function boot()
{
StructureScoutManager::add(\App\Scouts\ArrayableScout::class);
}
Artisan Commands: Cache all scouts during deployment:
php artisan structure-scouts:cache
Dynamic Discovery:
Use in register() to lazy-load configurations:
$this->app->singleton(\App\Contracts\ArrayableRepository::class, function () {
$classes = ArrayableScout::create()->get();
return new ArrayableRepository($classes);
});
Mocking Discovery:
Use NullDiscoverCacheDriver in tests:
Discover::in(__DIR__)
->withCache('test', new \Spatie\StructureDiscoverer\Cache\NullDiscoverCacheDriver())
->get();
Assertions:
$this->assertContains(\App\Models\User::class, Discover::in(app_path('Models'))->classes()->get());
Runtime Directories:
$directories = [app_path('Models'), app_path('Policies')];
Discover::in(...$directories)->classes()->get();
Environment-Based:
$discoverDir = config('app.env') === 'local' ? base_path('tests') : app_path();
Discover::in($discoverDir)->get();
use Spatie\StructureDiscoverer\Cache\DiscoverCacheDriver;
class RedisDiscoverCacheDriver implements DiscoverCacheDriver
{
public function has(string $id): bool { /* ... */ }
public function get(string $id): array { /* ... */ }
public function put(string $id, array $discovered): void { /* ... */ }
public function forget(string $id): void { /* ... */ }
}
Cache Invalidation:
php artisan structure-scouts:clear or manually clear via:
StructureScoutManager::clear([app_path('Scouts')]);
Performance Spikes:
->parallel()) may overload the system if not configured properly.Discover::in(app_path())->parallel(50)->get(); // Safer for shared hosting
Namespace Conflicts:
Discover::in(app_path())->custom(fn($structure) => !str_starts_with($structure->namespace, 'Vendor\\'))->get();
Chains Overhead:
extendsChain, implementsChain) can be slow for large projects.Discover::in(app_path())->withoutChains()->get();
Attribute Detection:
autoload-dev or manually include them in composer.json:
"autoload-dev": {
"files": ["vendor/your-package/src/Attributes.php"]
}
Case Sensitivity:
Sort::CaseInsensitiveName for consistent sorting:
Discover::in(app_path())->sortBy(\Spatie\StructureDiscoverer\Enums\Sort::CaseInsensitiveName)->get();
Inspect Discovered Structures:
->full()->get() to debug metadata:
$structures = Discover::in(app_path())->full()->get();
dump($structures[0]->file); // Check file paths
Log Discovery Queries:
class DebugScout extends StructureScout
{
protected function definition(): Discover
{
\Log::info('Discovering structures in: ' . app_path());
return Discover::in(app_path())->classes();
}
}
Verify Cache:
$cache = new FileDiscoverCacheDriver(storage_path('framework/cache'));
dump($cache->has('scout_key')); // Check if cached
dump($cache->get('scout_key')); // Inspect cached data
Slow Discovery:
class PublicClassCondition extends DiscoverCondition
{
public function satisfies(DiscoveredStructure $
How can I help you explore Laravel packages today?