PHPackages                             kariricode/class-discovery - 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. [Utility &amp; Helpers](/categories/utility)
4. /
5. kariricode/class-discovery

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

kariricode/class-discovery
==========================

PHP 8.4+ class discovery component with attribute scanning, token-based parsing, multi-tier caching, and dependency analysis. Part of the KaririCode Framework ecosystem.

v1.1.0(2mo ago)00MITPHPPHP ^8.4CI passing

Since Mar 3Pushed 2mo agoCompare

[ Source](https://github.com/KaririCode-Framework/kariricode-classdiscovery)[ Packagist](https://packagist.org/packages/kariricode/class-discovery)[ RSS](/packages/kariricode-class-discovery/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (1)DependenciesVersions (4)Used By (0)

KaririCode ClassDiscovery
=========================

[](#kariricode-classdiscovery)

[![PHP 8.4+](https://camo.githubusercontent.com/270717987f5341772d79b57567226e54ed27b2d4199bbdc98a96e2edf24902fa/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e342532422d3737374242343f6c6f676f3d706870266c6f676f436f6c6f723d7768697465)](https://www.php.net/)[![License: MIT](https://camo.githubusercontent.com/1e64768fef09f35b66921728160f533208fd2e3e792a2755187d16c25d535511/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d3232633535652e737667)](LICENSE)[![PHPStan Level 9](https://camo.githubusercontent.com/a812723b363d3726b682e5d739e91f2ade163846054ce3797b9085b84cc61806/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d4c6576656c253230392d344634364535)](https://phpstan.org/)[![Tests](https://camo.githubusercontent.com/81789ab2398d566943571abcd59685f11ad58888e3d9e4e3866b00fcaa03b2a3/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f54657374732d32323225323070617373696e672d323263353565)](https://kariricode.org)[![Coverage](https://camo.githubusercontent.com/e59c340f38b70ddb53a8b25e02153e091dc16ef7793b12537af2e37c730a6ad1/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f436f7665726167652d39352e34342532352d323263353565)](https://kariricode.org)[![Zero Dependencies](https://camo.githubusercontent.com/b23453f8bb7b1e49fa5272ef23947cc9f8f2e82222cdcdb1469f61a436ec07fe/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f646570656e64656e636965732d7a65726f2d627269676874677265656e)](https://kariricode.org)[![KaririCode Framework](https://camo.githubusercontent.com/bd3e3709bf161ac982b76f7afd06c39afe478d15f2b5e1d47df8606b5c9c03f0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4b6172697269436f64652d4672616d65776f726b2d6f72616e6765)](https://kariricode.org)

**PHP 8.4+ class discovery — token-based scanning, attribute automation, multi-tier caching and dependency analysis.**

Discover controllers, services, and event listeners automatically — without loading a single class.

[Installation](#installation) · [Quick Start](#quick-start) · [Use Cases](#real-world-use-cases) · [Scanners](#scanner-comparison) · [Caching](#caching) · [CI Integration](#ci-integration)

---

The Problem
-----------

[](#the-problem)

Modern PHP applications rely on convention over configuration — routes registered from `#[Route]`, services injected from `#[Service]`, listeners wired from `#[EventListener]`. Without a discovery engine, teams end up writing this manually:

```
// routes.php — manually maintained, drifts over time
Router::get('/users', [UserController::class, 'index']);
Router::post('/users', [UserController::class, 'store']);
Router::get('/products', [ProductController::class, 'index']);
// ... 300 more lines
```

And for services:

```
// bootstrap.php — duplicated, error-prone
$container->bind(MailerService::class, MailerService::class);
$container->bind(PaymentService::class, PaymentService::class);
// ... one entry per class forever
```

The Solution
------------

[](#the-solution)

```
composer require kariricode/class-discovery
```

```
use KaririCode\ClassDiscovery\Filter\AttributeFilter;
use KaririCode\ClassDiscovery\Scanner\{ComposerNamespaceResolver, FileScanner};

$scanner = new FileScanner(new ComposerNamespaceResolver());
$scanner->addFilter(new AttributeFilter(Route::class));

// Discovers and returns all #[Route]-annotated controllers — instantly
$result = $scanner->scan(['src/Controller']);
```

One scan replaces hundreds of manual registrations. Results are **immutable**, **cacheable**, and resolved in **30–80ms cold / &lt;3ms warm**.

---

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

[](#requirements)

RequirementVersionPHP8.4 or higherComposer2.x---

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

[](#installation)

```
composer require kariricode/class-discovery
```

**Optional integrations:**

```
composer require kariricode/cache                   # PSR-16 cache backend
composer require kariricode/configurator            # Environment-aware configuration
composer require kariricode/dependency-injection    # PSR-11 container
```

---

Quick Start
-----------

[](#quick-start)

### 1 — Discover all classes

[](#1--discover-all-classes)

```
use KaririCode\ClassDiscovery\Scanner\{ComposerNamespaceResolver, FileScanner};

$scanner = new FileScanner(new ComposerNamespaceResolver());
$result  = $scanner->scan(['src/']);

foreach ($result as $fqcn => $metadata) {
    echo "{$fqcn} — {$metadata->filePath}\n";
}

echo "Found " . $result->count() . " classes in "
   . round($result->getScanDuration() * 1000, 1) . "ms\n";
```

### 2 — Filter by attribute

[](#2--filter-by-attribute)

```
use KaririCode\ClassDiscovery\Filter\AttributeFilter;

$scanner->addFilter(new AttributeFilter(Route::class));
$result = $scanner->scan(['src/Controller']);
```

### 3 — Enable caching

[](#3--enable-caching)

```
use KaririCode\ClassDiscovery\Cache\{ChainCacheStrategy, FileCacheStrategy, MemoryCacheStrategy};

$cache = new ChainCacheStrategy(
    new MemoryCacheStrategy(),
    new FileCacheStrategy('/var/cache/discovery'),
);

$scanner->setCacheStrategy($cache);
$result = $scanner->scan(['src/']);  // cold: ~60ms · warm: addFilter(new AttributeFilter(Route::class));
$result  = $scanner->scan(['src/Controller']);

foreach ($result as $fqcn => $metadata) {
    $router->mount($fqcn);  // class-level #[Route]

    foreach ($metadata->methods as $method) {
        if ($method->hasAttribute('Route')) {
            // Use ReflectionScanner to read path / method values
        }
    }
}
```

---

### DI Container — auto-register services from `#[Service]`

[](#di-container--auto-register-services-from-service)

```
$scanner = new FileScanner(new ComposerNamespaceResolver());
$scanner->addFilter(new AttributeFilter(Service::class));
$scanner->setCacheStrategy($cache);  // warm: scan(['src/Service', 'src/Repository']);

foreach ($result as $fqcn => $metadata) {
    $container->bind($fqcn, $fqcn);  // zero manual registration
}
```

---

### Event Dispatcher — auto-wire listeners from `#[EventListener]`

[](#event-dispatcher--auto-wire-listeners-from-eventlistener)

```
use KaririCode\ClassDiscovery\Scanner\ReflectionScanner;

// ReflectionScanner reads actual attribute constructor values
$scanner = new ReflectionScanner(new ComposerNamespaceResolver());
$scanner->addFilter(new AttributeFilter(EventListener::class));
$result  = $scanner->scan(['src/Listener']);

foreach ($result as $fqcn => $metadata) {
    foreach ($metadata->attributes as $attrMeta) {
        if ($attrMeta->instance instanceof EventListener) {
            $dispatcher->listen(
                event   : $attrMeta->instance->event,
                listener: $fqcn,
                priority: $attrMeta->instance->priority,
            );
        }
    }
}
```

---

### Plugin System — discover extensions by interface

[](#plugin-system--discover-extensions-by-interface)

```
use KaririCode\ClassDiscovery\Filter\InterfaceFilter;

$scanner = new FileScanner(new ComposerNamespaceResolver());
$scanner->addFilter(new InterfaceFilter(PaymentGatewayInterface::class));

$result = $scanner->scan(['plugins/']);

foreach ($result as $fqcn => $metadata) {
    $registry->register(new $fqcn());
}
```

---

### Refactoring Guard — detect circular dependencies

[](#refactoring-guard--detect-circular-dependencies)

```
use KaririCode\ClassDiscovery\Analyzer\{CircularDetector, DependencyAnalyzer};

$scanner  = new FileScanner(new ComposerNamespaceResolver());
$result   = $scanner->scan(['src/']);

$detector = new CircularDetector(
    new DependencyAnalyzer(),
    throwOnDetection: true,  // throws DiscoveryException::circularDependency()
);

$cycles = $detector->check($result);
// [['App\A', 'App\B', 'App\C', 'App\A']]
```

---

### Console — auto-register commands from `#[Command]`

[](#console--auto-register-commands-from-command)

```
use KaririCode\ClassDiscovery\Filter\{AttributeFilter, NamespaceFilter};

$scanner = new FileScanner(new ComposerNamespaceResolver());
$scanner->addFilter(new AttributeFilter(Command::class));
$scanner->addFilter(new NamespaceFilter('App\\Console\\Command'));

$result = $scanner->scan(['src/Console']);

foreach ($result as $fqcn => $metadata) {
    $application->add(new $fqcn());
}
```

---

Scanner Comparison
------------------

[](#scanner-comparison)

ScannerParserLoads ClassesPerformance\*Best For`FileScanner``token_get_all`❌ Never30–80ms cold / &lt;3ms warmGeneral listing, attribute names`AttributeScanner`FileScanner + filter❌ Never50–100ms cold / &lt;5ms warmAttribute-driven discovery`DirectoryScanner`FileScanner + constraints❌ Neverdep. on I/ODepth/pattern-bounded scan`ReflectionScanner``ReflectionClass`✅ Required300–800msFull attribute argument values\*per 1,000 classes — with cache, warm scans are typically **5–10× faster**

---

Filters
-------

[](#filters)

```
use KaririCode\ClassDiscovery\Filter\{
    AttributeFilter,
    InterfaceFilter,
    NamespaceFilter,
    StructuralFilter,
    CompositeFilter,
};

// By attribute (OR — multiple attribute classes)
$scanner->addFilter(new AttributeFilter(Route::class, Command::class));

// By implemented interface
$scanner->addFilter(new InterfaceFilter(HandlerInterface::class));

// By namespace prefix
$scanner->addFilter(new NamespaceFilter('App\\Handler'));

// By structural characteristics
$scanner->addFilter(new StructuralFilter(isFinal: true, isReadonly: true));

// OR-logic composite (attribute OR interface)
$scanner->addFilter(new CompositeFilter(
    requireAll: false,
    new AttributeFilter(Route::class),
    new InterfaceFilter(ControllerInterface::class),
));
```

---

Caching
-------

[](#caching)

```
use KaririCode\ClassDiscovery\Cache\{
    ChainCacheStrategy,     // L1 → L2 chain with automatic promotion
    FileCacheStrategy,      // Atomic writes (temp → rename), OPcache-friendly
    MemoryCacheStrategy,    // In-process, process-lifetime
};

// Multi-tier: Memory (L1) → File (L2)
$cache = new ChainCacheStrategy(
    new MemoryCacheStrategy(defaultTtl: 60),
    new FileCacheStrategy('/var/cache/discovery', defaultTtl: 3600),
);

$scanner->setCacheStrategy($cache);

// First request  : hits filesystem (~60ms)
// Every request after: hits memory   (scan(['src/']);
```

---

PSR-11 Container Registration
-----------------------------

[](#psr-11-container-registration)

```
use KaririCode\ClassDiscovery\Integration\PSR11Integration;
use KaririCode\ClassDiscovery\Contract\{Scanner, AttributeScanner};

$container->singleton(
    Scanner::class,
    fn () => PSR11Integration::createDefaultScanner($cache),
);

$container->singleton(
    AttributeScanner::class,
    fn () => PSR11Integration::createAttributeScanner($cache),
);
```

---

Error Handling
--------------

[](#error-handling)

```
use KaririCode\ClassDiscovery\Exception\DiscoveryException;

try {
    $result = $scanner->scan(['/path/that/does/not/exist']);
} catch (DiscoveryException $e) {
    match ($e->getCode()) {
        1001 => handlePathNotFound($e),
        1002 => handlePathTraversal($e),
        1003 => handleSymlinkEscape($e),
        1004 => handleMaxDepthExceeded($e),
        1005 => handleMaxFilesExceeded($e),
        1010 => handleCircularDependency($e),
        default => throw $e,
    };
}
```

---

CI Integration
--------------

[](#ci-integration)

### GitHub Actions — unified pipeline

[](#github-actions--unified-pipeline)

```
name: Quality
on: [push, pull_request]
jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.4'
          coverage: pcov
      - run: composer install --no-progress --no-scripts
      - run: vendor/bin/kcode init
      - run: vendor/bin/kcode quality
```

### GitHub Actions — parallel jobs

[](#github-actions--parallel-jobs)

```
jobs:
  cs-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with: { php-version: '8.4' }
      - run: composer install --no-progress --no-scripts
      - run: vendor/bin/kcode init && vendor/bin/kcode cs:fix --check

  analyse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with: { php-version: '8.4' }
      - run: composer install --no-progress --no-scripts
      - run: vendor/bin/kcode init && vendor/bin/kcode analyse

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with: { php-version: '8.4', coverage: pcov }
      - run: composer install --no-progress --no-scripts
      - run: vendor/bin/kcode init && vendor/bin/kcode test
```

---

Architecture
------------

[](#architecture)

### Component layout

[](#component-layout)

```
src/
├── Contract/      7 interfaces (ISP-compliant)
├── Scanner/       5 implementations (composition pattern)
├── Result/        5 final readonly DPOs (ARFA 1.3 P1)
├── Filter/        5 composable predicates (AND/OR)
├── Cache/         3 cache strategies (Chain, Memory, File)
├── Analyzer/      2 analyzers (dependency graph + DFS cycle detection)
├── Integration/   3 bridges (PSR-16, PSR-11, Configurator)
├── Enum/          2 PHP 8.1 enums (backed + pure)
└── Exception/     1 exception class, 10 named constructors

```

### Key design decisions

[](#key-design-decisions)

DecisionRationaleADRToken-based parsingNever loads or executes discovered classes[ADR-001](docs/adr/ADR-001-token-based-scanning.md)Zero external dependenciesWorks in any PHP 8.4 project, no version conflicts[ADR-002](docs/adr/ADR-002-zero-external-dependencies.md)Immutable result objectsThread-safe, cacheable, ARFA 1.3 compliant[ADR-003](docs/adr/ADR-003-immutable-discovery-results.md)Composition over inheritanceScanners compose `FileScanner`; no deep hierarchies[ADR-005](docs/adr/ADR-005-composition-over-inheritance.md)Multi-tier cache strategyMemory (L1) → File (L2), 5–10× warm speedup[ADR-006](docs/adr/ADR-006-multi-tier-cache-strategy.md)### Specifications

[](#specifications)

SpecCovers[SPEC-001](docs/spec/SPEC-001-component-specification.md)Component architecture, contracts, scanner strategies---

Project Stats
-------------

[](#project-stats)

MetricValuePHP source files33Total source lines~2,800External runtime dependencies0Filter types5 (Attribute, Interface, Namespace, Structural, Composite)Scanner strategies4 (File, Attribute, Directory, Reflection)Cache strategies3 (Memory, File, Chain)PHPStan level9 (0 errors)Psalm level3 (0 errors)Test suite222 tests · 440 assertionsLine coverage95.44%PHP version8.4+ARFA compliance1.3---

KaririCode Ecosystem Integration
--------------------------------

[](#kariricode-ecosystem-integration)

ComponentAttributes Discovered`kariricode/router``#[Route]`, `#[Middleware]`, `#[Guard]``kariricode/console``#[Command]`, `#[Argument]`, `#[Option]``kariricode/dependency-injection``#[Singleton]`, `#[Scoped]`, `#[Tagged]``kariricode/event-dispatcher``#[EventListener]`, `#[EventSubscriber]``kariricode/websocket``#[WebSocketRoute]`, `#[OnConnect]`, `#[OnMessage]``kariricode/i18n``#[TranslatableResource]`---

Contributing
------------

[](#contributing)

```
git clone https://github.com/kariricode/class-discovery.git
cd class-discovery
composer install
vendor/bin/kcode init
vendor/bin/kcode quality   # Must pass before opening a PR
```

CI enforces code quality (PHPStan level 9, Psalm, CS-Fixer, PHPUnit) on every push and PR.

---

License
-------

[](#license)

[MIT License](LICENSE) © [Walmir Silva](mailto:walmir.silva@kariricode.org)

---

Part of the **[KaririCode Framework](https://kariricode.org)** ecosystem.

[kariricode.org](https://kariricode.org) · [GitHub](https://github.com/kariricode) · [Packagist](https://packagist.org/packages/kariricode/)

###  Health Score

38

—

LowBetter than 85% of packages

Maintenance86

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity53

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

74d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/e42da33070ae9e744b18f4be67303b8935f6e14488a88328e7c121d909b4f2db?d=identicon)[growthcodeoficial](/maintainers/growthcodeoficial)

---

Top Contributors

[![walmir-silva](https://avatars.githubusercontent.com/u/142913591?v=4)](https://github.com/walmir-silva "walmir-silva (24 commits)")

### Embed Badge

![Health badge](/badges/kariricode-class-discovery/health.svg)

```
[![Health](https://phpackages.com/badges/kariricode-class-discovery/health.svg)](https://phpackages.com/packages/kariricode-class-discovery)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
