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

Classtools Laravel Package

hanneskod/classtools

Scan the filesystem for PHP classes, interfaces, and traits using Symfony Finder. Build a class-to-file map, detect parse/syntax errors, and iterate results as ReflectionClass objects, with optional autoloading for discovered classes.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require hanneskod/classtools
    
  2. Basic Usage:

    use hanneskod\classtools\Iterator\ClassIterator;
    use Symfony\Component\Finder\Finder;
    
    $finder = Finder::create()->in(app_path('Http/Controllers'));
    $iterator = new ClassIterator($finder);
    
    // Get class map (class name => SplFileInfo)
    $classMap = $iterator->getClassMap();
    
    // Iterate over ReflectionClass objects
    $iterator->enableAutoloading();
    foreach ($iterator as $reflectionClass) {
        echo $reflectionClass->getName() . "\n";
    }
    
  3. First Use Case:

    • Audit unused classes in a Laravel module:
      $unusedClasses = $iterator
          ->not($iterator->where('isUsed')) // Custom filter (see below)
          ->getClassMap();
      

Where to Look First


Implementation Patterns

Workflows

1. Class Discovery for Dynamic Registration

  • Use Case: Auto-register service providers or console commands.
$iterator = new ClassIterator(Finder::create()->in(base_path('app/Providers')));
$iterator->enableAutoloading();

foreach ($iterator->type('Illuminate\Support\ServiceProvider') as $provider) {
    $this->app->register($provider->getName());
}

2. Filtering for Laravel-Specific Logic

  • Use Case: Find all Command classes implementing ShouldQueue.
$iterator = new ClassIterator(Finder::create()->in(app_path('Console/Commands')));
$iterator->enableAutoloading();

foreach ($iterator
    ->type('Illuminate\Console\Command')
    ->where(fn($class) => in_array('ShouldQueue', class_uses($class->getName())))
    as $command) {
    // Process queued commands
}

3. Code Generation with Transformers

  • Use Case: Merge all DTO classes into a single file for API docs.
$iterator = new ClassIterator(Finder::create()->in(app_path('DTO')));
$iterator->enableAutoloading();

$mergedCode = $iterator->transform(new \hanneskod\classtools\Transformer\MinimizingWriter);
file_put_contents(public_path('api-docs/dtos.php'), $mergedCode);

4. Syntax Error Detection in CI

  • Use Case: Fail builds if syntax errors exist in app/Http.
$iterator = new ClassIterator(Finder::create()->in(app_path('Http')));
$errors = $iterator->getErrors();

if (!empty($errors)) {
    throw new \RuntimeException("Syntax errors found: " . implode(', ', $errors));
}

Integration Tips

Laravel-Specific Patterns

  1. Artisan Commands:

    use hanneskod\classtools\Iterator\ClassIterator;
    use Symfony\Component\Finder\Finder;
    
    class ClassScannerCommand extends Command
    {
        protected $signature = 'classes:scan {--path= : Path to scan}';
        protected $description = 'Scan classes in a directory';
    
        public function handle()
        {
            $iterator = new ClassIterator(Finder::create()->in($this->option('path')));
            $iterator->enableAutoloading();
    
            foreach ($iterator as $class) {
                $this->line($class->getName());
            }
        }
    }
    
  2. Service Provider Binding:

    $this->app->singleton('class.scanner', function () {
        return new ClassIterator(Finder::create()->in(app_path('Modules')));
    });
    
  3. Event Listeners:

    public function handle()
    {
        $iterator = app('class.scanner');
        $iterator->enableAutoloading();
    
        foreach ($iterator->type('App\Events\ShouldLog') as $event) {
            Log::info("Discovered event: {$event->getName()}");
        }
    }
    
  4. Custom Filters: Extend \hanneskod\classtools\Iterator\Filter\AbstractFilter to add Laravel-specific logic:

    class UsesQueueFilter extends AbstractFilter
    {
        public function accept($class)
        {
            return in_array('ShouldQueue', class_uses($class->getName()));
        }
    }
    

    Usage:

    $iterator->filter(new UsesQueueFilter());
    

