Product Decisions This Supports
- Modular and Plugin-Based Architectures: Enables dynamic discovery and registration of classes (e.g., workflow nodes, event listeners, or policies) without manual configuration, reducing boilerplate and improving maintainability. Aligns seamlessly with Laravel’s plugin/extension patterns and supports a composable architecture.
- Developer Productivity: Accelerates development by automating repetitive tasks like class registration, allowing teams to focus on core business logic. Ideal for systems requiring frequent updates, third-party integrations, or rapid prototyping.
- Build vs. Buy Decision: Replaces ad-hoc directory scanning logic (e.g.,
glob() + ReflectionClass) with a battle-tested, actively maintained package, reducing technical debt and improving reliability. Avoids reinventing the wheel for a common but complex problem.
- Roadmap Priorities:
- Extensibility: Supports plugin architectures by dynamically loading classes that implement specific interfaces or traits (e.g.,
PaymentGateway, AuthProvider). Enables a marketplace-like extensibility for internal tools.
- Conditional and Environment-Aware Loading: Enables feature flags or environment-based class registration (e.g.,
hasTrait('Serializable')->when(fn() => config('features.enabled'))), supporting gradual rollouts and A/B testing.
- Testing and Mocking: Simplifies test data generation by scanning for classes with specific methods (e.g.,
hasMethod('generateTestData')), improving test coverage and CI/CD efficiency.
- CLI and Artisan Tools: Loads commands from
app/Console/Commands that implement custom traits (e.g., QueuedCommand), enabling self-documenting CLI tools.
- Use Cases:
- Workflow Engines: Auto-register all non-abstract
Node implementations in a directory (e.g., app/Workflow/Nodes) to build dynamic, data-driven workflows.
- Event-Driven Systems: Dynamically register listeners or subscribers implementing
ShouldQueue or HandlesEvents, reducing manual wiring in EventServiceProvider.
- Domain-Specific Logic: Discover and instantiate classes in namespaces like
app/Policies, app/Rules, or app/Observers to enforce consistent domain patterns.
- Dynamic Configuration: Load and merge configurations from classes implementing
Configurable trait, enabling runtime configuration overrides.
- Legacy System Integration: Bridge legacy codebases by scanning for classes with specific signatures (e.g.,
hasMethod('legacyProcess')) and wrapping them in modern interfaces.
When to Consider This Package
Adopt When:
- You need to dynamically discover and filter classes/files in Laravel based on PSR-4 paths, traits, inheritance, methods, or abstract status, and require a maintained, expressive solution.
- Your project demands auto-registration of classes (e.g., for workflows, plugins, or event listeners) to eliminate boilerplate and improve scalability.
- You’re building a modular, plugin-based, or microservice-like system where components should be conditionally loaded based on runtime conditions (e.g., environment, features flags).
- You want to eliminate manual class registration in service providers, configuration files, or facades, reducing cognitive load and errors.
- Your stack includes Laravel 11+ and PHP 8.1+, and you prefer a lightweight, MIT-licensed solution with active maintenance.
- You need lazy-loaded collections to avoid memory overhead from eager-loading all classes/files upfront, especially in large codebases or monorepos.
- You’re working with complex directory structures and need to filter classes based on specific criteria (e.g., traits, methods, or inheritance hierarchies).
- You require PSR-4 compliant classname resolution and want to leverage Composer autoload mappings for accurate discovery.
- You need to integrate third-party or vendor classes dynamically (e.g., scanning
vendor/ for implementations of custom interfaces).
Look Elsewhere When:
- You need cross-language support (e.g., Python, Node.js) or non-PHP file operations (use language-specific tools or frameworks).
- You require advanced file operations beyond discovery (e.g., diffing, merging, real-time watching, or file content analysis). Consider tools like Symfony Finder, Laravel Scout, or custom
SplFileInfo logic.
- Your project uses non-PSR-4 class naming conventions (e.g., custom autoloading or legacy namespaces). Customize
resolveClassnameUsing or pre-process paths, but expect additional maintenance.
- You’re targeting Laravel <11 or PHP <8.1. Fork/backport the package or use lower-level tools like
SplFileInfo, ReflectionClass, or get_declared_classes().
- You need vendor-agnostic class discovery beyond PSR-4 (e.g., for frameworks like Symfony or custom autoloaders). Override resolvers or combine with
class_alias() or get_declared_classes().
- You require database-backed or remote class discovery (e.g., for distributed systems). Use service discovery tools (e.g., Consul, etcd) or microservice registries.
- You’re working in a non-Laravel PHP environment (e.g., Lumen, Symfony, or standalone PHP). Use the standalone mode with
setBasePath() or adapt the logic to your framework’s conventions.
- You need real-time file watching or live reloading (e.g., for development tools). Use Laravel Mix, Vite, or custom
fsnotify bindings.
- You require performance-critical scanning (e.g., for build tools or compilers). Benchmark against alternatives like
glob() + ReflectionClass or consider caching results.
How to Pitch It (Stakeholders)
For Executives:
*"Lody is a game-changer for developer velocity in Laravel projects, automating the tedious task of manually registering classes—like workflow steps, event listeners, or CLI commands. Instead of writing repetitive code to scan directories and filter classes, our team can auto-discover and register components in a single line, like this:
Lody::classes('app/Workflow/Nodes')
->isNotAbstract()
->isInstanceOf(Node::class)
->each(fn ($class) => $this->register($class));
Why this matters:
- Reduces engineering time by 30–50% for modular systems, freeing up resources for high-impact features.
- Future-proofs our architecture for plugins, dynamic workflows, and third-party integrations.
- Low-risk adoption: MIT-licensed, actively maintained, and fully compatible with our Laravel stack (11+).
- Scalable: Handles large codebases efficiently with lazy loading, avoiding memory bloat.
- Consistent: Enforces patterns like PSR-4 and reduces errors from manual registration.
By adopting Lody, we’ll cut technical debt, accelerate feature delivery, and empower teams to focus on business logic—not boilerplate. It’s a no-brainer for projects prioritizing modularity and scalability."*
For Engineers:
*"Lody replaces spaghetti glob/class_exists hacks with a clean, expressive API for discovering and filtering classes/files. Here’s why you’ll love it:
- Lazy Collections: No upfront performance cost—ideal for large directories or monorepos.
- Fluent Filtering: Chain methods like
isInstanceOf(), hasTrait(), or hasMethod() to precisely target classes:
Lody::classes('app/Rules')
->hasMethod('passes')
->doesNotHaveTrait('Deprecated')
->each(fn ($rule) => Rule::extend($rule, fn () => new $rule()));
- PSR-4 Compliant: Resolves classnames using Composer autoload mappings (works in
vendor/ too).
- Customizable: Override path resolution or classname logic to fit edge cases:
Lody::resolveClassnameUsing(fn (SplFileInfo $file) => 'App\\' . str_replace(['/', '.php'], ['\\', ''], $file->getRelativePathname()));
- Standalone or Laravel: Works out-of-the-box in Laravel or as a standalone tool with
setBasePath().
- Testable: Simplifies test data generation (e.g.,
hasMethod('generateTestData')) and mocking.
Use Cases You’ll Care About:
- Auto-register workflow nodes: Scan
app/Workflow/Nodes and instantiate non-abstract classes implementing Node.
- Dynamic event listeners: Load subscribers from
app/Listeners that implement ShouldQueue.
- CLI plugins: Discover commands in
app/Console/Commands with custom traits (e.g., QueuedCommand).
- Legacy integration: Wrap old classes with modern interfaces by scanning for specific methods.
- Feature flags: Conditionally load classes based on runtime config (e.g.,
hasTrait('BetaFeature')->when(fn () => config('beta.enabled'))).
Migration Path: Replace manual glob() + ReflectionClass logic with Lody’s fluent API—**zero