We’ve already talked a bit about the Illuminate Request
object. In Chapter 3, for example, you saw how you can typehint it in constructors to get an instance or use the request()
helper to retrieve it, and in Chapter 7 we looked at how you can use it to get information about the user’s input.
In this chapter, you’ll learn more about what the Request
object is, how it’s generated and what it represents, and what part it plays in your application’s lifecycle. We’ll also talk about the Response
object and Laravel’s implementation of the middleware pattern.
Every request coming into a Laravel application, whether generated by an HTTP request or a command-line interaction, is immediately converted into an Illuminate Request
object, which then crosses many layers and ends up being parsed by the application itself. The application then generates an Illuminate Response
object, which is sent back out across those layers and finally returned to the end user.
This request/response lifecycle is illustrated in Figure 10-1. Let’s take a look at what it takes to make each of these steps happen, from the first line of code to the last.
Every Laravel application has some form of configuration set up at the web server level, in an Apache .htaccess file or an Nginx configuration setting or something similar, that captures every web request regardless of URL and routes it to public/index.php in the Laravel application directory (app).
index.php doesn’t actually have that much code in it. It has three primary functions.
First, it loads Composer’s autoload file, which registers all of the Composer-loaded dependencies.
Next, it kicks off Laravel’s bootstrap, creating the application container (you’ll learn more about the container in Chapter 11) and registering a few core services (including the kernel, which we’ll talk about in just a bit).
Finally, it creates an instance of the kernel, creates a request representing the current user’s web request, and passes the request to the kernel to handle. The kernel responds with an Illuminate Response
object, which index.php returns to the end user. Then, the kernel terminates the page request.
The kernel is the core router of every Laravel application, responsible for taking in a user request, processing it through middleware, handling exceptions and passing it to the page router, and then returning the final response. Actually, there are two kernels, but only one is used for each page request. One of the routers handles web requests (the HTTP kernel) and the other handles console, cron, and Artisan requests (the console kernel). Each has a handle()
method that’s responsible for taking in an Illuminate Request
object and returning an Illuminate Response
object.
The kernel runs all of the bootstraps that need to run before every request, including determining which environment the current request is running in (staging, local, production, etc.) and running all of the service providers. The HTTP kernel additionally defines the list of middleware that will wrap each request, including the core middleware responsible for sessions and CSRF protection.
While there’s a bit of procedural code in these bootstraps, almost all of Laravel’s bootstrap code is separated into something Laravel calls service providers. A service provider is a class that encapsulates logic that various parts of your application need to run in order to bootstrap their core functionality.
For example, there’s an AuthServiceProvider
that bootstraps all of the registrations necessary for Laravel’s authentication system and a RouteServiceProvider
that bootstraps the routing system.
The concept of service providers can be a little hard to understand at first, so think about it this way: many components of your application have bootstrap code that needs to run when the application initializes. Service providers are a tool for grouping that bootstrap code into related classes. If you have any code that needs to run in preparation for your application code to work, it’s a strong candidate for a service provider.
For example, if you ever find that the feature you’re working on requires some classes registered in the container (you’ll learn more about this in Chapter 11), you would create a service provider just for that piece of functionality. You might have a GitHub
ServiceProvider
or a MailerServiceProvider
.
Service providers have two important methods: boot()
and register()
. There’s also a DeferrableProvider
interface (5.8+) or a $defer
property (5.7 and earlier) that you might choose to use. Here’s how they work.
First, all of the service providers’ register()
methods are called. This is where you’ll want to bind classes and aliases to the container. You don’t want to do anything in register()
that relies on the entire application being bootstrapped.
Second, all of the service providers’ boot()
methods are called. You can now do any other bootstrapping here, like binding event listeners or defining routes—anything that may rely on the entire Laravel application having been bootstrapped.
If your service provider is only going to register bindings in the container (i.e., teach the container how to resolve a given class or interface), but not perform any other bootstrapping, you can “defer” its registrations, which means they won’t run unless one of their bindings is explicitly requested from the container. This can speed up your application’s average time to bootstrap.
If you want to defer your service provider’s registrations, in 5.8+, first implement the IlluminateContractsSupportDeferrableProvider
interface; or, in 5.7 and earlier, first give it a protected $defer
property and set it to true
; and then, in all versions, give the service provider a provides()
method that returns a list of bindings the provider provides, as shown in Example 10-1.
...
use
IlluminateContractsSupportDeferrableProvider
;
class
GitHubServiceProvider
extends
ServiceProvider
implements
DeferrableProvider
{
public
function
provides
()
{
return
[
GitHubClient
::
class
,
];
}
Service providers also have a suite of methods and configuration options that can provide advanced functionality to the end user when the provider is published as part of a Composer package. Take a look at the service provider definition in the Laravel source to learn more about how this can work.
Now that we’ve covered the application bootstrap, let’s take a look at the Request
object, the most important output of the bootstrap.
The IlluminateHttpRequest
class is a Laravel-specific extension of Symfony’s HttpFoundation
Request
class.
The Request
object is intended to represent every relevant piece of information you might care to know about a user’s HTTP request.
In native PHP code, you might find yourself looking to $_SERVER
, $_GET
, $_POST
, and other combinations of globals and processing logic to get information about the current user’s request. What files has the user uploaded? What’s their IP address? What fields did they post? All of this is sprinkled around the language—and your code—in a way that’s hard to understand and harder to mock.
Symfony’s Request
object instead collects all of the information necessary to represent a single HTTP request into a single object, and then tacks on convenience methods to make it easy to get useful information from it. The Illuminate Request
object adds even more convenience methods to get information about the request it’s representing.
You’ll very likely never need to do this in a Laravel app, but if you ever need to capture your own Illuminate Request
object directly from PHP’s globals, you can use the capture()
method:
$request
=
IlluminateHttpRequest
::
capture
();
Laravel creates an internal Request
object for each request, and there are a few ways you can get access to it.
First—and again, we’ll cover this more in Chapter 11—you can typehint the class in any constructor or method that’s resolved by the container. That means you can typehint it in a controller method or a service provider, as seen in Example 10-2.
...
use
IlluminateHttpRequest
;
class
PersonController
extends
Controller
{
public
function
index
(
Request
$request
)
{
$allInput
=
$request
->
all
();
}
Alternatively, you can use the request()
global helper, which allows you to call methods on it (e.g., request()->input()
) and also allows you to call it on its own to get an instance of $request
:
$request
=
request
();
$allInput
=
$request
->
all
();
// or
$allInput
=
request
()
->
all
();
Finally, you can use the app()
global method to get an instance of Request
. You can pass either the fully qualified class name or the shortcut request
:
$request
=
app
(
IlluminateHttpRequest
::
class
);
$request
=
app
(
'request'
);
Now that you know how to get an instance of Request
, what can you do with it? The primary purpose of the Request
object is to represent the current HTTP request, so the primary functionality the Request
class offers is to make it easy to get useful information about the current request.
I’ve categorized the methods described here, but note that there’s certainly overlap between the categories, and the categories are a bit arbitrary—for example, query parameters could just as easily be in “User and request state” as they are in “Basic user input.” Hopefully these categories will make it easy for you to learn what’s available, and then you can throw away the categories.
Also, be aware that there are many more methods available on the Request
object; these are just the most commonly used methods.
The basic user input methods make it simple to get information that the users themselves explicitly provide—likely through submitting a form or an Ajax component. When I reference “user-provided input” here, I’m talking about input from query strings (GET
), form submissions (POST
), or JSON. The basic user input methods include the following:
all()
input(fieldName)
only(fieldName|[array,of,field,names])
Returns an array of all user-provided input for the specified field name(s).
except(fieldName|[array,of,field,names])
Returns an array of all user-provided input except for the specified field name(s).
exists(fieldName)
Returns a Boolean indicating whether the field exists in the input. has()
is an alias.
filled(fieldName)
Returns a Boolean indicating whether the field exists in the input and is not empty (that is, has a value).
json()
Returns a ParameterBag
if the page had JSON sent to it.
json(keyName)
Returns the value of the given key from the JSON sent to the page.
Example 10-3 gives a few quick examples of how to use the user-provided information methods from a request.
// form<form
method=
"POST"
action=
"/form"
>
@csrf<input
name=
"name"
>
Name<br>
<input
type=
"submit"
>
</form>
// Route receiving the form
Route
::
post
(
'form'
,
function
(
Request
$request
)
{
echo
'name is '
.
$request
->
input
(
'name'
)
.
'<br>'
;
echo
'all input is '
.
print_r
(
$request
->
all
())
.
'<br>'
;
echo
'user provided email address: '
.
$request
->
has
(
'email'
)
?
'true'
:
'false'
;
});
The user and request state methods include input that wasn’t explicitly provided by the user through a form:
method()
Returns the method (GET
, POST
, PATCH
, etc.) used to access this route.
path()
Returns the path (without the domain) used to access this page; for example, http://www.myapp.com/abc/def would return abc/def
.
url()
Returns the URL (with the domain) used to access this page; for example, http://www.myapp.com/abc would return http://www.myapp.com/abc
.
is()
Returns a Boolean indicating whether or not the current page request fuzzy-matches a provided string (e.g., /a/b/c
would be matched by $request->is('*b*')
, where *
stands for any characters); uses a custom regex parser found in Str::is()
.
ip()
header()
Returns an array of headers (e.g., ['accept-language' => ['en-US,en;q=0.8']]
), or, if passed a header name as a parameter, returns just that header.
server()
Returns an array of the variables traditionally stored in $_SERVER
(e.g., REMOTE_ADDR
), or, if passed a $_SERVER
variable name, returns just that value.
secure()
Returns a Boolean indicating whether this page was loaded using HTTPS.
pjax()
Returns a Boolean indicating whether this page request was loaded using Pjax.
wantsJson()
Returns a Boolean indicating whether this request has any /json
content types in its Accept
headers.
isJson()
Returns a Boolean indicating whether this page request has any /json
content types in its Content-Type
header.
accepts()
Returns a Boolean indicating whether this page request accepts a given content type.
So far, all of the input we’ve covered is either explicit (retrieved by methods like all()
, input()
, etc.) or defined by the browser or referring site (retrieved by methods like pjax()
). File inputs are similar to explicit user input, but they’re handled much differently:
file()
Returns an array of all uploaded files, or, if a key is passed (the file upload field name), returns just the one file.
allFiles()
Returns an array of all uploaded files; useful as opposed to file()
because of clearer naming.
hasFile()
Returns a Boolean indicating whether a file was uploaded at the specified key.
Every file that’s uploaded will be an instance of SymfonyComponentHttpFoundationFileUploadedFile
, which provides a suite of tools for validating, processing, and storing uploaded files.
Take a look at Chapter 14 for more examples of how to handle uploaded files.
The request can also provide functionality for interacting with the session. Most session functionality lives elsewhere, but there are a few methods that are particularly relevant to the current page request:
flash()
Flashes the current request’s user input to the session to be retrieved later, which means it’s saved to the session but disappears after the next request.
flashOnly()
Flashes the current request’s user input for any keys in the provided array.
flashExcept()
Flashes the current request’s user input, except for any keys in the provided array.
old()
Returns an array of all previously flashed user input, or, if passed a key, returns the value for that key if it was previously flashed.
flush()
cookie()
Retrieves all cookies from the request, or, if a key is provided, retrieves just that cookie.
hasCookie()
Returns a Boolean indicating whether the request has a cookie for the given key.
The flash*()
and old()
methods are used for storing user input and retrieving it later, often after the input is validated and rejected.
Similar to the Request
object, there’s an Illuminate Response
object that represents the response your application is sending to the end user, complete with headers, cookies, content, and anything else used for sending the end user’s browser instructions on rendering a page.
Just like Request
, the IlluminateHttpResponse
class extends a Symfony class: SymfonyComponentHttpFoundationResponse
. This is a base class with a series of properties and methods that make it possible to represent and render a response; Illuminate’s Response
class decorates it with a few helpful shortcuts.
Before we talk about how you can customize your Response
objects, let’s step back and see how we most commonly work with Response
objects.
In the end, any Response
object returned from a route definition will be converted into an HTTP response. It may define specific headers or specific content, set cookies, or whatever else, but eventually it will be converted into a response your users’ browsers can parse.
Let’s take a look at the simplest possible response, in Example 10-4.
Route
::
get
(
'route'
,
function
()
{
return
new
IlluminateHttpResponse
(
'Hello!'
);
});
// Same, using global function:
Route
::
get
(
'route'
,
function
()
{
return
response
(
'Hello!'
);
});
We create a response, give it some core data, and then return it. We can also customize the HTTP status, headers, cookies, and more, like in Example 10-5.
Route
::
get
(
'route'
,
function
()
{
return
response
(
'Error!'
,
400
)
->
header
(
'X-Header-Name'
,
'header-value'
)
->
cookie
(
'cookie-name'
,
'cookie-value'
);
});
We define a header on a response by using the header()
fluent method, like in Example 10-5. The first parameter is the header name, and the second is the header value.
We can also set cookies directly on the Response
object if we’d like. We’ll cover Laravel’s cookie handling a bit more in Chapter 14, but take a look at Example 10-6 for a simple use case for attaching cookies to a response.
return
response
(
$content
)
->
cookie
(
'signup_dismissed'
,
true
);
There are also a few special response types for views, downloads, files, and JSON. Each is a predefined macro that makes it easy to reuse particular templates for headers or content structure.
In Chapter 4, I used the global view()
helper to show how to return a template—for example, view('view.name.here')
or something similar. But if you need to customize the headers, HTTP status, or anything else when returning a view, you can use the view()
response type as shown in Example 10-7.
Route
::
get
(
'/'
,
function
(
XmlGetterService
$xml
)
{
$data
=
$xml
->
get
();
return
response
()
->
view
(
'xml-structure'
,
$data
)
->
header
(
'Content-Type'
,
'text/xml'
);
});
Sometimes you want your application to force the user’s browser to download a file, whether you’re creating the file in Laravel or serving it from a database or a protected location. The download()
response type makes this simple.
The required first parameter is the path for the file you want the browser to download. If it’s a generated file, you’ll need to save it somewhere temporarily.
The optional second parameter is the filename for the downloaded file (e.g., export.csv). If you don’t pass a string here, it will be generated automatically. The optional third parameter allows you to pass an array of headers. Example 10-8 illustrates the use of the download()
response type.
public
function
export
()
{
return
response
()
->
download
(
'file.csv'
,
'export.csv'
,
[
'header'
=>
'value'
]);
}
public
function
otherExport
()
{
return
response
()
->
download
(
'file.pdf'
);
}
If you wish to delete the original file from the disk after returning a download response, you can chain the deleteFileAfterSend()
method after the download()
method:
public
function
export
()
{
return
response
()
->
download
(
'file.csv'
,
'export.csv'
)
->
deleteFileAfterSend
();
}
The file response is similar to the download response, except it allows the browser to display the file instead of forcing a download. This is most common with images and PDFs.
The required first parameter is the filename, and the optional second parameter can be an array of headers (see Example 10-9).
public
function
invoice
(
$id
)
{
return
response
()
->
file
(
"./invoices/
{
$id
}
.pdf"
,
[
'header'
=>
'value'
]);
}
JSON responses are so common that, even though they’re not really particularly complex to program, there’s a custom response for them as well.
JSON responses convert the passed data to JSON (with json_encode()
) and set the Content-Type
to application/json
. You can also optionally use the setCallback()
method to create a JSONP response instead of JSON, as seen in Example 10-10.
public
function
contacts
()
{
return
response
()
->
json
(
Contact
::
all
());
}
public
function
jsonpContacts
(
Request
$request
)
{
return
response
()
->
json
(
Contact
::
all
())
->
setCallback
(
$request
->
input
(
'callback'
));
}
public
function
nonEloquentContacts
()
{
return
response
()
->
json
([
'Tom'
,
'Jerry'
]);
}
Redirects aren’t commonly called on the response()
helper, so they’re a bit different from the other custom response types we’ve discussed already, but they’re still just a different sort of response. Redirects, returned from a Laravel route, send the user a redirect (often a 301) to another page or back to the previous page.
You technically can call a redirect from response()
, as in return response()
->redirectTo('/')
. But more commonly, you’ll use the redirect-specific global helpers.
There is a global redirect()
function that can be used to create redirect responses, and a global back()
function that is a shortcut to redirect()->back()
.
Just like most global helpers, the redirect()
global function can either be passed parameters or be used to get an instance of its class that you then chain method calls onto. If you don’t chain, but just pass parameters, redirect()
performs the same as redirect()->to()
; it takes a string and redirects to that string URL. Example 10-11 shows some examples of its use.
return
redirect
(
'account/payment'
);
return
redirect
()
->
to
(
'account/payment'
);
return
redirect
()
->
route
(
'account.payment'
);
return
redirect
()
->
action
(
'AccountController@showPayment'
);
// If redirecting to an external domain
return
redirect
()
->
away
(
'https://tighten.co'
);
// If named route or controller needs parameters
return
redirect
()
->
route
(
'contacts.edit'
,
[
'id'
=>
15
]);
return
redirect
()
->
action
(
'ContactController@edit'
,
[
'id'
=>
15
]);
You can also redirect “back” to the previous page, which is especially useful when handling and validating user input. Example 10-12 shows a common pattern in validation contexts.
public
function
store
()
{
// If validation fails...
return
back
()
->
withInput
();
}
Finally, you can redirect and flash data to the session at the same time. This is common with error and success messages, like in Example 10-13.
Route
::
post
(
'contacts'
,
function
()
{
// Store the contact
return
redirect
(
'dashboard'
)
->
with
(
'message'
,
'Contact created!'
);
});
Route
::
get
(
'dashboard'
,
function
()
{
// Get the flashed data from session--usually handled in Blade template
echo
session
(
'message'
);
});
You can also create your own custom response types using macros. This allows you to define a series of modifications to make to the response and its provided content.
Let’s recreate the json()
custom response type, just to see how it works. As always, you should probably create a custom service provider for these sorts of bindings, but for now we’ll just put it in AppServiceProvider
, as seen in Example 10-14.
...
class
AppServiceProvider
{
public
function
boot
()
{
Response
::
macro
(
'myJson'
,
function
(
$content
)
{
return
response
(
json_encode
(
$content
))
->
withHeaders
([
'Content-Type'
=>
'application/json'
]);
});
}
Then, we can use it just like we would use the predefined json()
macro:
return
response
()
->
myJson
([
'name'
=>
'Sangeetha'
]);
This will return a response with the body of that array encoded for JSON, with the JSON-appropriate Content-Type
header.
If you’d like to customize how you’re sending responses and a macro doesn’t offer enough space or enough organization, or if you want any of your objects to be capable of being returned as a “response” with their own logic of how to be displayed, the Responsable
interface (introduced in Laravel 5.5) is for you.
The Responsable
interface, IlluminateContractsSupportResponsable
, dictates its implementors must have a toResponse()
method. This needs to return an Illuminate Response
object. Example 10-15 illustrates how to create a Responsable
object.
...
use
IlluminateContractsSupportResponsable
;
class
MyJson
implements
Responsable
{
public
function
__construct
(
$content
)
{
$this
->
content
=
$content
;
}
public
function
toResponse
()
{
return
response
(
json_encode
(
$this
->
content
))
->
withHeaders
([
'Content-Type'
=>
'application/json'
]);
}
Then, we can use it just like our custom macro:
return
new
MyJson
([
'name'
=>
'Sangeetha'
]);
This probably looks like a lot of work relative to the response macros we covered earlier. But the Responsable
interface really shines when you’re working with more complicated controller manipulations. One common example is to use it to create view models (or view objects), like in Example 10-16.
...
use
IlluminateContractsSupportResponsable
;
class
GroupDonationDashboard
implements
Responsable
{
public
function
__construct
(
$group
)
{
$this
->
group
=
$group
;
}
public
function
budgetThisYear
()
{
// ...
}
public
function
giftsThisYear
()
{
// ...
}
public
function
toResponse
()
{
return
view
(
'groups.dashboard'
)
->
with
(
'annual_budget'
,
$this
->
budgetThisYear
())
->
with
(
'annual_gifts_received'
,
$this
->
giftsThisYear
());
}
It starts to make a little bit more sense in this context—move your complex view preparation into a dedicated, testable object, and keep your controllers clean. Here’s a controller that uses that Responsable
object:
...
class
GroupController
{
public
function
index
(
Group
$group
)
{
return
new
GroupDonationsDashboard
(
$group
);
}
Take a look back at Figure 10-1, at the start of this chapter.
We’ve covered the requests and responses, but we haven’t actually looked into what middleware is. You may already be familiar with middleware; it’s not unique to Laravel, but rather a widely used architecture pattern.
The idea of middleware is that there is a series of layers wrapping around your application, like a multilayer cake or an onion.1 Just as shown in Figure 10-1, every request passes through every middleware layer on its way into the application, and then the resulting response passes back through the middleware layers on its way out to the end user.
Middleware are most often considered separate from your application logic, and usually are constructed in a way that should theoretically be applicable to any application, not just the one you’re working on at the moment.
A middleware can inspect a request and decorate it, or reject it, based on what it finds. That means middleware is great for something like rate limiting: it can inspect the IP address, check how many times it’s accessed this resource in the last minute, and send back a 429 (Too Many Requests) status if a threshold is passed.
Because middleware also gets access to the response on its way out of the application, it’s great for decorating responses. For example, Laravel uses a middleware to add all of the queued cookies from a given request/response cycle to the response right before it is sent to the end user.
But some of the most powerful uses of middleware come from the fact that it can be nearly the first and the last thing to interact with the request/response cycle. That makes it perfect for something like enabling sessions—PHP needs you to open the session very early and close it very late, and middleware is also great for this.
Let’s imagine we want to have a middleware that rejects every request that uses the DELETE
HTTP method, and also sends a cookie back for every request.
There’s an Artisan command to create custom middleware. Let’s try it out:
php artisan make:middleware BanDeleteMethod
You can now open up the file at app/Http/Middleware/BanDeleteMethod.php. The default contents are shown in Example 10-17.
...
class
BanDeleteMethod
{
public
function
handle
(
$request
,
Closure
$next
)
{
return
$next
(
$request
);
}
}
How this handle()
method represents the processing of both the incoming request and the outgoing response is the most difficult thing to understand about middleware, so let’s walk through it.
First, remember that middleware are layered one on top of another, and then finally on top of the app. The first middleware that’s registered gets first access to a request when it comes in, then that request is passed to every other middleware in turn, then to the app; then the resulting response is passed outward through the middleware, and finally this first middleware gets last access to the response when it goes out.
Let’s imagine we’ve registered BanDeleteMethod
as the first middleware to run. That means the $request
coming into it is the raw request, unadulterated by any other middleware. Now what?
Passing that request to $next()
means handing it off to the rest of the middleware. The $next()
closure just takes that $request
and passes it to the handle()
method of the next middleware in the stack. It then gets passed on down the line until there are no more middleware to hand it to, and it finally ends up at the application.
Next, how does the response come out? This is where it might be hard to follow. The application returns a response, which is passed back up the chain of middleware—because each middleware returns its response. So, within that same handle()
method, the middleware can decorate a $request
and pass it to the $next()
closure, and can then choose to do something with the output it receives before finally returning that output to the end user. Let’s look at some pseudocode to make this clearer (Example 10-18).
...
class
BanDeleteMethod
{
public
function
handle
(
$request
,
Closure
$next
)
{
// At this point, $request is the raw request from the user.
// Let's do something with it, just for fun.
if
(
$request
->
ip
()
===
'192.168.1.1'
)
{
return
response
(
'BANNED IP ADDRESS!'
,
403
);
}
// Now we've decided to accept it. Let's pass it on to the next
// middleware in the stack. We pass it to $next(), and what is
// returned is the response after the $request has been passed
// down the stack of middleware to the application and the
// application's response has been passed back up the stack.
$response
=
$next
(
$request
);
// At this point, we can once again interact with the response
// just before it is returned to the user
$response
->
cookie
(
'visited-our-site'
,
true
);
// Finally, we can release this response to the end user
return
$response
;
}
}
Finally, let’s make the middleware do what we actually promised (Example 10-19).
...
class
BanDeleteMethod
{
public
function
handle
(
$request
,
Closure
$next
)
{
// Test for the DELETE method
if
(
$request
->
method
()
===
'DELETE'
)
{
return
response
(
"Get out of here with that delete method"
,
405
);
}
$response
=
$next
(
$request
);
// Assign cookie
$response
->
cookie
(
'visited-our-site'
,
true
);
// Return response
return
$response
;
}
}
We’re not quite done yet. We need to register this middleware in one of two ways: globally or for specific routes.
Global middleware are applied to every route; route middleware are applied on a route-by-route basis.
Both bindings happen in app/Http/Kernel.php. To add a middleware as global, add its class name to the $middleware
property, as in Example 10-20.
// app/Http/Kernel.php
protected
$middleware
=
[
AppHttpMiddlewareTrustProxies
::
class
,
IlluminateFoundationHttpMiddlewareCheckForMaintenanceMode
::
class
,
AppHttpMiddlewareBanDeleteMethod
::
class
,
];
Middleware intended for specific routes can be added as a route middleware or as part of a middleware group. Let’s start with the former.
Route middleware are added to the $routeMiddleware
array in app/Http/Kernel.php. It’s similar to adding them to $middleware
, except we have to give one a key that will be used when applying this middleware to a particular route, as seen in Example 10-21.
// app/Http/Kernel.php
protected
$routeMiddleware
=
[
'auth'
=>
AppHttpMiddlewareAuthenticate
::
class
,
...
'ban-delete'
=>
AppHttpMiddlewareBanDeleteMethod
::
class
,
];
We can now use this middleware in our route definitions, like in Example 10-22.
// Doesn't make much sense for our current example...
Route
::
get
(
'contacts'
,
'ContactController@index'
)
->
middleware
(
'ban-delete'
);
// Makes more sense for our current example...
Route
::
prefix
(
'api'
)
->
middleware
(
'ban-delete'
)
->
group
(
function
()
{
// All routes related to an API
});
Laravel 5.2 introduced the concept of middleware groups. They’re essentially pre-packaged bundles of middleware that make sense to be together in specific contexts.
The default routes file in earlier releases of 5.2, routes.php, had three distinct sections: the root route (/
) wasn’t under any middleware group, and then there was a web
middleware group and an api
middleware group. It was a bit confusing for new users, and it meant the root route didn’t have access to the session or anything else that’s kicked off in the middleware.
In later releases of 5.2 everything’s simplified: every route in routes.php is in the web
middleware group. In 5.3 and later, you get a routes/web.php file for web routes and a routes/api.php file for API routes. If you want to add routes in other groups, read on.
Out of the box there are two groups: web
and api
. web
has all the middleware that will be useful on almost every Laravel page request, including middleware for cookies, sessions, and CSRF protection. api
has none of those—it has a throttling middleware and a route model binding middleware, and that’s it. These are all defined in app/Http/Kernel.php.
You can apply middleware groups to routes just like you apply route middleware to routes, with the middleware()
fluent method:
Route
::
get
(
'/'
,
'HomeController@index'
)
->
middleware
(
'web'
);
You can also create your own middleware groups and add and remove route middleware to and from preexisting middleware groups. It works just like adding route middleware normally, but you’re instead adding them to keyed groups in the $middlewareGroups
array.
You might be wondering how these middleware groups match up with the two default routes files. Unsurprisingly, the routes/web.php file is wrapped with the web
middleware group, and the routes/api.php file is wrapped with the api
middleware group.
The routes/* files are loaded in the RouteServiceProvider
. Take a look at the map()
method there (Example 10-23) and you’ll find a mapWebRoutes()
method and a mapApiRoutes()
method, each of which loads its respective files already wrapped in the appropriate middleware group.
// AppProvidersRouteServiceProvider
public
function
map
()
{
$this
->
mapApiRoutes
();
$this
->
mapWebRoutes
();
}
protected
function
mapApiRoutes
()
{
Route
::
prefix
(
'api'
)
->
middleware
(
'api'
)
->
namespace
(
$this
->
namespace
)
->
group
(
base_path
(
'routes/api.php'
));
}
protected
function
mapWebRoutes
()
{
Route
::
middleware
(
'web'
)
->
namespace
(
$this
->
namespace
)
->
group
(
base_path
(
'routes/web.php'
));
}
As you can see in Example 10-23, we’re using the router to load a route group under the default namespace (AppHttpControllers
) and with the web
middleware group, and another under the api
middleware group.
It’s not common, but there are times when you need to pass parameters to a route middleware. For example, you might have an authentication middleware that will act differently depending on whether you’re guarding for the member
user type or the owner
user type:
Route
::
get
(
'company'
,
function
()
{
return
view
(
'company.admin'
);
})
->
middleware
(
'auth:owner'
);
To make this work, you’ll need to add one or more parameters to the middleware’s handle()
method and update that method’s logic accordingly, as shown in Example 10-24.
public
function
handle
(
$request
,
$next
,
$role
)
{
if
(
auth
()
->
check
()
&&
auth
()
->
user
()
->
hasRole
(
$role
))
{
return
$next
(
$request
);
}
return
redirect
(
'login'
);
}
Note that you can also add more than one parameter to the handle()
method, and pass multiple parameters to the route definition by separating them with commas:
Route
::
get
(
'company'
,
function
()
{
return
view
(
'company.admin'
);
})
->
middleware
(
'auth:owner,view'
);
If you use any Laravel tools to generate URLs within the app, you’ll notice that Laravel detects whether the current request was via HTTP or HTTPS and will generate any links using the appropriate protocol.
However, this doesn’t always work when you have a proxy (e.g., a load balancer or other web-based proxy) in front of your app. Many proxies send nonstandard headers like X_FORWARDED_PORT
and X_FORWARDED_PROTO
to your app, and expect your app to “trust” those, interpret them, and use them as a part of the process of interpreting the HTTP request. In order to make Laravel correctly treat proxied HTTPS calls like secure calls, and in order for Laravel to process other headers from proxied requests, you need to define how it should do so.
You likely don’t just want to allow any proxy to send traffic to your app; rather, you want to lock your app to only trust certain proxies, and even from those proxies you may only want to trust certain forwarded headers.
Since Laravel 5.6, the package TrustedProxy is included by default with every installation of Laravel—but if you’re using an older version, you can still pull it into your package. TrustedProxy makes it possible for you to whitelist certain sources of traffic and mark them as “trusted,” and also mark which forwarded headers you want to trust from those sources and how to map them to normal headers.
To configure which proxies your app will trust, you can edit the AppHttp
Middleware
TrustProxies
middleware and add the IP address for your load balancer or proxy to the $proxies
array, as shown in Example 10-25.
/**
* The trusted proxies for this application
*
* @var array
*/
protected
$proxies
=
[
'192.168.1.1'
,
'192.168.1.2'
,
];
/**
* The headers that should be used to detect proxies
*
* @var string
*/
protected
$headers
=
Request
::
HEADER_X_FORWARDED_ALL
;
As you can see, the $headers
array defaults to trusting all forwarded headers from the trusted proxies; if you want to customize this list, take a look at the Symfony docs on trusting proxies.
Outside of the context of you as a developer using requests, responses, and middleware in your own testing, Laravel itself actually uses each quite a bit.
When you’re doing application testing with calls like $this->get('/')
, you’re instructing Laravel’s application testing framework to generate request objects that represent the interactions that you’re describing. Then those request objects are passed to your application as these were actual visits. That’s why the application tests are so accurate: your application doesn’t actually “know” that it’s not a real user that’s interacting with it.
In this context, many of the assertions you’re making—say, assertResponseOk()
—are assertions against the response object generated by the application testing framework. The assertResponseOk()
method just looks at the response object and asserts that its isOk()
method returns true
—which is just checking that its status code is 200. In the end, everything in application testing is acting as if this were a real page request.
Find yourself in a context where you need a request to work with in your tests? You can always pull one from the container with $request = request()
. Or you could create your own—the constructor parameters for the Request
class, all optional, are as follows:
$request
=
new
IlluminateHttpRequest
(
$query
,
// GET array
$request
,
// POST array
$attributes
,
// "attributes" array; empty is fine
$cookies
,
// Cookies array
$files
,
// Files array
$server
,
// Servers array
$content
// Raw body data
);
If you’re really interested in an example, check out the method Symfony uses to create a new Request
from the globals PHP provides: SymfonyComponentHttpFoundationRequest@createFromGlobals()
.
Response
objects are even simpler to create manually, if you need to. Here are the (optional) parameters:
$response
=
new
IlluminateHttpResponse
(
$content
,
// response content
$status
,
// HTTP status, default 200
$headers
// array headers array
);
Finally, if you need to disable your middleware during an application test, import the WithoutMiddleware
trait into that test. You can also use the $this
->
withoutMiddleware()
method to disable middleware just for a single test method.
Every request coming into a Laravel application is converted into an Illuminate Request
object, which then passes through all the middleware and is processed by the application. The application generates a Response
object, which is then passed back through all of the middleware (in reverse order) and returned to the end user.
Request
and Response
objects are responsible for encapsulating and representing every relevant piece of information about the incoming user request and the outgoing server response.
Service providers collect together related behavior for binding and registering classes for use by the application.
Middleware wrap the application and can reject or decorate any request and response.