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 in app/ that implement Arrayable:
use Spatie\StructureDiscoverer\Discover;
$arrayableClasses = Discover::in(app_path())->classes()->implementing(\Illuminate\Contracts\Support\Arrayable::class)->get();
Key Files:
config/structure-discoverer.php (Laravel)vendor/spatie/php-structure-discoverer/src/ (Core package)Basic Discovery:
// Discover all classes in a directory
Discover::in(app_path('Models'))->classes()->get();
// Discover enums with a specific name
Discover::in(app_path('Enums'))->enums()->named('StatusEnum')->get();
Conditional Discovery:
// Classes extending a base model
Discover::in(app_path())->classes()->extending(\Illuminate\Database\Eloquent\Model::class)->get();
// Classes using a specific attribute
Discover::in(app_path())->withAttribute(\Illuminate\Foundation\Testing\Concerns\InteractsWithTime::class)->get();
Custom Conditions:
// Using closure
Discover::in(app_path())
->custom(fn($structure) => str_contains($structure->namespace, 'App\\Policies'))
->get();
// Using a dedicated class
class PolicyCondition extends DiscoverCondition {
public function satisfies($structure) {
return str_contains($structure->namespace, 'App\\Policies');
}
}
Discover::in(app_path())->custom(new PolicyCondition())->get();
Combining Conditions:
// OR logic for classes or enums
use Spatie\StructureDiscoverer\ConditionBuilder;
Discover::in(app_path())
->any(
ConditionBuilder::create()->classes(),
ConditionBuilder::create()->enums()
)
->get();
Laravel Service Providers:
Register scouts in boot():
public function boot() {
StructureScoutManager::add(\App\Scouts\PolicyScout::class);
}
Artisan Commands: Cache scouts during deployment:
// In a command
StructureScoutManager::cache([app_path('Scouts')]);
Testing:
Use NullDiscoverCacheDriver for unit tests:
Discover::in(__DIR__)
->withCache('test', new NullDiscoverCacheDriver())
->get();
Performance Optimization:
// Parallel discovery (requires amphp/parallel)
Discover::in(app_path())->parallel(100)->get();
Cache Invalidation:
php artisan structure-scouts:clear
php artisan structure-scouts:cache post-deployment.Chains Overhead:
extending()/implementing()) can significantly slow discovery for large codebases.withoutChains() for performance-critical paths:
Discover::in(app_path())->withoutChains()->extending(BaseModel::class)->get();
Namespace Conflicts:
DiscoveredStructure properties like $namespace or $file for precision.Parallel Limitations:
if (!extension_loaded('pcntl')) {
Discover::in(app_path())->get(); // Sequential fallback
}
Cache Issues:
config/structure-discoverer.php.FileDiscoverCacheDriver with a writable directory.Condition Logic:
full() to inspect DiscoveredStructure objects:
$structures = Discover::in(app_path())->full()->get();
dd($structures[0]->extendsChain); // Inspect inheritance
Performance Bottlenecks:
->withoutChains() to isolate slow queries.Custom Cache Drivers:
class RedisDiscoverCacheDriver implements DiscoverCacheDriver {
public function has(string $id): bool { /* ... */ }
public function get(string $id): array { /* ... */ }
// Implement remaining methods
}
Structure Scouts:
StructureScout for reusable queries:
class PolicyScout extends StructureScout {
protected function definition() {
return Discover::in(app_path('Policies'))
->classes()
->custom(fn($s) => str_contains($s->namespace, 'App\\Policies'));
}
}
Metadata Enrichment:
DiscoveredStructure to add custom properties:
class CustomDiscoveredStructure extends DiscoveredStructure {
public function __construct(...) {
parent::__construct(...);
$this->customProperty = $this->parseCustomLogic();
}
}
Ignored Files:
ignored_files in config to exclude vendor/ or tests/:
'ignored_files' => [
app_path('Tests'),
vendor_path(),
],
Directory Scoping:
structure_scout_directories includes all relevant paths:
'structure_scout_directories' => [
app_path(),
database_path('Factories'),
],
Laravel Cache Store:
'cache' => [
'driver' => \Spatie\StructureDiscoverer\Cache\LaravelDiscoverCacheDriver::class,
'store' => 'redis',
],
How can I help you explore Laravel packages today?