Performance Optimization

  • Cache Results:

    $cache = new \Illuminate\Filesystem\Filesystem();
    $cacheKey = 'classes_scan_' . md5(app_path('Http'));
    
    if ($cache->exists($cacheKey)) {
        $classMap = unserialize($cache->get($cacheKey));
    } else {
        $iterator = new ClassIterator(Finder::create()->in(app_path('Http')));
        $classMap = $iterator->getClassMap();
        $cache->put($cacheKey, serialize($classMap), now()->addHours(1));
    }
    
  • Limit Scans to Relevant Paths: Use Laravel’s app_path(), config_path(), or database_path() to avoid scanning irrelevant directories.

Testing

  • Unit Test Filters:

    public function testUsesQueueFilter()
    {
        $iterator = new ClassIterator(Finder::create()->in(__DIR__.'/stubs'));
        $iterator->enableAutoloading();
    
        $filter = new UsesQueueFilter();
        $queuedClasses = iterator_to_array($iterator->filter($filter));
    
        $this->assertCount(1, $queuedClasses);
        $this->assertEquals('App\Events\QueuedEvent', $queuedClasses[0]->getName());
    }
    
  • Mock ReflectionClass: Use ReflectionClass::newInstanceWithoutConstructor() in tests to avoid autoloading.


Gotchas and Tips

Pitfalls

  1. Autoloading Conflicts:

    • Issue: Enabling enableAutoloading() may trigger Laravel’s autoloader recursively, causing memory spikes or infinite loops.
    • Fix: Use spl_autoload_register() with a custom loader or disable Laravel’s autoloader temporarily:
      spl_autoload_unregister([$loader, 'load']);
      $iterator->enableAutoloading();
      // Process classes...
      spl_autoload_register([$loader, 'load']);
      
  2. Namespace Resolution:

    • Issue: Classes in root namespace (e.g., class User) may not resolve correctly if the file isn’t in the root directory.
    • Fix: Ensure Finder includes the correct base path:
      Finder::create()->in(base_path())->files()->name('*.php');
      
  3. Syntax Error Handling:

    • Issue: getErrors() returns file paths, not line numbers or context.
    • Fix: Combine with Symfony\Component\Finder\SplFileInfo for details:
      foreach ($iterator->getErrors() as $file) {
          $this->error("Syntax error in {$file} (line: " . (new \SplFileInfo($file))->getLineEnd() . ")");
      }
      
  4. Windows Paths:

    • Issue: Path separators (\) may break on Unix systems.
    • Fix: Normalize paths:
      $realPath = str_replace('\\', '/', $splFileInfo->getRealPath());
      
  5. ReflectionClass Caching:

    • Issue: Repeated calls to ReflectionClass::newInstance() can be slow.
    • Fix: Cache ReflectionClass instances:
      static $reflectionCache = [];
      if (!isset($reflectionCache[$className])) {
          $reflectionCache[$className] = new \ReflectionClass($className);
      }
      

Debugging Tips

  1. Verify Class Map:

    foreach ($iterator->getClassMap() as $class => $file) {
        $this->info("{$class} => {$file->getRealPath()}");
    }
    
  2. Check Autoloading:

    if (!$iterator->isAutoloadingEnabled()) {
        $iterator->enableAutoloading();
    }
    
  3. Inspect Filters:

    $filtered = $iterator->type('App\Model')->inNamespace('App\\');
    foreach ($filtered as $class) {
        $this->line($class->getName());
    }
    
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.
emuniq/filament-browser-notifications
syriable/filament-translator
hungnm28/livewire-form
wenprise/eloquent
crudly/encrypted
fadion/bouncy
cuci/prototurk-sdk
gos/pubsub-router-bundle
cuci/prototurk-sdk-symfony
clementtalleu/easyadmin-markdown-bundle
codeflextech/permission-manager
karnoweb/livewire-datepicker
sayedenam/sayed-dashboard
milito/query-filter
apiboxsym/user-bundle
apiboxsym/health-check-bundle
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui