Chapter 10. Requests, Responses, and Middleware

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.

Laravel’s Request Lifecycle

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.

lur2 1001
Figure 10-1. Request/response lifecycle

Bootstrapping the Application

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.

Laravel’s kernel

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.

Service Providers

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 GitHubServiceProvider or a MailerServiceProvider.

boot(), register(), and deferring on service providers

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.

Example 10-1. Deferring the registration of a service provider
...
use IlluminateContractsSupportDeferrableProvider;

class GitHubServiceProvider extends ServiceProvider implements DeferrableProvider
{
    public function provides()
    {
        return [
            GitHubClient::class,
        ];
    }

More Uses for Service Providers

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 Request Object

The IlluminateHttpRequest class is a Laravel-specific extension of Symfony’s HttpFoundationRequest 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.

Capturing a Request

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();

Getting a Request Object in Laravel

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.

Example 10-2. Typehinting in a container-resolved method to receive a Request object
...
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');

Getting Basic Information About a 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.

Basic user input

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()

Returns an array of all user-provided input.

input(fieldName)

Returns the value of a single user-provided input field.

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.

Example 10-3. Getting basic user-provided information from the 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';
});

User and request state

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()

Returns the user’s IP address.

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.

Files

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.

Persistence

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()

Wipes all previously flashed user input.

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.

The Response Object

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.

Using and Creating Response Objects in Controllers

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.

Example 10-4. Simplest possible HTTP response
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.

Example 10-5. Simple HTTP response with customized status and headers
Route::get('route', function () {
    return response('Error!', 400)
        ->header('X-Header-Name', 'header-value')
        ->cookie('cookie-name', 'cookie-value');
});

Setting headers

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.

Adding cookies

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.

Example 10-6. Attaching a cookie to a response
    return response($content)
        ->cookie('signup_dismissed', true);

Specialized Response Types

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.

View responses

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.

Example 10-7. Using the view() response type
Route::get('/', function (XmlGetterService $xml) {
    $data = $xml->get();
    return response()
        ->view('xml-structure', $data)
        ->header('Content-Type', 'text/xml');
});

Download responses

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.

Example 10-8. Using 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();
}

File responses

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).

Example 10-9. Using the file() response type
public function invoice($id)
{
    return response()->file("./invoices/{$id}.pdf", ['header' => 'value']);
}

JSON responses

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.

Example 10-10. Using the json() response type
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']);
}

Redirect responses

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.

Example 10-11. Examples of using the redirect() global helper
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.

Example 10-12. Redirect back with input
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.

Example 10-13. Redirect with flashed data
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');
});

Custom response macros

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.

Example 10-14. Creating a custom response macro
...
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.

The Responsable interface

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.

Example 10-15. Creating a simple 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.

Example 10-16. Using Responsable to create a view object
...
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);
    }

Laravel and Middleware

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.

An Introduction to Middleware

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.

Creating Custom Middleware

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.

Example 10-17. Default middleware contents
...
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.

Understanding middleware’s handle() method

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).

Example 10-18. Pseudocode explaining the middleware call process
...
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).

Example 10-19. Sample middleware banning the delete method
...
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;
    }
}

Binding Middleware

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.

Binding global middleware

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.

Example 10-20. Binding global middleware
// app/Http/Kernel.php
protected $middleware = [
    AppHttpMiddlewareTrustProxies::class,
    IlluminateFoundationHttpMiddlewareCheckForMaintenanceMode::class,
    AppHttpMiddlewareBanDeleteMethod::class,
];

Binding route middleware

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.

Example 10-21. Binding route middleware
// 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.

Example 10-22. Applying route middleware in route definitions
// 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
});

Using middleware groups

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.

Middleware Groups in 5.2 and 5.3

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.

Example 10-23. Default route service provider in Laravel 5.3+
// 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.

Passing Parameters to Middleware

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.

Example 10-24. Defining a route middleware that accepts parameters
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');

Trusted Proxies

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 AppHttpMiddlewareTrustProxies middleware and add the IP address for your load balancer or proxy to the $proxies array, as shown in Example 10-25.

Example 10-25. Configuring the TrustProxies middleware
    /**
     * 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.

Testing

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.

TL;DR

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.

1 Or an ogre.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.54.63