43

How does reflection in Laravel actually work?

I tried to debug it to see how Laravel uses reflection in a controller's constructor or methods to resolve their dependencies and sub-dependencies and then and give it back to us.

But I found it hard, and it's very complicated to see and to even understand 50% of. Jumping from class to class, I can't really see it. I tried a few times by debugging it with low results of understanding.

I am very impressed by this and by reflection, and the way Laravel uses it makes my heart burn—it's just beautiful. And I wish to fully understand that—the whole process—in general, and step by step.

Beginning from hitting the route to finally having, let's say, dd($x), where $x is from a method argument and is a TestClass that has another dependency of TestClass2 that should be constructed through: $x = new TestClass(new TestClass2());

I think those are beautiful mechanics and architecture, and understanding this is something I want so badly.

So again, my question is: how does reflection in Laravel actually work?


It's not about dd guys... Let's say without dd. Just as I said earlier - when we have this object instantiated from the class method. It's not about dumping it, it's just about having it from method injection by reflection.

The dd was only an example. It can even be die(var_dump()); and it will work

Cy Rossignol
  • 16,216
  • 4
  • 57
  • 83
Krystian Polska
  • 1,286
  • 3
  • 15
  • 27
  • You're asking a very broad question here, and this isn't the best forum for *How does this work...* style questions. `dd` itself uses reflection to output private information about an object. The dpendency injection layer uses it to recursively examine a target class's dependencies. None of this is particularly Laravel specific - most packages for either dumping a variable or DI will do something similar. – iainn Nov 09 '17 at 11:30
  • @iainn edited my question – Krystian Polska Nov 09 '17 at 18:26
  • Laravel is not about Reflection only. There are many things going on under the hood. Start by reading these articles: http://alanstorm.com/category/laravel/#container – Hamoud Nov 13 '17 at 10:07
  • @Hamoud I know about it. that's why this question is precious too – Krystian Polska Nov 13 '17 at 10:18
  • @CyRossignol maybe something about this "a bit of extra logic needed to separate class dependencies from route parameters" – Krystian Polska Nov 27 '17 at 21:33
  • @Krystian I updated the answer with an example and some more info. Let me know if that helps. – Cy Rossignol Nov 29 '17 at 21:17
  • @CyRossignol yes, thank you, great answer :) – Krystian Polska Nov 30 '17 at 09:29

1 Answers1

89

Laravel uses PHP's reflection API for several components. Of these, the inverson-of-control (IoC) dependency injection container and controller method injection are most visible to developers.

To more clearly illustrate the use of reflection, here's a dramatically simplified version of the routine Laravel's IoC container class uses to build up an object's dependencies through constructor injection:

function build($className) 
{
    $reflector = new ReflectionClass($className);
    $constructor = $reflector->getConstructor();

    foreach ($constructor->getParameters() as $dependency) {
        $instances[] = build($dependency->getClass()->name);
    }

    return $reflector->newInstanceArgs($instances);
}

As we can see, the concept isn't too difficult to understand. The container uses PHP's ReflectionClass to find the names of the classes in an object's constructor, and then loops through each of these names recursively to create instances of each object in the dependency tree. With these instances, build() finally instantiates the original class and passes the dependencies as arguments to the constructor.

Controller method injection uses the same container functionality shown above to resolve instances of dependencies declared as method parameters, but there's a bit of extra logic needed to separate class dependencies from route parameters:

function dispatch(Route $route, Controller $controller, $methodName) 
{
    $routeParameters = $route->parametersWithoutNulls();
    $method = new ReflectionMethod($controller, $methodName);

    foreach ($method->getParameters() as $index => $parameter) {
        $class = $parameter->getClass();

        if ($class !== null) {
            $instance = build($class->name);
            array_splice($routeParameters, $index, 0, [ $instance ]);
        }
    }

    $controller->callAction($methodName, $routeParameters);
}

Again, this adaptation is slimmed-down to highlight the role reflection plays and relies on our build() function shown previously. The ControllerDispatcher class uses the getParameters() method of PHP's ReflectionMethod to determine which parameters a controller method expects, and then loops through these to find parameters that represent dependencies that it can resolve from the container. Then, it splices each dependency it finds back into the array of route parameters that it passes back to the controller method defined for the route. See RouteDependencyResolverTrait for details.

If we ignore the application bootstrapping process, this dependency injection cascade typically starts for a request when Laravel maps a request to a route, and then determines which controller to pass the request to. Laravel first resolves an instance of the controller from the container, which builds out any constructor-injected dependencies. Then, Laravel finds the appropriate controller method and resolves any more dependencies for the arguments as needed.

As shown here, Laravel uses relatively simple techniques to implement these tools using reflection. However, unlike the examples shown in this answer, the framework adds a lot of additional code to make them as robust and flexible as they are today.

Cy Rossignol
  • 16,216
  • 4
  • 57
  • 83