Weave Code
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

Lody Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require lorisleiva/lody
    

    No additional configuration is required for basic usage in Laravel.

  2. 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));
    
  3. Where to Look First:

    • README.md: Focus on the Usage section for core methods (Lody::files(), Lody::classes()).
    • ClassnameLazyCollection: Explore filtering methods like isInstanceOf(), hasTrait(), or hasMethod().
    • Configuration: Check resolvePathUsing() or resolveClassnameUsing() if paths or classnames don’t resolve as expected.

Implementation Patterns

Core Workflows

1. Dynamic Class Registration

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));

2. Conditional Loading

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));
}

3. Plugin/Extension Discovery

Pattern: Discover and instantiate plugins in a plugins/ directory.

Lody::classes('plugins')
    ->hasMethod('boot')
    ->each(fn ($pluginClass) => $this->loadPlugin(new $pluginClass()));

4. Test Data Generation

Pattern: Find classes with a generateTestData() method to populate fixtures.

Lody::classes('tests/DataFactories')
    ->hasMethod('generateTestData')
    ->each(fn ($factory) => $this->factories[] = new $factory());

5. CLI Command Auto-Registration

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);

Integration Tips

With Service Providers

Register discovered classes in the boot() method:

public function boot()
{
    Lody::classes('app/Providers/Extensions')
        ->isNotAbstract()
        ->each(fn ($provider) => $this->app->register($provider));
}

With Laravel Packages

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'));
}

With Custom Finder Logic

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);

Chaining with Collections

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()]);

Gotchas and Tips

Pitfalls

  1. Path Resolution Quirks:

    • Issue: Paths starting with / are treated as absolute; others are resolved relative to base_path().
    • Fix: Use Lody::resolvePathUsing() to customize logic:
      Lody::resolvePathUsing(fn ($path) => Str::startsWith($path, '/') ? $path : app_path($path));
      
  2. Classname Resolution Failures:

    • Issue: Custom PSR-4 mappings or non-standard class names may break resolution.
    • Fix: Override resolveClassnameUsing():
      Lody::resolveClassnameUsing(fn (SplFileInfo $file) => 'Custom\\Namespace\\' . class_basename($file));
      
  3. Performance with Large Directories:

    • Issue: Lazy collections still iterate files on first use, which can be slow for deep directories.
    • Fix: Use ->depth(1) in Finder or limit paths:
      Lody::classes('app/Commands', recursive: false);
      
  4. False Positives in classExists():

    • Issue: getClassnames() may include invalid classes (e.g., namespaced incorrectly).
    • Fix: Chain ->classExists() to filter:
      Lody::files('app/Helpers')->getClassnames()->classExists();
      
  5. Trait/Method Detection Edge Cases:

    • Issue: Static methods or traits in parent classes may not be detected as expected.
    • Fix: Use recursive: false for precise control:
      ->hasTrait(SomeTrait::class, recursive: false);
      

Debugging Tips

  1. Inspect Raw Files:

    Lody::files('app/Workflow/Nodes')->each(fn ($file) => dump($file->getPathname()));
    
  2. Verify Classnames:

    Lody::classes('app/Workflow/Nodes')->each(fn ($class) => dump(class_exists($class)));
    
  3. Check Finder Logic:

    $finder = Finder::create()->files()->in(app_path('Nodes'));
    Lody::classesFromFinder($finder)->each(fn ($class) => dump($class));
    
  4. 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'
    );
    

Extension Points

  1. Custom Resolvers:

    • Override path or classname resolution for non-standard projects:
      Lody::resolvePathUsing(fn ($path) => storage_path('app/' . $path));
      
  2. 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)));
    });
    
  3. Integrate with Laravel’s Container: Bind discovered classes dynamically:

    Lody::classes('app/Repositories')
        ->each(fn ($repo) => $this->app->bind($repo, fn () => new $repo()));
    
  4. 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'));
    
  5. Standalone Usage: Set a custom base path for non-Laravel projects:

    Lody::setBasePath(__DIR__);
    Lody::files('src/Commands')->each(fn ($file) => dump($file));
    
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.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport