Laravel is weird

Victor Todoran
5 min readOct 1, 2023

--

Photo by Kasia Derenda on Unsplash

Among the developers I've worked with, Laravel was never popular, the main reasons being ActiveRecord and the fact that it does not feel like a 'serious framework', whatever that means.

As a product of my environment, I must say I'm not a big fan of Laravel either.

Like most people in the industry, who have rather strong opinions about a language, framework or tool, I say that with great conviction and without actually really working with it, recently or ever.

It's not because of ActiveRecord, it's more because of the 'serious framework' thingy, that and the omnipresence of static calls that I've seen in all Laravel related code snippets that make their way across the Internet.

Recently I've learned that those static calls are not really static calls.
Well they are, but not really. That is some magic reserved for another post, but if you are impacient feel free to take a look at this.

The internet loves Laravel. For the first time ever I work with a developer who is very much into Laravel. Big product companies like Personio or About You use/used Laravel.

All this convinced me it's time I have a closer look at Laravel.

Whenever I want to learn a new tool I usually start by reading the documentation, I pair this with a close look at how things presented in the documentation are implemented.

That is how I stumbled over this:

FIG I. Route configuration and grouping by Controller in Laravel

This piece of code looks unnatural to me. We are adding two new Routes, by passing a Closure, that calls methods on the Route class, to
RouteRegistrar::group (the return of the Route::controller method).

How can the RouteRegistrar::group register Routes with just a Closure that does not return anything? (Hint: It's not)

When it's not clear to me how something works, I look at the implementation, as one does.

First things first, Route is just a Facade to the Router class. Here is a crash course in Laravel Facades, they are just a 'static' wrappers over a Service, in this case the Router.

FIG 2. Excerpt from the Route class definition

The Route::controller method returns an instance of the RouteRegistrar class, which in the case of the RouteRegistrar::group method is just an Adapter of the Router, the same Router to which the Route Facade points to.

FIG III. RouteRegistrar::group method

Let us go further into Router::group method.

FIG IV. Router::group method.

Things get strange here, hence the need of a three line comment, just as many lines of code the interior of this foreach has.

The $attributes, which in our case is empty but in other cases is not, need to end up in the Route somehow. This is achieved by temporarily adding them to Router::groupStack property for what ever happens in Router::loadRoutes to use them, then we discard them.

This is weird and the best proof this is weird is the three line comment that is needed to explain why is it necessary to put something somewhere and why we can just throw it away one line after that.

Remember how this started? I wouldn't either so let me remind you:

I wanted to understand how RouterRegistrar::group(now known as Router::group in disguise) adds two Routes by receiving a method that does not return anything.

Onwards to Router::loadRoutes

FIG V. Router::loadRoutes

In our case $routes is indeed a Closure which will be executed.

This Closure to be exact.

FIG VI. Closure that will be executed in Router::loadRoutes

Onwards to Router::get and Router::post. I say Router and not Route because by now we know that Route is just a 'Facade' for the Router.

FIG VII. Router::get

Both Router::get and Router::post are just Adapters over Router::addRoute.

Onwards to Router::addRoute.

FIG VIII. Router::addRoute

Though it might not be entirely obvious, we have found the answer to our question.

The RouteRegistrar does not really register any Routes (at least not directly) which, combined with the fact that contents of the Closure actually register the Route, means the Closure need not return anything.

For extra credit we will go one level deeper into Router::createRoute so we can see where the attributes added to Router::groupStack in Router::group (a while ago) are actually used.

FIG IX. Router::createRoute()
FIG X. Excerpt from Router::createRoute()

Some thoughts on what we just went through:

The names Route and Router make you think of conceptually two different things, yet in Laravel, the 'Facade' for the Router is called Route, this introduces unnecessary confusion from the beginning.

In our example the Router is doing a whole lot and the RouterRegistrar is doing close to nothing. I would expect a Router to route and a RouterRegistrar to actually register Routes.

The way $attributes are handled makes me think of words like temporal coupling and weak method cohesion. My argument/proof on this topic is the number of comments in Router::group and Router::createRoute which almost match the number of lines of code.

Passing Closures which call methods of a class to methods of the same class through a shell of a class also feels like an unnecessary complex artifice.

It's hard for me to write a conclusion for this article.

So far I'm intrigued. I want to know more. I will definitely continue my quest of discovering Laravel.

But at times, such as this, I find Laravel to be weird.

That’s it. Thanks for reading.

Disclaimer: Consider this to be a living document, which means it's subject to undocumented changes and it might even die in the future.

--

--

Victor Todoran

I write, read and think about software and its users for a living