Try Live
Add Docs
Rankings
Pricing
Docs
Install
Install
Docs
Pricing
More...
More...
Try Live
Rankings
Enterprise
Create API Key
Add Docs
PHP Structure Discoverer
https://github.com/spatie/php-structure-discoverer
Admin
Automatically discover classes, interfaces, enums, and traits within your PHP application with
...
Tokens:
8,189
Snippets:
85
Trust Score:
8.5
Update:
2 weeks ago
Context
Skills
Chat
Benchmark
86.3
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# PHP Structure Discoverer PHP Structure Discoverer is a powerful package by Spatie that automatically discovers classes, interfaces, enums, and traits within PHP applications. It provides a fluent API to scan directories and filter structures based on various conditions such as class inheritance, interface implementation, attributes, and custom criteria. The package is particularly useful for building auto-discovery systems, plugin architectures, and automatic service registration in frameworks like Laravel. The package includes built-in caching functionality for production environments, parallel processing support via AMP for large codebases, and detailed metadata extraction for discovered structures. It works seamlessly with Laravel but can also be used standalone in any PHP 8.3+ project. The core `Discover` class provides the entry point for all discovery operations, while `StructureScout` classes enable reusable, cacheable discovery definitions. ## Installation Install the package via Composer. ```bash composer require spatie/php-structure-discoverer ``` For Laravel applications, publish the configuration file: ```bash php artisan vendor:publish --tag="structure-discoverer-config" ``` ## Discover::in() - Start Discovery in Directories The static `in()` method creates a new Discover instance and specifies which directories to scan for PHP structures. ```php use Spatie\StructureDiscoverer\Discover; // Discover in a single directory $structures = Discover::in(__DIR__)->get(); // Returns: ['App\Models\User', 'App\Models\Post', 'App\Enums\Status', ...] // Discover in multiple directories $structures = Discover::in( app_path('Models'), app_path('Enums'), app_path('Services') )->get(); // Returns: ['App\Models\User', 'App\Enums\UserStatus', 'App\Services\PaymentService', ...] ``` ## classes() - Discover Only Classes Filters the discovery to return only class definitions, excluding interfaces, enums, and traits. ```php use Spatie\StructureDiscoverer\Discover; // Find all classes in the Models directory $classes = Discover::in(app_path('Models')) ->classes() ->get(); // Returns: ['App\Models\User', 'App\Models\Post', 'App\Models\Comment'] // Combine with other conditions $abstractClasses = Discover::in(__DIR__) ->classes() ->custom(fn($structure) => $structure->isAbstract) ->full() ->get(); // Returns DiscoveredClass objects for all abstract classes ``` ## interfaces() - Discover Only Interfaces Filters the discovery to return only interface definitions. ```php use Spatie\StructureDiscoverer\Discover; // Find all interfaces in the Contracts directory $interfaces = Discover::in(app_path('Contracts')) ->interfaces() ->get(); // Returns: ['App\Contracts\PaymentGateway', 'App\Contracts\Notifiable', 'App\Contracts\Exportable'] ``` ## enums() - Discover Only Enums Filters the discovery to return only PHP 8.1+ enum definitions. ```php use Spatie\StructureDiscoverer\Discover; // Find all enums in your application $enums = Discover::in(app_path('Enums')) ->enums() ->get(); // Returns: ['App\Enums\OrderStatus', 'App\Enums\PaymentMethod', 'App\Enums\UserRole'] // Get full enum metadata including backing type $enumsWithMetadata = Discover::in(app_path('Enums')) ->enums() ->full() ->get(); // Returns DiscoveredEnum objects with type (Unit, String, Int), implements, attributes ``` ## traits() - Discover Only Traits Filters the discovery to return only trait definitions. ```php use Spatie\StructureDiscoverer\Discover; // Find all traits in your application $traits = Discover::in(app_path('Traits')) ->traits() ->get(); // Returns: ['App\Traits\HasUuid', 'App\Traits\Auditable', 'App\Traits\SoftDeletes'] ``` ## extending() - Find Classes Extending a Parent Discovers classes that extend a specific parent class, including indirect inheritance through the chain. ```php use Spatie\StructureDiscoverer\Discover; use Illuminate\Database\Eloquent\Model; // Find all Eloquent models $models = Discover::in(app_path()) ->classes() ->extending(Model::class) ->get(); // Returns: ['App\Models\User', 'App\Models\Post', 'App\Models\Comment'] // Includes classes that extend Model indirectly (e.g., User extends Authenticatable extends Model) // Find classes extending multiple possible parents (OR condition) $controllers = Discover::in(app_path('Http/Controllers')) ->classes() ->extending(Controller::class) ->get(); ``` ## extendingWithoutChain() - Direct Extension Only Discovers classes that directly extend a specific parent class, without following the inheritance chain. ```php use Spatie\StructureDiscoverer\Discover; use App\Models\BaseModel; // Find only classes that directly extend BaseModel // (not classes that extend a class that extends BaseModel) $directExtenders = Discover::in(app_path('Models')) ->classes() ->extendingWithoutChain(BaseModel::class) ->get(); // Returns only immediate children, not grandchildren ``` ## implementing() - Find Structures Implementing an Interface Discovers classes, interfaces, or enums that implement a specific interface, including through inheritance chains. ```php use Spatie\StructureDiscoverer\Discover; use Illuminate\Contracts\Support\Arrayable; use App\Contracts\Exportable; // Find all classes implementing Arrayable $arrayables = Discover::in(app_path()) ->implementing(Arrayable::class) ->get(); // Returns: ['App\Models\User', 'App\DTOs\UserData', 'App\Collections\UserCollection'] // Find exportable structures $exportables = Discover::in(app_path()) ->classes() ->implementing(Exportable::class) ->get(); ``` ## implementingWithoutChain() - Direct Implementation Only Discovers structures that directly implement an interface without following the inheritance chain. ```php use Spatie\StructureDiscoverer\Discover; use App\Contracts\PaymentGateway; // Find only classes that directly implement PaymentGateway $gateways = Discover::in(app_path('Services/Payment')) ->classes() ->implementingWithoutChain(PaymentGateway::class) ->get(); // Returns: ['App\Services\Payment\StripeGateway', 'App\Services\Payment\PayPalGateway'] ``` ## withAttribute() - Find Structures with PHP Attributes Discovers classes, interfaces, enums, or traits that have specific PHP 8 attributes applied. ```php use Spatie\StructureDiscoverer\Discover; use App\Attributes\AsCommand; use App\Attributes\Listener; // Find all classes with AsCommand attribute $commands = Discover::in(app_path('Console/Commands')) ->classes() ->withAttribute(AsCommand::class) ->get(); // Returns: ['App\Console\Commands\ImportUsers', 'App\Console\Commands\SendNotifications'] // Find event listeners $listeners = Discover::in(app_path('Listeners')) ->classes() ->withAttribute(Listener::class) ->get(); ``` ## named() - Filter by Structure Name Filters discovered structures to include only those with specific names. ```php use Spatie\StructureDiscoverer\Discover; // Find a specific class by name $structure = Discover::in(app_path()) ->named('UserService') ->get(); // Returns: ['App\Services\UserService'] // Find multiple named structures $structures = Discover::in(app_path()) ->named('UserService', 'OrderService', 'PaymentService') ->get(); ``` ## custom() - Custom Filter Conditions Applies custom filtering logic using a closure or a custom DiscoverCondition class. ```php use Spatie\StructureDiscoverer\Discover; use Spatie\StructureDiscoverer\Data\DiscoveredStructure; use Spatie\StructureDiscoverer\DiscoverConditions\DiscoverCondition; // Using a closure for custom filtering $appStructures = Discover::in(app_path()) ->custom(fn(DiscoveredStructure $structure) => str_starts_with($structure->namespace, 'App\\Services')) ->get(); // Returns: ['App\Services\UserService', 'App\Services\PaymentService'] // Find final classes only $finalClasses = Discover::in(app_path()) ->classes() ->custom(fn(DiscoveredStructure $structure) => $structure->isFinal) ->full() ->get(); // Creating a reusable custom condition class class InNamespaceCondition extends DiscoverCondition { public function __construct(private string $namespace) {} public function satisfies(DiscoveredStructure $structure): bool { return str_starts_with($structure->namespace, $this->namespace); } } // Using the custom condition $services = Discover::in(app_path()) ->custom(new InNamespaceCondition('App\\Services')) ->get(); ``` ## any() - OR Condition Combinations Combines multiple conditions with OR logic, returning structures that match any of the specified conditions. ```php use Spatie\StructureDiscoverer\Discover; use Spatie\StructureDiscoverer\Support\Conditions\ConditionBuilder; // Find classes OR enums $classesAndEnums = Discover::in(app_path()) ->any( ConditionBuilder::create()->classes(), ConditionBuilder::create()->enums() ) ->get(); // Returns all classes and all enums // Complex OR conditions: classes implementing Arrayable OR enums implementing Stringable $structures = Discover::in(app_path()) ->any( ConditionBuilder::create()->classes()->implementing(Arrayable::class), ConditionBuilder::create()->enums()->implementing(Stringable::class) ) ->get(); ``` ## exact() - AND Condition Combinations Combines multiple conditions with AND logic (this is the default behavior, but can be made explicit). ```php use Spatie\StructureDiscoverer\Discover; use Spatie\StructureDiscoverer\Support\Conditions\ConditionBuilder; // Complex AND conditions within OR conditions $structures = Discover::in(app_path()) ->any( ConditionBuilder::create()->exact( ConditionBuilder::create()->classes(), ConditionBuilder::create()->implementing(Arrayable::class) ), ConditionBuilder::create()->exact( ConditionBuilder::create()->enums(), ConditionBuilder::create()->implementing(Stringable::class) ) ) ->get(); // Returns: classes implementing Arrayable OR enums implementing Stringable ``` ## full() - Get Complete Structure Metadata Returns DiscoveredStructure objects with complete metadata instead of just FQCN strings. ```php use Spatie\StructureDiscoverer\Discover; // Get full metadata for discovered classes $classes = Discover::in(app_path('Models')) ->classes() ->full() ->get(); foreach ($classes as $class) { // $class is a DiscoveredClass object echo "Name: {$class->name}\n"; echo "Namespace: {$class->namespace}\n"; echo "File: {$class->file}\n"; echo "FQCN: {$class->getFcqn()}\n"; echo "Is Final: " . ($class->isFinal ? 'Yes' : 'No') . "\n"; echo "Is Abstract: " . ($class->isAbstract ? 'Yes' : 'No') . "\n"; echo "Is Readonly: " . ($class->isReadonly ? 'Yes' : 'No') . "\n"; echo "Extends: {$class->extends}\n"; echo "Implements: " . implode(', ', $class->implements) . "\n"; echo "Attributes: " . implode(', ', array_map(fn($a) => $a->class, $class->attributes)) . "\n"; } // Get full metadata for enums $enums = Discover::in(app_path('Enums')) ->enums() ->full() ->get(); foreach ($enums as $enum) { // $enum is a DiscoveredEnum object echo "Enum: {$enum->name}\n"; echo "Type: {$enum->type->name}\n"; // Unit, String, or Int echo "Implements: " . implode(', ', $enum->implements) . "\n"; } ``` ## sortBy() - Sort Discovery Results Sorts the discovered structures by various criteria using Symfony Finder's sorting options. ```php use Spatie\StructureDiscoverer\Discover; use Spatie\StructureDiscoverer\Enums\Sort; // Sort by name $sorted = Discover::in(app_path()) ->classes() ->sortBy(Sort::Name) ->get(); // Sort by modification time (newest first with reverse) $recentlyModified = Discover::in(app_path()) ->classes() ->sortBy(Sort::ModifiedTime, reverse: true) ->get(); // Available sort options: // Sort::Name - Sort alphabetically by name // Sort::Size - Sort by file size // Sort::Type - Sort by file type // Sort::Extension - Sort by file extension // Sort::ChangedTime - Sort by inode change time // Sort::ModifiedTime - Sort by last modification time // Sort::AccessedTime - Sort by last access time // Sort::CaseInsensitiveName - Case-insensitive alphabetical sort ``` ## withCache() - Inline Caching Enables caching for discovery results without using StructureScout classes. ```php use Spatie\StructureDiscoverer\Discover; use Spatie\StructureDiscoverer\Cache\FileDiscoverCacheDriver; // Cache discovery results to a file $structures = Discover::in(app_path('Models')) ->classes() ->extending(Model::class) ->withCache( 'eloquent-models', new FileDiscoverCacheDriver('/tmp/discoverer-cache') ) ->get(); // First call: performs discovery and caches results // Subsequent calls: returns cached results instantly ``` ## parallel() - Parallel Processing Enables parallel file scanning using AMP for faster discovery in large codebases. ```php use Spatie\StructureDiscoverer\Discover; // First install amphp/parallel: // composer require amphp/parallel // Enable parallel processing with default 50 files per job $structures = Discover::in(app_path()) ->classes() ->parallel() ->get(); // Custom files per job for fine-tuning performance $structures = Discover::in(app_path()) ->classes() ->parallel(filesPerJob: 100) ->get(); ``` ## withoutChains() - Disable Inheritance Chain Resolution Disables the automatic inheritance chain resolution for better performance when not needed. ```php use Spatie\StructureDiscoverer\Discover; // Disable chain resolution for performance $structures = Discover::in(app_path()) ->withoutChains() ->classes() ->extending(Model::class) // Will only find direct children of Model ->get(); ``` ## useReflection() - Use Reflection Parser Switches from the default token parser to PHP's Reflection API for parsing structures. ```php use Spatie\StructureDiscoverer\Discover; // Use reflection parser (faster but less robust) $structures = Discover::in(app_path()) ->useReflection( basePath: base_path(), rootNamespace: null ) ->classes() ->get(); // Laravel default configuration $structures = Discover::in(app_path()) ->useReflection(basePath: base_path()) ->get(); ``` ## ignoreFiles() - Exclude Specific Files Excludes specific files from the discovery process. ```php use Spatie\StructureDiscoverer\Discover; // Ignore specific files $structures = Discover::in(app_path()) ->ignoreFiles( app_path('Models/LegacyUser.php'), app_path('Services/DeprecatedService.php') ) ->classes() ->get(); ``` ## StructureScout - Reusable Cached Discovery StructureScout provides a way to define reusable, cacheable discovery definitions as dedicated classes. ```php use Spatie\StructureDiscoverer\Discover; use Spatie\StructureDiscoverer\StructureScout; use Spatie\StructureDiscoverer\Cache\DiscoverCacheDriver; use Spatie\StructureDiscoverer\Cache\FileDiscoverCacheDriver; use Illuminate\Database\Eloquent\Model; // Define a StructureScout for Eloquent models class ModelsStructureScout extends StructureScout { protected function definition(): Discover { return Discover::in(app_path('Models')) ->classes() ->extending(Model::class); } // Required for non-Laravel apps; optional for Laravel (uses config) public function cacheDriver(): DiscoverCacheDriver { return new FileDiscoverCacheDriver(storage_path('framework/cache')); } } // Usage in your application $models = ModelsStructureScout::create()->get(); // First call: discovers and caches // Subsequent calls: returns from cache // Check if cached if (ModelsStructureScout::create()->isCached()) { echo "Using cached results"; } // Force cache refresh $models = ModelsStructureScout::create()->cache(); // Clear the cache ModelsStructureScout::create()->clear(); ``` ## StructureScoutManager - Manage Multiple Scouts The StructureScoutManager provides methods to cache and clear all StructureScout instances at once. ```php use Spatie\StructureDiscoverer\Support\StructureScoutManager; // Cache all structure scouts in specified directories $cachedScouts = StructureScoutManager::cache([ app_path(), app_path('Domain'), ]); // Returns: ['App\Scouts\ModelsStructureScout', 'App\Scouts\EnumsStructureScout', ...] // Clear all structure scout caches $clearedScouts = StructureScoutManager::clear([ app_path(), ]); // Add a package's scout to be included in cache/clear operations StructureScoutManager::add(\Package\Scouts\SettingsStructureScout::class); ``` ## Laravel Artisan Commands Laravel integration provides Artisan commands for managing StructureScout caches in production deployments. ```bash # Warm all StructureScout caches (run during deployment) php artisan structure-scouts:cache # Clear all StructureScout caches php artisan structure-scouts:clear ``` ## FileDiscoverCacheDriver - File-Based Caching Caches discovered structures to files on the filesystem. ```php use Spatie\StructureDiscoverer\Cache\FileDiscoverCacheDriver; // Basic file cache $cache = new FileDiscoverCacheDriver( directory: '/tmp/discoverer-cache' ); // With serialization disabled (stores as PHP array) $cache = new FileDiscoverCacheDriver( directory: storage_path('cache'), serialize: false // Stores as PHP return statement for opcache optimization ); // With custom filename $cache = new FileDiscoverCacheDriver( directory: storage_path('cache'), filename: 'structures.cache' ); // Cache interface methods $cache->has('my-cache-id'); // bool: Check if cache exists $cache->get('my-cache-id'); // array: Get cached data $cache->put('my-cache-id', $data); // void: Store data $cache->forget('my-cache-id'); // void: Remove cached data ``` ## LaravelDiscoverCacheDriver - Laravel Cache Integration Uses Laravel's cache system for storing discovered structures. ```php use Spatie\StructureDiscoverer\Cache\LaravelDiscoverCacheDriver; // Use default Laravel cache store $cache = new LaravelDiscoverCacheDriver(); // Use specific cache store with prefix $cache = new LaravelDiscoverCacheDriver( prefix: 'my-app', store: 'redis' ); // Configuration in config/structure-discoverer.php return [ 'cache' => [ 'driver' => LaravelDiscoverCacheDriver::class, 'store' => 'redis', // null uses default store ], ]; ``` ## Laravel Configuration The Laravel configuration file allows customizing the package behavior application-wide. ```php // config/structure-discoverer.php return [ // Files to ignore during discovery 'ignored_files' => [ app_path('Legacy/OldModel.php'), app_path('Deprecated'), ], // Directories to search for StructureScout classes 'structure_scout_directories' => [ app_path(), app_path('Domain'), ], // Cache configuration 'cache' => [ 'driver' => \Spatie\StructureDiscoverer\Cache\LaravelDiscoverCacheDriver::class, 'store' => null, // Uses default cache store // 'store' => 'redis', // Or specify a specific store ], ]; ``` ## Summary PHP Structure Discoverer excels at automatic service discovery, plugin systems, and dynamic class registration. Common use cases include auto-registering event listeners, discovering command classes, finding all models for a data export feature, building plugin architectures where plugins implement a specific interface, and creating automatic dependency injection bindings. The package's fluent API makes it easy to build complex discovery queries that combine type filtering, inheritance checks, interface implementation, and custom conditions. For production deployments, the StructureScout pattern combined with the caching system ensures that discovery only happens once during deployment, with subsequent requests using cached results for optimal performance. The parallel processing feature handles large codebases efficiently, while the choice between token-based and reflection-based parsing allows fine-tuning the trade-off between robustness and speed. Integration with Laravel is seamless through the service provider, configuration file, and Artisan commands, but the package works equally well in standalone PHP applications.