Weave 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

Callable Reflection Laravel Package

technically/callable-reflection

A PHP package for reflecting any callable—closures, invokable objects, function names, and array callables—through a single, consistent API. Inspect parameters, types, defaults, and return types to power dependency injection, routing, and event handling in Laravel or plain PHP.

View on GitHub
Deep Wiki
Context7

Getting Started

Install it like any Composer library — there is no service provider, facade, or config file to publish:

composer require technically/callable-reflection

Verify the require block in composer.json — it targets PHP 8.0+, so it fits Laravel 9+ without ceremony.

Where to look first. The whole surface is the static constructor and the reflection/invocation methods it returns:

  • CallableReflection::from($callable) — normalizes any callable shape into one object.
  • ->getReflection() — returns the underlying ReflectionFunctionAbstract (ReflectionFunction or ReflectionMethod), so every native reflection API you already know is available.
  • ->getParameters() — ReflectionParameter[], shape-independent.
  • ->getName() — a stable string name regardless of shape ('strlen', 'App\Foo::bar', '{closure}').
  • ->invoke(...$args) — calls the underlying callable.

First use case. The one that pays for itself immediately in a Laravel app is introspecting a controller action or single-action class before you run it. Something like:

use Technically\CallableReflection\CallableReflection;

$action = CallableReflection::from([SendInvoice::class, 'handle']);

$action->getName();         // "App\Actions\SendInvoice::handle"
$attributes = $action->getReflection()->getAttributes(Transactional::class);
$parameters = $action->getParameters();

$action->invoke($invoice);  // no container resolution — see Gotchas

If you only need a name, skip this package — Laravel's Illuminate\Support\Reflector::getCallableName() already does that. Use this when you need parameters, attributes, return types, or invocation through the same object across heterogeneous callables.

Implementation Patterns

Attribute-driven dispatch on listeners and actions. Read method-level attributes before invoking. This is the cleanest day-to-day win — you get the attributes off getReflection() and then decide whether to wrap, log, or short-circuit:

$ref = CallableReflection::from([$listener, 'handle']);
if ($ref->getReflection()->getAttributes(Transactional::class)) {
    DB::transaction(fn () => $ref->invoke($event));
}

Signature-based conditional wiring. When a callable comes from config, a route macro, or a user-supplied option, inspect the parameters to decide what to pass. Parameter count and getType()?->getName() are your two tools:

$params = CallableReflection::from($callback)->getParameters();
$deps = array_map(fn ($p) => $p->getType()?->getName(), $params);

Normalizing heterogeneous callables. The core workflow: you accept string|array|Closure|object from somewhere (config, an attribute, a job payload) and funnel it through CallableReflection::from() so downstream code sees a single stable contract. This is the pattern that justifies the dependency — everywhere you'd otherwise write if (is_string(...)) { ... } elseif (is_array(...)) { ... }.

Reflect with this package, invoke with the container. Best integration tip: use getParameters() / getReflection() to describe the call, then delegate to app()->call($callable) for actual execution so Laravel resolves dependencies. Only use ->invoke(...$args) when you're passing everything explicitly.

Testing helpers. In architecture tests (Pest arch, PHPStan custom rules) you can assert shape — return types on controller methods, expected attributes on action classes, arity of closures — without writing per-shape reflection code.

Boundary validation at boot. If you're building a Laravel package that accepts a callable option (rate limiter, queue callback, macro), reflect the signature once at registration and throw a friendly error if the first parameter isn't the event type — instead of a runtime TypeError deep in a queue worker.

Small wrapper service. Because invocation is static (CallableReflection::from()), fakes are awkward in tests. Bind a thin ActionReflector interface in your container that delegates to the library, so tests can swap it out.

Gotchas and Tips

First-class callables lose metadata. $obj->method(...) is a Closure; you cannot reliably recover $obj. If you need the object (or the class/method name), pass [$obj, 'method'] or [$class, 'method'] instead. This is the number-one trap with PHP 8.1 syntax.

Invokable objects reflect __invoke. CallableReflection::from($invokable) reflects the __invoke method, not the class. Use getReflection()->getDeclaringClass() when you need the class identity.

Static string callables and autoloading. 'App\Foo::bar' is only callable once App\Foo is autoloadable. If your input comes from a config file or DB, call class_exists() first or you'll get a "not callable" exception instead of a useful message.

Array callables with a class string. [Foo::class, 'bar'] reflects against the class. If bar is non-static, invoke() will fail at runtime — resolve an instance ([app(Foo::class), 'bar']) and reflect that instead.

No container injection. invoke(...$args) passes exactly what you give it. Laravel devs reach for this and are surprised when dependencies aren't resolved. Prefer app()->call($callable) for execution and reserve invoke() for callables whose arguments you fully control.

Access levels. On PHP 8.1+ reflection no longer needs setAccessible(true) for non-public members; on 8.0 you may need it. Laravel 10 requires 8.1 and Laravel 11 requires 8.2, so this only bites legacy apps — but check your floor before assuming.

Overlap with Illuminate\Support\Reflector. Laravel's Reflector::getCallableName() and getParameterClassName() cover a slice of what this package does. Do not add a dependency for just a name string; add it when you want parameters + attributes + return type + invocation through one object.

Exception types leak. Invalid/non-callable input throws library-specific exceptions. Catch them at your HTTP/queue boundary and translate to a domain error — don't let a Technically\... exception escape to a 500.

Return types lie about self/static/parent. getReturnType()->getName() returns those literal strings. Resolve them yourself via the declaring class. Guard with instanceof ReflectionNamedType before assuming a single name — union/intersection types will break naive code.

Cache reflections. Reflecting on every route/listener is cheap but not free. Memoize by a stable key: the string name for string callables, Class::method for arrays, spl_object_hash() for objects. Avoid keying by closure identity unless you control lifetime.

Debugging is native. getReflection() returns a genuine ReflectionFunctionAbstract, so dd($ref->getReflection()), stack tracing, and IDE support all work as usual. This is a real advantage over hand-rolled callable string parsing.

Pin and wrap. Nine stars, zero dependents — treat it as a small utility, not an ecosystem. Pin a minor version, wrap it behind your own interface, and add tests for the exact callable shapes you rely on ('strlen', [$obj, 'm'], [$cls, 'm'], fn () => null, new Invokable, $obj(...)). If a maintained alternative appears, the swap should be a one-file change on your side.

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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core