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.
- 🔍 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
composer require bermudaphp/finder
- PHP 8.4 or higher
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";
}
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/');
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 { ... }
Finds abstract classes:
use Bermuda\ClassFinder\Filter\IsAbstractFilter;
$filter = new IsAbstractFilter();
// Matches: abstract class BaseController { ... }
Finds final classes:
use Bermuda\ClassFinder\Filter\IsFinalFilter;
$filter = new IsFinalFilter();
// Matches: final class UserService { ... }
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']);
Finds classes extending a specific parent class:
use Bermuda\ClassFinder\Filter\SubclassFilter;
$filter = new SubclassFilter('App\\Controller\\AbstractController');
// Finds all classes extending AbstractController
Finds callable classes (classes with __invoke
method):
use Bermuda\ClassFinder\Filter\CallableFilter;
$filter = new CallableFilter();
// Matches: class MyClass { public function __invoke() { ... } }
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');
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;
}
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);
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);
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/');
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/');
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/');
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/');
use Bermuda\ClassFinder\ClassFinder;
use Psr\Container\ContainerInterface;
// Create from container
$finder = ClassFinder::createFromContainer($container);
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);
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);
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)
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);
- 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
// 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)
];
$finder = new ClassFinder([
new InstantiableFilter(),
new PatternFilter('*Controller'),
new SubclassFilter('App\\Controller\\BaseController')
]);
$controllers = $finder->find('src/Controllers/');
$finder = new ClassFinder([
new InstantiableFilter(),
new AttributeSearchFilter(['Service'], deepSearch: true),
new PatternFilter('*Service')
]);
$services = $finder->find('src/Services/');
// 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/');
$finder = new ClassFinder([
new PatternFilter('*Test'),
new SubclassFilter('PHPUnit\\Framework\\TestCase')
]);
$tests = $finder->find('tests/');
$finder = new ClassFinder([
new InstantiableFilter(),
new AttributeSearchFilter(['Route', 'ApiResource'], deepSearch: true)
]);
$endpoints = $finder->find('src/Api/');
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/');
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/');
This project is licensed under the MIT License - see the LICENSE file for details.