bermudaphp / finder
Requires
- php: ^8.4
- bermudaphp/filter: ^1.0
- bermudaphp/tokenizer: ^1.0
- psr/container: ^2.0.2
- symfony/finder: ^7.2.2
This package is auto-updated.
Last update: 2025-06-12 11:50:14 UTC
README
A powerful and flexible PHP library for discovering and filtering PHP classes and related elements (classes, interfaces, traits, enums) in your codebase. Built with performance in mind and leveraging PHP 8.4+ features.
Features
- 🔍 Smart Discovery: Find classes, interfaces, traits, and enums in specified directories
- 🎯 Advanced Filtering: Rich set of filters with optimized performance
- 🚀 High Performance: Optimized algorithms with early termination and efficient pattern matching
- 🔄 Event System: Listener-based notifications for discovered elements
- 🎨 Attribute Support: Deep search through PHP 8+ attributes with pattern matching
- 📦 PSR-11 Compatible: Full dependency injection container support
- 🧩 Extensible: Easy to extend with custom filters and listeners
Installation
composer require bermudaphp/finder
Requirements
- PHP 8.4 or higher
Quick Start
Basic Usage
use Bermuda\ClassFinder\ClassFinder; // Create finder instance $finder = new ClassFinder(); // Find all PHP classes in directories $classes = $finder->find(['src/', 'app/']); foreach ($classes as $filename => $classInfo) { echo "Found: $classInfo->fullQualifiedName in $filename\n"; }
With Filters
use Bermuda\ClassFinder\ClassFinder; use Bermuda\ClassFinder\Filter\{ InstantiableFilter, ImplementsFilter, AttributeSearchFilter }; // Find instantiable classes that implement specific interfaces $finder = new ClassFinder([ new InstantiableFilter(), new ImplementsFilter(['Serializable', 'Countable']), new AttributeSearchFilter(['Route']) ]); $results = $finder->find('src/');
Available Filters
Class Type Filters
InstantiableFilter
Finds classes that can be instantiated (not abstract, interfaces, or traits):
use Bermuda\ClassFinder\Filter\InstantiableFilter; $filter = new InstantiableFilter(); // Matches: class UserService { ... } // Excludes: abstract class BaseService { ... }
IsAbstractFilter
Finds abstract classes:
use Bermuda\ClassFinder\Filter\IsAbstractFilter; $filter = new IsAbstractFilter(); // Matches: abstract class BaseController { ... }
IsFinalFilter
Finds final classes:
use Bermuda\ClassFinder\Filter\IsFinalFilter; $filter = new IsFinalFilter(); // Matches: final class UserService { ... }
Interface and Inheritance Filters
ImplementsFilter
Finds classes implementing specific interfaces:
use Bermuda\ClassFinder\Filter\ImplementsFilter; // Single interface $filter = new ImplementsFilter('Serializable'); // Multiple interfaces (ALL required - AND logic) $filter = new ImplementsFilter(['Serializable', 'Countable']); // Any interface (OR logic) $filter = ImplementsFilter::implementsAny(['Serializable', 'Countable']);
SubclassFilter
Finds classes extending a specific parent class:
use Bermuda\ClassFinder\Filter\SubclassFilter; $filter = new SubclassFilter('App\\Controller\\AbstractController'); // Finds all classes extending AbstractController
Callable Filter
CallableFilter
Finds callable classes (classes with __invoke
method):
use Bermuda\ClassFinder\Filter\CallableFilter; $filter = new CallableFilter(); // Matches: class MyClass { public function __invoke() { ... } }
Pattern Matching Filters
PatternFilter
Smart pattern matching with automatic target detection:
use Bermuda\ClassFinder\Filter\PatternFilter; // Search in class names $filter = new PatternFilter('*Controller'); // Classes ending with "Controller" $filter = new PatternFilter('Abstract*'); // Classes starting with "Abstract" $filter = new PatternFilter('*Test*'); // Classes containing "Test" // Search in namespaces $filter = new PatternFilter('App\\Controllers\\*'); // Classes in Controllers namespace // Search in full qualified names $filter = new PatternFilter('*\\Api\\*Controller'); // API controllers // Static helpers for common patterns $filter = PatternFilter::exactMatch('UserController'); $filter = PatternFilter::contains('Service'); $filter = PatternFilter::startsWith('Abstract'); $filter = PatternFilter::endsWith('Controller'); $filter = PatternFilter::namespace('App\\Services');
Attribute Filters
Deep Search Feature
The deepSearch
parameter in attribute filters controls the scope of attribute searching:
deepSearch: false
(default): Searches only for attributes on the class itselfdeepSearch: true
: Extends search to include attributes on class members:- Method attributes
- Property attributes
- Constant attributes
// Example class with attributes at different levels class UserController { #[Inject] private UserService $userService; #[Route('/users')] #[Auth('admin')] public function index(): Response { // method implementation } #[Deprecated] public const STATUS_ACTIVE = 1; }
AttributeSearchFilter
Advanced attribute filtering with multiple search options:
use Bermuda\ClassFinder\Filter\AttributeSearchFilter; // Basic search - only class-level attributes $filter = new AttributeSearchFilter(['Route', 'Controller']); // Deep search - includes method, property, and constant attributes $filter = new AttributeSearchFilter(['Inject'], deepSearch: true); // Will find UserController because $userService has #[Inject] // Mixed exact names and patterns with deep search $filter = new AttributeSearchFilter(['Route', '*Test*', 'Api*'], deepSearch: true); // AND logic with deep search - must have ALL attributes (anywhere in class) $filter = new AttributeSearchFilter(['Route', 'Auth'], matchAll: true, deepSearch: true); // Will find UserController because it has both #[Route] and #[Auth] on methods // Comparison of search scopes: // Without deep search - only finds classes with Route attribute on class declaration $classOnlyFilter = new AttributeSearchFilter(['Route'], deepSearch: false); // With deep search - finds classes with Route on class OR on any method/property/constant $deepFilter = new AttributeSearchFilter(['Route'], deepSearch: true); // Static helpers with deep search $filter = AttributeSearchFilter::hasAttribute('Inject', deepSearch: true); $filter = AttributeSearchFilter::hasAnyAttribute(['Route', 'Controller'], deepSearch: true); $filter = AttributeSearchFilter::hasAllAttributes(['Route', 'Middleware'], deepSearch: true);
AttributePatternFilter
Pattern matching for attribute names:
use Bermuda\ClassFinder\Filter\AttributePatternFilter; // Basic pattern matching - only class-level attributes $filter = new AttributePatternFilter('*Route*'); $filter = new AttributePatternFilter('Api*'); // Deep search - includes attributes on methods, properties, constants $filter = new AttributePatternFilter('*Route*', deepSearch: true); // Will find UserController because index() method has #[Route('/users')] $filter = new AttributePatternFilter('*Inject*', deepSearch: true); // Will find UserController because $userService property has #[Inject] // Comparison of search scopes: // Without deep search - only finds classes with Http* attributes on class $classOnlyFilter = new AttributePatternFilter('Http*', deepSearch: false); // With deep search - finds classes with Http* on class OR members $deepFilter = new AttributePatternFilter('Http*', deepSearch: true); // Optimized static helpers with deep search $filter = AttributePatternFilter::exactAttribute('HttpGet', deepSearch: true); $filter = AttributePatternFilter::anyAttribute(['Route', 'HttpGet'], deepSearch: true); $filter = AttributePatternFilter::attributePrefix('Http', deepSearch: true);
Filter Combination
ChainableFilter (AND Logic)
Combines multiple filters with AND logic - an element must pass ALL filters to be accepted:
use Bermuda\Filter\ChainableFilter; use Bermuda\ClassFinder\Filter\{ InstantiableFilter, PatternFilter, AttributeSearchFilter }; // All conditions must be true $chainFilter = new ChainableFilter([ new InstantiableFilter(), // AND: must be instantiable new PatternFilter('*Controller'), // AND: must end with "Controller" new AttributeSearchFilter(['Route']) // AND: must have Route attribute ]); $finder = new ClassFinder([$chainFilter]); $strictControllers = $finder->find('src/Controllers/');
OneOfFilter (OR Logic)
Combines multiple filters with OR logic - an element only needs to pass ONE filter to be accepted:
use Bermuda\Filter\OneOfFilter; use Bermuda\ClassFinder\Filter\{ PatternFilter, AttributeSearchFilter, ImplementsFilter }; // Any condition can be true $orFilter = new OneOfFilter([ new PatternFilter('*Controller'), // OR: ends with "Controller" new PatternFilter('*Service'), // OR: ends with "Service" new AttributeSearchFilter(['Component']), // OR: has Component attribute new ImplementsFilter('App\\Contracts\\HandlerInterface') // OR: implements HandlerInterface ]); $finder = new ClassFinder([$orFilter]); $flexibleResults = $finder->find('src/');
Complex Filter Combinations
Combine different logic types for sophisticated filtering:
use Bermuda\Filter\{ChainableFilter, OneOfFilter}; use Bermuda\ClassFinder\Filter\{ InstantiableFilter, PatternFilter, AttributeSearchFilter, ImplementsFilter }; // Complex logic: Must be instantiable AND (Controller OR Service OR has Route attribute) $complexFilter = new ChainableFilter([ new InstantiableFilter(), // Must be instantiable new OneOfFilter([ // AND any of these: new PatternFilter('*Controller'), // - ends with "Controller" new PatternFilter('*Service'), // - ends with "Service" new AttributeSearchFilter(['Route']) // - has Route attribute ]) ]); $finder = new ClassFinder([$complexFilter]); $results = $finder->find('src/');
Advanced Usage
Combining Multiple Filters
use Bermuda\ClassFinder\ClassFinder; use Bermuda\ClassFinder\Filter\{ InstantiableFilter, PatternFilter, AttributeSearchFilter }; $finder = new ClassFinder([ new InstantiableFilter(), // Only instantiable classes new PatternFilter('*Controller'), // Class names ending with "Controller" new AttributeSearchFilter(['Route']) // Must have Route attribute ]); $controllers = $finder->find('src/Controllers/');
Using with Dependency Injection
use Bermuda\ClassFinder\ClassFinder; use Psr\Container\ContainerInterface; // Create from container $finder = ClassFinder::createFromContainer($container);
Event Listeners
Listen for discovered classes:
use Bermuda\ClassFinder\{ClassFinder, ClassNotifier}; use Bermuda\ClassFinder\ClassFoundListenerInterface; use Bermuda\Tokenizer\ClassInfo; class MyListener implements ClassFoundListenerInterface { public function handle(ClassInfo $info): void { echo "Found class: {$info->fullQualifiedName}\n"; } } // Create notifier with listener $notifier = new ClassNotifier([new MyListener()]); // Find and notify $finder = new ClassFinder(); $classes = $finder->find('src/'); $notifier->notify($classes);
Search Modes
Control what types of class elements to discover using bitwise flags:
use Bermuda\ClassFinder\ClassFinder; use Bermuda\Tokenizer\TokenizerInterface; $finder = new ClassFinder(); // Search only for classes $results = $finder->find('src/', [], TokenizerInterface::SEARCH_CLASSES); // Search only for interfaces $results = $finder->find('src/', [], TokenizerInterface::SEARCH_INTERFACES); // Search only for traits $results = $finder->find('src/', [], TokenizerInterface::SEARCH_TRAITS); // Search only for enums $results = $finder->find('src/', [], TokenizerInterface::SEARCH_ENUMS); // Combine search modes with bitwise OR $results = $finder->find('src/', [], TokenizerInterface::SEARCH_CLASSES | TokenizerInterface::SEARCH_INTERFACES ); // Search for everything (default) - classes, interfaces, traits, enums $results = $finder->find('src/', [], TokenizerInterface::SEARCH_ALL);
Available Search Mode Constants:
SEARCH_CLASSES = 1
- Find only classesSEARCH_INTERFACES = 2
- Find only interfacesSEARCH_TRAITS = 4
- Find only traitsSEARCH_ENUMS = 8
- Find only enumsSEARCH_ALL = 15
- Find all OOP elements (default)
Working with Results
use Bermuda\ClassFinder\ClassFinder; $finder = new ClassFinder(); $classes = $finder->find('src/'); // Count results echo "Found " . count($classes) . " classes\n"; // Convert to array $array = $classes->toArray(); // Add filters dynamically $filtered = $classes->withFilter(new InstantiableFilter()); // Remove filters $unfiltered = $classes->withoutFilter($someFilter);
Performance Tips
- Use specific filters: The more specific your filters, the faster the search
- Order matters: Place the most restrictive filters first
- Pattern optimization: Simple patterns (prefix*, *suffix, contains) are automatically optimized
- Reflection caching: Repeated searches on the same classes benefit from reflection caching
Configuration
Container Configuration
// config/dependencies.php use Bermuda\ClassFinder\{ ClassFinder, ClassFoundListenerProviderInterface, ClassFoundListenerProvider, ConfigProvider }; return [ 'config' => [ ConfigProvider::CONFIG_KEY_FILTERS => [ // Your default filters ], ConfigProvider::CONFIG_KEY_LISTENERS => [ // Your listeners ] ], ClassFoundListenerProviderInterface::class => fn() => ClassFoundListenerProvider::createFromContainer($container) ];
Examples
Find All Controllers
$finder = new ClassFinder([ new InstantiableFilter(), new PatternFilter('*Controller'), new SubclassFilter('App\\Controller\\BaseController') ]); $controllers = $finder->find('src/Controllers/');
Find Services with Dependency Injection
$finder = new ClassFinder([ new InstantiableFilter(), new AttributeSearchFilter(['Service'], deepSearch: true), new PatternFilter('*Service') ]); $services = $finder->find('src/Services/');
Find Classes with Deep Attribute Search
// Find all classes that use dependency injection anywhere in the class $finder = new ClassFinder([ new AttributeSearchFilter(['Inject', 'Autowired'], deepSearch: true) ]); $diClasses = $finder->find('src/'); // Find API-related classes by checking for routing attributes in methods $finder = new ClassFinder([ new InstantiableFilter(), new AttributePatternFilter('*Route*', deepSearch: true) // Checks class AND method attributes ]); $apiClasses = $finder->find('src/');
Find Test Classes
$finder = new ClassFinder([ new PatternFilter('*Test'), new SubclassFilter('PHPUnit\\Framework\\TestCase') ]); $tests = $finder->find('tests/');
Find API Endpoints
$finder = new ClassFinder([ new InstantiableFilter(), new AttributeSearchFilter(['Route', 'ApiResource'], deepSearch: true) ]); $endpoints = $finder->find('src/Api/');
Complex Filter Logic
use Bermuda\Filter\{ChainableFilter, OneOfFilter}; // Find classes that are either Controllers OR Services, but must be instantiable $finder = new ClassFinder([ new InstantiableFilter(), // Must be instantiable new OneOfFilter([ // AND (Controller OR Service) new PatternFilter('*Controller'), new PatternFilter('*Service') ]) ]); $handleableClasses = $finder->find('src/');
Flexible Component Discovery
use Bermuda\Filter\OneOfFilter; // Find any component-like classes using multiple criteria $componentFilter = new OneOfFilter([ new AttributeSearchFilter(['Component', 'Service', 'Repository']), new ImplementsFilter(['App\\Contracts\\ComponentInterface']), new PatternFilter('App\\Components\\*') ]); $finder = new ClassFinder([$componentFilter]); $components = $finder->find('src/');
License
This project is licensed under the MIT License - see the LICENSE file for details.