Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
by
FREELessons:2Length:13 minutes

Next lesson playing in 5 seconds

Cancel
  • Overview
  • Transcript

1.2 How It's Made: Laravel Router

In this lesson, we’ll explore the intricacies of Laravel Router. We’ll talk about the fundamentals of HTTP routing, before we go on to analyze some of the decisions and nuances in the design of Laravel Router. We’ll also talk about some of the awesome features that have been recently added to the router.

Code Snippet

This snippet shows how you can use model bindings in the Laravel router. This route will allow users to request users with URLs like /users/1.

Related Links

1.How It Works: Laravel Router
2 lessons, 12:54

1.1
Introduction
00:24

1.2
How It's Made: Laravel Router
12:30


1.2 How It's Made: Laravel Router

[SOUND] Before we dive into the code base directly, let's take a minute to check out what routing entails from HTTP into our PHP application. Let's say we wanna check out a profile of user one. Assuming we're already on a site in our browser, we would click a link which would tell our browser to send a GET request to access /users/1. The web server will interpret this request and kick it off to a PHP front controller like clairabellsindex.php, if it's set up to do so. The index.php script will have the server super global value set up from the web server itself. We care about the request_method and the request_URI key specifically, which will maintain the GET and the users/1 values, respectively, from the web server. At this point, the PHP application has enough information available to determine what it would like to do with that request. It knows that it received a GET request and it knows the URI that it was requested. Now it can select the code path that knows how to handle GET requests for that resource at users/1. This is allowing the PHP application to govern the routing of a request, rather than mirroring a file system as static assets would from Apache or EngineX. We're mapping request URIs and their methods to a custom handler of whatever we choose. This gives us a lot of control and power over the way our application's resources can be located, without having to ensure a one-to-one PHP script to endpoint mapping at a given accessible point in the file system. We can logically provide URLs for our resources and functionality that have strong semantic meaning while still organizing the code in a way that makes the most sense for our apps at that time. This concept is one of the things that led routers to become so popular and widely used in modern web development. That and the fact that raw CGI scripts just aren't that great to work with for bigger projects and having individual PHP scripts means there's a lot of extra repeating. Let's start off from the top and drill our way deeper into the routing core. Our first stop is where our application becomes aware of the request, and as such, aware of the routing information. Inside of public/index.php in the Laravel app skeleton, you'll see this tiny little line of code near the bottom calling capture on the request class. Jumping into the definition of capture, this will lead us to the Illuminate HTTP package request class. In this method, you'll see createFromBase taking a Symfony request that is bootstrapped from the PHP super globals. I'll spare diving in any further because this would mean switching over to the Symfony code base using the HTTP foundation repository. But this static constructor is named perfectly for what it does. It instantiates all of its internal representations of things like query parameters, post arguments, server variables, etc., from the PHP super globals. That means that our Symfony request now has the request method and URI, as we noted we need earlier. And then from there we instantiate our Laravel request based on the Symfony one. So that's wonderful, but before we change directions again, I wanna address this extra little method up here. This enables the HTTP verb to be overridden by passing a special query string parameter or request body parameter with a key of _method. This key spoofs the request verb into the verb you send as a value. So if I were to send a post request to a PUT route but I wanted to act like it's a PUT, so long as I add _method into my query string or parameter and I set the value to PUT, it will register that request as a PUT request. It's a pretty handy helper from Symfony when you wanna submit a PUT, PATCH, or DELETE from your HTML form, since the current HTML spec does not allow for browsers to handle forms in such a way. Okay, let's pop back up the stack to our capture call. As a side note, I'm navigating my code base using C tags, so I can easily jump around the definitions and pop back up the function stack. So you won't really see me clicking on anything or using typical file navigation strategies, but I am jumping between lines and files by pressing Ctrl+] to go to a definition and Ctrl+T to pop back from a definition. Right, we're now back at our capture invocation, and we're gonna look at the line directly above this, calling handle on the HTTP kernel variable. Let's jump into the definition of handle to check that out next. Right here you can see a very clearly named method, sendRequestThroughRouter. And because tailor tends to define methods and classes based on the order of use, we can just scroll down a little bit to get to that method. Now there's something pretty slick in this method that we'll run into a few more times, the pipeline class. While I won't go off and explain how this works under the hood in this screencast, we just need to cover the basics of what's happening. We inject an app container as a constructor argument, and then we send the request object down the pipeline, where it's gonna travel through the middleware, and then it's finally gonna fire dispatchToRoute. I think having this pipeline class is a brilliant way to wrap up a pattern that gets repeated a lot, so I wanted to take a moment and give you that overview so you know what's happening now and when we see it again later. Since dispatchToRouter method is the destination to the pipeline, and the destination must always be a callback in the pipeline, it is expected that this method will return a closure. Jumping to this definition, you can see that that is indeed true, and it's setting the application's request instance to be the one we're handling now. And next it will call dispatch on its router's property. Inside of dispatch, it delegates off to dispatchToRoute and returns as a result as a response. So scrolling down a bit to dispatchToRoute, we see a bulk of the code and a nice comment that sums up a bunch of it for us. It tries to find a matching route and then will also set up the request so that the middleware have direct access to the route instance that we're gonna use for fetching parameters. Let's see exactly how a route is found inside of this findRoute method. Looks like this calls match by sending the request into the routes property, which is an instance of routes collection, so let's check that out next. In this method on the first line, we can see that the request method, or the verb, is in use here to find any routes that are set up to listen on that particular verb. As the comment states, if we find a matching route from the check call below, the work is done. However, if no match is found, it's going to check for alternate verbs. If one is found, it'll handle and return that one. Otherwise, it's going to return a method not found error, which is an HTTP status code 405. Walking through this one step at a time, check iterates over the routes passed in and will return the first one whose matches method returns true. Down in matches in the routes class, it will compile itself to a Symfony compiled route, which is something we can largely just gloss over for the sake of time in this video. Then it'll run against the validators to ensure that the route is a valid match for all of the criteria needed. This includes method, which we've already seen. Scheme, which ensures it's gonna be HTTP or HTTPS. Host, ensuring subdomain and domains match what's expected. And URI, which ensures that our route matches the endpoint it was designed to handle. If it is, we've found ourselves a winner. Otherwise, we continue along until a match is found or we run out of routes. Okay, now we have to pop back up the stack to the match method. And we're first going to investigate what happens if we didn't find a match. We'll first check for alternate verbs, which, as the comment describes, will allow Laravel to set proper header information for both a successful or a failed request. If it finds an additional method for a route, and the current request is an options verb, then we have a special case. This one you'll commonly see used in cross site Ajax requests as a pre-flight check. An options request gets sent first before your actual request, to inform your browser whether a request that method you want is supported in the form of a 200 OK response. If the method you wanna use is part of the list, and the proper access headers are set, then your browser's clear to go ahead with the full request. Otherwise, it's gonna be rejected. And if the alternate method is not an options but perhaps a PUT instead of a POST, then a 405 method not found error will be returned to the browser with the available method sent back in the headers. That way the browser or the user knows which methods are acceptable for that request type. Okay, and now we're gonna go back up so we can follow our other path inside of match, assuming we did indeed find a matching route. We'll then bind the request to the route. And inside of the bind method, we're going to invoke bindParameters, and this will bind all of the wildcards that you defined in your routes to the literal values matching inside the route's parameter array. So if you had users/1, the route may have been defined as Route::get('users/ {user}, as in this example here. Where in the parameters array we have now, we'll see the user key mapped to the value of 1. Next we'll see what else Laravel allows you to do with these values. Let's pop back up to our findRoute method and proceed to the return expression. And here inside of substituteBindings, we can swap out the literal values we just got for other values of our choosing. Let's assume I told the router that I would like to bind the user model to the lowercase user key using the router's route model binding feature. That would look like the second example here, where I declare the route model binding, and then inject it inside of the callable or controller action. At this stage, when the router sees user as a parameter, it will take the value, which is, in our example, 1, and then call user first, passing in 1, or user firstOrFail in order to attempt to find the user. If successful, it will overwrite user to be defined as that located user object. Otherwise the request will fail and raise an exception, or it will invoke a default optional closure that will allow you to do something different if no match is found. You can even bind your own custom loaders like in this last example. I can simply return some goofy array and hope for the best. As this demonstrates, the binding is not limited only to Eloquent, though Eloquent models do have a direct way to invoke bindings because it's such a common use case in code. That pretty much covers that. And since substituteBindings was returned from our findRoute method, we can pop back out to where that was called, and then we fire off an event. This event is fired every time a route is matched successfully. This is helpful if you have some logging method or some other bit of code that you wanna run every time a route is matched up with a URL. You typically don't need to do this in your day-to-day work, but it's good to know where this event's fired from if you ever do need to track something down. We can now focus here and dive into runRouteWithinStack which will, again, return a response. The comment here about an onion is a nice foreshadowing of what's coming, another pipeline. This pipeline is specific to middleware that is mapped to this route rather than to every endpoint. In the closure pass to the then block, we're invoking the run method on the route directly and passing in the current request. So let's go take a look at that method on route. Looking at this code is probably where you're best gonna be able to start to draw the relationship to what you actually write out in the routes file. If the current request was not explicitly defined with the uses key or the uses key is not a string, like controller at action, it will invoke runCallable assuming it is a closure. Otherwise it will invoke runController for a little extra work. And if either of these things happens to throw an exception, it'll be caught and turned into an HTTP exception for the browser to handle. This is the code that takes your match route definitions, start executing it, and begins processing the response. If we scroll down a bit more, we can see how these two run actions are defined. runCallable does some reflection magic to inject the dependencies and then executes the closure, returning the result. runController uses a ControllerDispatcher instance to dispatch the request off with some extra logic needed to invoke a controller. If we dive into this method, and then into its delegated callWithinStack method, we'll find yet another pipeline to utilize even more middleware, the one where you can define them directly on a controller instance. Tracing the then closure's code to the source of the call method, we can see that it resolves the needed dependencies again, and then invokes callAction on a controller, which just calls the method on a controller with the given parameters inside of the controller class. So that was a nice deep dive through the primary code path. Hopefully that helped you understand the flow of information in and out of the router. There's way more features in Laravel router than we covered today, but I think this gives you a good idea of how a router works and how the data flows through it. So thanks for watching, and hopefully we can do another deep dive together soon.

Back to the top