PHPackages                             componenta/class-finder - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [PSR &amp; Standards](/categories/psr-standards)
4. /
5. componenta/class-finder

ActiveLibrary[PSR &amp; Standards](/categories/psr-standards)

componenta/class-finder
=======================

Lazy class discovery with composable filters and listener notification

v1.0.0(1mo ago)095MITPHPPHP ^8.4

Since Jun 17Pushed 1mo agoCompare

[ Source](https://github.com/componenta/class-finder)[ Packagist](https://packagist.org/packages/componenta/class-finder)[ RSS](/packages/componenta-class-finder/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (1)Dependencies (10)Versions (2)Used By (5)

Componenta ClassFinder
======================

[](#componenta-classfinder)

Lazy PHP declaration discovery with composable filters and listener notification.

ClassFinder scans PHP files, extracts named class/interface/trait/enum declarations through `componenta/tokenizer`, applies filters, and returns a replayable iterator of `ClassInfo` metadata.

Installation
------------

[](#installation)

```
composer require componenta/class-finder
```

Requirements
------------

[](#requirements)

- PHP 8.4+
- `symfony/finder`
- `componenta/tokenizer`
- `componenta/filter`
- `componenta/arrayable`
- `componenta/iterator`
- `psr/container`
- `psr/log`

Related Packages
----------------

[](#related-packages)

PackageWhy it matters here`componenta/tokenizer`Parses PHP source and returns `ClassInfo` declarations.`symfony/finder`Walks directories and selects PHP files.`componenta/iterator`Provides replayable iteration over discovered declarations.`componenta/filter`Provides composable filters used by the finder.`componenta/app` and `*-app` packagesRun class discovery while building application cache.Quick Start
-----------

[](#quick-start)

```
use Componenta\ClassFinder\ClassFinder;
use Componenta\ClassFinder\Filter\InstantiableFilter;
use Componenta\ClassFinder\Filter\PatternFilter;

$finder = new ClassFinder([
    PatternFilter::endsWith('Controller'),
    new InstantiableFilter(),
]);

$controllers = $finder->find(__DIR__ . '/src', exclude: ['tests']);

foreach ($controllers as $file => $classInfo) {
    echo $classInfo->fullyQualifiedName . PHP_EOL;
}
```

Search Modes
------------

[](#search-modes)

`find()` accepts the tokenizer search bitmask directly:

```
use Componenta\Tokenizer\TokenizerInterface;

$classes = $finder->find('src/', mode: TokenizerInterface::SEARCH_CLASSES);
$contracts = $finder->find(
    'src/',
    mode: TokenizerInterface::SEARCH_INTERFACES | TokenizerInterface::SEARCH_TRAITS,
);
```

Search mode is a per-call argument. It is not a DI configuration key.

ClassIterator
-------------

[](#classiterator)

`ClassFinder::find()` returns `ClassIteratorInterface`: lazy, replayable, countable, arrayable, and filterable.

```
$classes = $finder->find('src/');

$classes->count();   // forces traversal once and caches the count
$classes->toArray(); // list

$filtered = $classes->withFilter(PatternFilter::namespace('App\\Http'));
```

The iterator caches traversed declarations so it can be iterated more than once.

Pattern Filters
---------------

[](#pattern-filters)

`PatternFilter` matches `ClassInfo` metadata without reflection.

```
use Componenta\ClassFinder\Filter\PatternFilter;

new PatternFilter('*Controller');        // class name suffix
new PatternFilter('User*');              // class name prefix
new PatternFilter('App\\User');          // exact fully-qualified class name
new PatternFilter('*\\Api\\*Controller'); // fully-qualified wildcard pattern

PatternFilter::exactMatch('UserController');
PatternFilter::namespace('App\\Http');              // namespace and children
PatternFilter::exactNamespace('App\\Http\\Admin');  // exact namespace only
PatternFilter::exactFqn('App\\Http\\UserController');
PatternFilter::fqn('App\\*\\*Controller');
PatternFilter::in(['UserController', 'PostController']);
```

Use `exactNamespace()` when the input is a namespace without wildcard. A bare string containing `\` is treated as a fully-qualified class name or FQN pattern.

Reflection Filters
------------------

[](#reflection-filters)

Some filters require the declaration to be loaded because they use `ClassInfo::$reflector`:

- `AttributeSearchFilter`
- `AttributePatternFilter`
- `AnyAttributeFilter`
- `HasAnyAttributesFilter`
- `ImplementsFilter`
- `ImplementsAnyFilter`
- `SubclassFilter`

These filters are appropriate when scanned classes are autoloadable. For pure source inspection of unloaded files, prefer metadata-only filters such as `PatternFilter`, `InstantiableFilter`, `IsAbstractFilter`, and `IsFinalFilter`.

Attribute Filters
-----------------

[](#attribute-filters)

```
use Componenta\ClassFinder\Filter\AttributePatternFilter;
use Componenta\ClassFinder\Filter\AttributeSearchFilter;
use Componenta\ClassFinder\Filter\AnyAttributeFilter;

AttributeSearchFilter::hasAttribute(Route::class);
AttributeSearchFilter::hasAnyAttribute([Route::class, Command::class]);
AttributeSearchFilter::hasAllAttributes([Cache::class, Validate::class]);
AttributeSearchFilter::hasAttribute(Inject::class, deepSearch: true);

new AttributePatternFilter('*Attribute', deepSearch: true);
AttributePatternFilter::attributePrefix('App\\Attribute\\');

new AnyAttributeFilter([Route::class, Command::class], deepSearch: true);
```

`deepSearch: true` also checks methods, properties, and constants.

Listeners
---------

[](#listeners)

Listeners are notified for each accepted declaration. Finalizable listeners are finalized after scanning, even when no declarations were found.

```
use Componenta\ClassFinder\ClassListenerInterface;
use Componenta\ClassFinder\FinalizableListenerInterface;
use Componenta\Tokenizer\ClassInfo;

final class RouteCollector implements FinalizableListenerInterface
{
    /** @var list */
    private array $routes = [];

    public function handle(ClassInfo $info): void
    {
        if ($info->reflector->getAttributes(Route::class) !== []) {
            $this->routes[] = $info->fullyQualifiedName;
        }
    }

    public function finalize(): void
    {
        // Build final registry or cache.
    }
}
```

`ClassListenerNotifier` snapshots the provider's listeners once per `notify()`call, so the same listener instances receive `handle()` and `finalize()`.

When a finalizable listener is later compiled into an application build cache, it should also implement `FinalizationStateInterface`. The `finalized`property becomes `true` only after a successful `finalize()` call. Repeated finalization may be rejected by the listener; the package provides `FinalizationExceptionInterface` and `ListenerAlreadyFinalizedException` for that case.

Compile Integration
-------------------

[](#compile-integration)

Packages that collect metadata through listeners can expose compilers without depending on an application runner:

```
use Componenta\ClassFinder\Compile\CompileResult;
use Componenta\ClassFinder\Compile\ListenerCompilerInterface;

final class RouteCollectorCompiler implements ListenerCompilerInterface
{
    public function supports(object $listener): bool
    {
        return $listener instanceof RouteCollector;
    }

    public function compile(object $listener, string $cacheDir): CompileResult
    {
        return CompileResult::filesOnly([
            $cacheDir . '/routes.cache.php' => '
