ergebnis/classy
ergebnis/classy adds convenient helpers for working with PHP classes and reflection. Generate class names, namespaces, and short names, and inspect class metadata in a clean, test-friendly way—useful for tooling, libraries, and code generation.
Installation:
composer require ergebnis/classy
Add to composer.json if not auto-discovered:
"autoload": {
"psr-4": {
"App\\": "app/",
"Ergebnis\\Classy\\": "vendor/ergebnis/classy/src/"
}
}
Run composer dump-autoload.
First Use Case:
List all classes in a namespace (e.g., App\Models):
use Ergebnis\Classy\Classy;
$classes = Classy::classes('App\Models');
foreach ($classes as $class) {
echo $class->getName() . "\n";
}
Key Classes:
Classy: Main facade for collectors.ClassCollector: Core collector for classes/enums/interfaces/traits.ClassReflection: Reflection wrapper with caching.Namespace Scanning:
// Get all classes in a namespace (recursive)
$collector = new \Ergebnis\Classy\ClassCollector('App\\');
$classes = $collector->getClasses();
// Filter by trait
$traitClasses = $collector->getClassesWithTrait(\Illuminate\Foundation\Testing\Concerns\InteractsWithContainer::class);
Reflection with Caching:
$reflection = Classy::reflect('App\Models\User');
$methods = $reflection->getMethods();
$properties = $reflection->getProperties();
Integration with Laravel:
public function boot()
{
$this->app->singleton(Classy::class, function ($app) {
return new Classy(new ClassCollector('App\\'));
});
}
Classy to auto-register classes (e.g., for event listeners):
$listeners = Classy::classes('App\\Events\\Handlers')
->filter(fn ($class) => $class->isSubclassOf(\Illuminate\Contracts\Queue\ShouldQueue::class))
->map(fn ($class) => [$class->getName(), 'handle']);
Custom Collectors:
Extend ClassCollector to add filters:
class CustomCollector extends ClassCollector
{
public function getClassesWithAnnotation(string $annotation)
{
return $this->getClasses()
->filter(fn ($class) => $class->hasAnnotation($annotation));
}
}
Caching:
ClassReflection caches results by default. Clear cache manually if classes change:
Classy::clearCache();
new ClassCollector('App\\', cache: false);
Performance:
$this->app->singleton('app.classes', function () {
return cache()->remember('app.classes', now()->addHours(1), fn () =>
Classy::classes('App\\')->all()
);
});
Edge Cases:
isProxy() check:
if (!$reflection->isProxy()) {
// Safe to inspect
}
Laravel-Specific:
Classy::reflect(FooFacade::class)->getOriginalClass() to resolve facades.bind() may not appear in scans. Use app()->bound() to verify.Combining Collectors:
$collector = new ClassCollector('App\\');
$models = $collector->getClasses()
->filter(fn ($class) => $class->isSubclassOf(\Illuminate\Database\Eloquent\Model::class));
Annotation Support:
Use with packages like doctrine/annotations for metadata:
$annotatedClasses = $collector->getClasses()
->filter(fn ($class) => $class->hasAnnotation('\\Some\\Annotation'));
Testing:
Mock Classy in tests:
$this->mock(Classy::class)->shouldReceive('classes')
->andReturn(collect([new ClassReflection('Mock\\Class')]));
Extension Points:
ClassReflection to add custom methods (e.g., hasMethodWithAttribute).ClassCollector to support custom file patterns (e.g., *.test.php).How can I help you explore Laravel packages today?