Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

List Classes In Directory Laravel Package

wyrihaximus/list-classes-in-directory

A small utility that scans a directory and returns the fully qualified class names found inside it. Handy for auto-discovery, registering implementations, building class maps, or wiring up plugin-style architectures without manually maintaining lists.

View on GitHub
Deep Wiki
Context7

Getting Started

Install it like any small utility — there are no service providers, no config file, no Laravel auto-discovery:

composer require wyrihaximus/list-classes-in-directory

The package API is deliberately tiny. The entry point accepts either a directory or a single file and returns a flat array of fully-qualified class names as strings:

use WyriHaximus\ListClassesInDirectory\ListClassesInDirectory;

$classes = ListClassesInDirectory::list(__DIR__ . '/src');

// ['App\Plugins\Foo', 'App\Plugins\Bar', ...]
print_r($classes);

First use case worth wiring up today: stop maintaining hand-written class lists. Anywhere you currently have a hardcoded array of classes (a plugin registry, a list of Artisan commands, a list of policies, a list of models for a generic admin panel), replace it with a directory scan:

// app/Providers/PluginServiceProvider.php
public function register(): void
{
    foreach (ListClassesInDirectory::list(app_path('Plugins')) as $plugin) {
        if (is_subclass_of($plugin, Plugin::class)) {
            $this->app->bind($plugin);
        }
    }
}

Where to look first in the repo: the README for the exact method name on your pinned tag, then src/ — it is a few hundred lines and you should read the tokenizer wrapper once so you know exactly what it does and does not return.

Implementation Patterns

Attribute-based discovery (Laravel + PHP 8 attributes)

This is the highest-leverage pattern. Scan a directory, reflect, filter by attribute:

use Illuminate\Support\Facades\Cache;
use WyriHaximus\ListClassesInDirectory\ListClassesInDirectory;

final class Discover
{
    /** @return list<class-string> */
    public static function withAttribute(string $path, string $attribute): array
    {
        $found = [];

        foreach (ListClassesInDirectory::list(base_path($path)) as $fqcn) {
            if (!class_exists($fqcn)) {   // triggers autoload; guard it
                continue;
            }

            if ((new ReflectionClass($fqcn))->getAttributes($attribute) !== []) {
                $found[] = $fqcn;
            }
        }

        return $found;
    }
}
foreach (Discover::withAttribute('app/Domain', AsQuery::class) as $query) {
    // register handlers, build a dashboard, generate docs...
}

Interface / plugin registry

Prefer string-based checks (is_subclass_of($fqcn, Contract::class, true)) over instantiating during discovery — you avoid constructing objects you may not need and you sidestep constructor dependencies:

$handlers = array_values(array_filter(
    ListClassesInDirectory::list(app_path('Handlers')),
    static fn (string $c) => is_subclass_of($c, Handler::class),
));

Artisan command auto-registration

Inside a package or module provider, this removes the "forgot to add the command" class of bug:

public function register(): void
{
    $commands = array_filter(
        ListClassesInDirectory::list(__DIR__ . '/Commands'),
        static fn (string $c) => is_subclass_of($c, \Illuminate\Console\Command::class),
    );

    $this->commands($commands);
}

Cache the scan

Tokenizing a directory is cheap but not free, and it will hit every request if you wire it into a provider. Cache the class list — not the objects:

$map = Cache::rememberForever('class-map:app', fn () => ListClassesInDirectory::list(app_path()));

In development, add a composer dump-autoload / deploy hook or a php artisan cache:forget step, or key the cache on filemtime() of the directory so adding a file busts it automatically.

Combining with reflection for "declared vs. autoloadable"

The package tells you what is declared in source, not what Composer can load. In practice you want a two-stage pipeline: list → class_exists() → reflect. Anything that fails class_exists() is either a namespace/path mismatch or a stale classmap, and that is a useful lint signal on its own.

Gotchas and Tips

  • It returns strings, not ReflectionClass objects, and it never autoloads. That is the feature: you can list classes whose parent class is missing or whose constructor is expensive. Only reach for class_exists() / ReflectionClass when you actually need metadata, and be aware that this is the moment autoload failures can surface.
  • The FQCN is derived from the namespace token in the file, not from composer.json PSR-4. A file whose namespace doesn't match its path will produce a name that Composer cannot autoload. If your scans return classes you can't resolve, check the namespace declaration first.
  • Paths are relative to the current working directory. Always pass base_path(...), app_path(...), or an absolute __DIR__-based path. A provider that works via artisan may break under a queue worker with a different cwd.
  • Multiple classes per file and conditional declarations. The tokenizer sees every class-like declaration in the file, including ones wrapped in if (!class_exists(...)) guards. Filter with class_exists() if that matters to you.
  • Anonymous classes are not returned (they have no name to report), and class_alias() calls are invisible — the package reads syntax, not runtime state.
  • Enum / readonly / #[\Attribute] support depends on your pinned version. The last release (2024-07) targets modern PHP, but if you're on an older tag and enums silently don't appear, that's why. Bump the package rather than writing a fallback.
  • Don't point it at vendor/, storage/, or node_modules/. There is no built-in exclusion list. For anything larger than app/, either pass narrower subdirectories or wrap the call in a small helper with an ignore predicate.
  • Method name drift across tags. The API surface is one static method (historically also exposed as a namespaced function). If the call in the README doesn't match your installed version, read src/ — it's a five-minute job and saves guessing.
  • Debugging tip: when a scan returns an unexpected result, run the list against a single file first, then against the directory. Almost every "missing class" report is a namespace mismatch or a path that is one level off, not a parser bug.
  • Extension points are minimal by design. There is no pluggable file finder or AST visitor — it's a tokenizer over a directory iterator. If you need .php extension filtering, glob patterns, or PSR-4 reverse-mapping, build a thin wrapper around it and keep that logic in your own App\Support class so you have one place to change later.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource