lorisleiva/lody
Lody loads files or PHP classes from one or more paths as a Laravel LazyCollection. Discover classes via PSR-4, filter by traits/interfaces/abstract status, and iterate to auto-register services, nodes, handlers, etc.
Installation:
composer require lorisleiva/lody
No additional configuration is required for basic usage in Laravel.
First Use Case:
Discover and register all non-abstract Node implementations in a workflow directory:
use Lorisleiva\Lody\Lody;
Lody::classes('app/Workflow/Nodes')
->isNotAbstract()
->isInstanceOf(Node::class)
->each(fn (string $classname) => $this->register($classname));
Where to Look First:
Usage section for core methods (Lody::files(), Lody::classes()).isInstanceOf(), hasTrait(), or hasMethod().resolvePathUsing() or resolveClassnameUsing() if paths or classnames don’t resolve as expected.Pattern: Auto-register classes implementing a specific interface/trait (e.g., for event listeners, policies, or workflow nodes).
// Register all non-abstract event listeners in a directory
Lody::classes('app/Listeners')
->isNotAbstract()
->isInstanceOf(ShouldQueue::class)
->each(fn ($class) => $this->app->bind('queue-listener', $class));
Pattern: Load classes based on environment or feature flags.
if (config('features.workflows.enabled')) {
Lody::classes('app/Workflow/Nodes')
->isNotAbstract()
->each(fn ($class) => $this->registerNode($class));
}
Pattern: Discover and instantiate plugins in a plugins/ directory.
Lody::classes('plugins')
->hasMethod('boot')
->each(fn ($pluginClass) => $this->loadPlugin(new $pluginClass()));
Pattern: Find classes with a generateTestData() method to populate fixtures.
Lody::classes('tests/DataFactories')
->hasMethod('generateTestData')
->each(fn ($factory) => $this->factories[] = new $factory());
Pattern: Register all Artisan commands in a directory that implement a custom trait.
Lody::classes('app/Console/Commands')
->hasTrait(QueuedCommand::class)
->each(fn ($command) => $this->commands[] = $command::class);
Register discovered classes in the boot() method:
public function boot()
{
Lody::classes('app/Providers/Extensions')
->isNotAbstract()
->each(fn ($provider) => $this->app->register($provider));
}
Use in package bootstrapping to auto-discover components:
if ($this->app->runningInConsole()) {
Lody::classes('config/packages/my-package')
->each(fn ($config) => $this->mergeConfigFrom($config, 'my-package'));
}
Pass a Finder instance for advanced filtering:
use Symfony\Component\Finder\Finder;
$finder = Finder::create()
->files()
->in(app_path('Rules'))
->name('*.php')
->depth(1);
Lody::classesFromFinder($finder)
->hasMethod('passes')
->each(fn ($rule) => $this->rules[] = $rule);
Combine with Laravel’s Collection for further processing:
$classes = Lody::classes('app/Commands')
->isInstanceOf(Command::class)
->values(); // Convert to array
collect($classes)->mapWithKeys(fn ($class) => [$class => new $class()]);
Path Resolution Quirks:
/ are treated as absolute; others are resolved relative to base_path().Lody::resolvePathUsing() to customize logic:
Lody::resolvePathUsing(fn ($path) => Str::startsWith($path, '/') ? $path : app_path($path));
Classname Resolution Failures:
resolveClassnameUsing():
Lody::resolveClassnameUsing(fn (SplFileInfo $file) => 'Custom\\Namespace\\' . class_basename($file));
Performance with Large Directories:
->depth(1) in Finder or limit paths:
Lody::classes('app/Commands', recursive: false);
False Positives in classExists():
getClassnames() may include invalid classes (e.g., namespaced incorrectly).->classExists() to filter:
Lody::files('app/Helpers')->getClassnames()->classExists();
Trait/Method Detection Edge Cases:
recursive: false for precise control:
->hasTrait(SomeTrait::class, recursive: false);
Inspect Raw Files:
Lody::files('app/Workflow/Nodes')->each(fn ($file) => dump($file->getPathname()));
Verify Classnames:
Lody::classes('app/Workflow/Nodes')->each(fn ($class) => dump(class_exists($class)));
Check Finder Logic:
$finder = Finder::create()->files()->in(app_path('Nodes'));
Lody::classesFromFinder($finder)->each(fn ($class) => dump($class));
Profile Performance:
Use Laravel’s Benchmark facade to measure lazy collection iteration:
use Illuminate\Support\Facades\Benchmark;
Benchmark::dd(
Lody::classes('app/Commands')->count(),
'Class discovery time'
);
Custom Resolvers:
Lody::resolvePathUsing(fn ($path) => storage_path('app/' . $path));
Add Methods to ClassnameLazyCollection:
Extend the collection to add domain-specific filters:
use Lorisleiva\Lody\ClassnameLazyCollection;
ClassnameLazyCollection::macro('isSerializable', function () {
return $this->filter(fn ($class) => in_array(Serializable::class, class_uses($class)));
});
Integrate with Laravel’s Container: Bind discovered classes dynamically:
Lody::classes('app/Repositories')
->each(fn ($repo) => $this->app->bind($repo, fn () => new $repo()));
Combine with spatie/laravel-package-tools:
Use Lody to auto-discover package components:
Lody::classes('config/packages/my-package')
->each(fn ($config) => $this->mergeConfigFrom($config, 'my-package'));
Standalone Usage: Set a custom base path for non-Laravel projects:
Lody::setBasePath(__DIR__);
Lody::files('src/Commands')->each(fn ($file) => dump($file));
How can I help you explore Laravel packages today?