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

Reflection Laravel Package

illuminate/reflection

Illuminate\Reflection wraps PHP's native ReflectionClass with Laravel-specific helpers for inspecting methods, properties, traits, attributes, and doc comments. It underpins the service container, route model binding, and much of the framework's internal introspection.

View on GitHub
Deep Wiki
Context7

Getting Started

illuminate/reflection is the Composer split of the small static helper class Illuminate\Support\Reflector from the Laravel framework. It ships no service provider, no facade, no config file, and nothing to publish — it's a handful of static methods that wrap PHP's native ReflectionParameter / ReflectionMethod APIs with Laravel-flavoured edge-case handling (nullable types, unions, self/parent, backed enums).

Before installing: in a normal laravel/framework app you already have this class — composer require illuminate/reflection is only relevant if you're pulling Laravel components standalone. Check first:

ls vendor/illuminate/reflection/src 2>/dev/null || grep -rn "class Reflector" vendor/illuminate/support

Look here first: the class source itself (Reflector.php, roughly 100 lines) and the call sites in illuminate/container (Container::getDependencies(), Container::resolveDependencies()) — that's the canonical example of how the methods are meant to be composed.

First use case: reproduce what the container does when it injects into a constructor. Write a fixture with a union-typed, a nullable, and a parent-typed parameter, then dump what the helper reports:

use Illuminate\Support\Reflector;

$params = (new ReflectionMethod(MyJob::class, '__construct'))->getParameters();

Reflector::getParameterClassName($params[0]);   // 'App\Contracts\Logger' | null
Reflector::getParameterClassNames($params[0]);  // union members, e.g. ['Foo', 'Bar']
Reflector::isParameterSubclassOf($params[0], App\Contracts\Logger::class);

Doing this once tells you more than any blog post — the public API has drifted across versions (see Gotchas).

Implementation Patterns

1. Custom callable/DI resolvers. The main day-to-day use is any code that must "call this callable, injecting whatever it needs" outside the container's own call(): plugin/action registries, webhook handlers, listener dispatchers, test helpers. The loop is: iterate getParameters(), skip variadics, take getParameterClassName(), else $parameter->isDefaultValueAvailable() ? getDefaultValue() : $container->make(...), else throw with the parameter name. Using Reflector::getParameterClassName() instead of hand-rolling getType() instanceof ReflectionNamedType gets you correct self/parent resolution and builtin/null filtering for free.

2. Method-level injection. Same pattern one level down: ReflectionMethod + getParameterClassName() per parameter. This is how you'd build dynamic controller/middleware invocation, a micro "dispatch by convention" router, or attribute-driven job parameter binding.

3. Type-based filtering for discovery. isParameterSubclassOf() is a cheap guard before wiring a parameter to a service; isParameterBackedEnumWithStringBackingType() is the check you want when generating validation rules or form options from a method signature (that's why it exists — it backs Rule::enum-style code paths).

4. Callable checks. ::isCallable($class, $method) respects magic __call/__callStatic where method_exists() gives a false negative — useful in macro systems and hook registries.

5. Integrate via the container, not the split package. Depend on illuminate/container (or laravel/framework) in composer.json and let Reflector arrive transitively. Add illuminate/reflection explicitly only if you are genuinely framework-free; then pin it to the same major as any sibling Illuminate packages.

6. Cache the result. Reflection is cheap per call but not free in a request loop. For boot-time discovery (routes, commands, plugins) build a Class => [dependencies] map once in a service provider and write it to bootstrap/cache, mirroring what Laravel does with its package-manifest and event-discovery caches.

7. Test the resolution layer directly. Fixture classes covering self, parent, union, nullable-with-default, enum, and variadic parameters give you faster, more targeted coverage than booting the container in every test.

Gotchas and Tips

  • There is no Reflector::make(), ::resolve(), or ::canBeInstantiated(). If a snippet shows those, it's wrong — that behaviour lives on Illuminate\Contracts\Container\Container (make, bound, getParameterOverride). Reflector is a static helper, not a facade and not a container proxy.
  • It is not a replacement for native reflection. No getAttributes(), no docblocks, no getMethods() traversal. Use ReflectionClass/ReflectionMethod directly for that and reserve Reflector for parameter-type interpretation.
  • API drift is real. Methods have been added, deprecated (getTypeHint), and removed/moved across majors. Trust grep -n "public static function" vendor/illuminate/reflection/src/Reflector.php, not tutorials — especially if you're on the split package rather than the framework, where the version you get is whatever Composer resolved.
  • getParameterClassName() returns null for three different reasons — builtin type (int, array), untyped parameter, and non-named type. Log the ReflectionParameter itself when debugging, not just the null, or you'll chase the wrong cause.
  • Unions only work through getParameterClassNames(). getParameterClassName() handles ReflectionNamedType only. And the plural variant does not resolve self/parent in every version — verify before relying on it in a generic resolver.
  • Intersection types (A&B) are unhandled and surface as null. Fall back to native reflection for those.
  • static is not self. getParameterClassName() resolves self and parent but returns the literal 'static' for static return/param types, which is not instantiable via the container. Guard it or you'll get a confusing BindingResolutionException.
  • Silent falses. isParameterSubclassOf() returns false (never throws) for builtin or untyped parameters — fine in normal flow, misleading when you're debugging an injection failure. Assert on the parameter name alongside it.
  • Debugging "Target [X] is not instantiable": the usual culprits are a nullable class parameter with no default (?Foo $foo) and an ambiguous union. Run Reflector::getParameterClassNames() on the constructor parameter — it shows exactly what the container saw — and confirm allowsNull() / isDefaultValueAvailable().
  • Low adoption / packaging risk. The split package has essentially no direct installs; it's an implementation detail of illuminate/container. Don't pin your architecture to it, don't plan on upstream fixes, and if you're outside Laravel, copying the ~100-line Reflector.php into your own support namespace is lower risk than a long-lived Composer dependency.
  • Extension point: there isn't one. The class is static with no container binding, so keep all Reflector:: calls behind one small adapter class of your own — that's your swap point to native reflection if the split package stalls.
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.
alesima/laravel-azure-service-bus
tomshaw/shopcart
likyly/laravel
darthsoup/shoppingcart
tdt/core
labyrinthe/connect-php
encore2/laravel-admin2
cboxdk/laravel-console-kit
shapecode/doctrine-dbal-datetimeutc
gilads-otiannoh254/inertia-protocol
dakiquang/laravel-oauth-refresh-lock
woda/laravel-worktrees
dalefranks/ramasite
faghy/laracashbook
roundly-consulting/google-places-for-laravel
roundly-consulting/kubernetes-api-for-laravel
roundly-consulting/plausible-for-laravel
gvp/gvp-laravel-template
metasyncsite/laravel-metasync-client
roundly-consulting/onboarding-for-laravel