2016年11月25日 星期五

Middleware groups in Laravel 5.2

When you are creating a site of any significant size in Laravel, your routes file will often get pretty large. One of the first things I do in a new site is group my routes by logically distinct sections like "admin", "auth", "public". Usually each of these groups get their own set of middleware—admin, for example, gets auth. Maybe the API group gets a different auth middleware, and it might get an API-specific rate limiter or something else.
Laravel 5.2 has introduced something called middleware groups, which are essentially a shortcut to applying a larger group of middleware, using a single key.
Note: Even if you don't want to use the middleware "shortcuts" aspect of middleware groups, you should read on, because this is a big change to Laravel's global middleware stack.
So remember my admin example above? We can now create an "admin" middleware group. Let's learn how.

Defining middleware groups #

You can define middleware groups in app\Http\Kernel.php. There's a new property named $middlewareGroups that's an array; each key is a name and each value is the corresponding middleware.
Out of the box, it comes with web and api:
protected $middlewareGroups = [
    'web' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
    ],

    'api' => [
        'throttle:60,1',
    ],
];
As you can see, the keys can reference either a class or a route-specific middleware shortcut like throttle or auth. Let's make an admin group:
protected $middlewareGroups = [
    'web' => [...],
    'api' => [...],
    'admin' => [
        'web',
        'auth',
    ]
];
We've defined that the admin is a group that uses web (another group) and auth (a named route middleware). That's it!

Changes from 5.1 #

You might notice that the middleware in web are those that used to be applied to every route in Laravel 5.1 and before. That's a pretty big shift in thinking, so please take note of that: anything that's not given a web middleware will not have cookies or session or CSRF functional.
That also means we have a lot more flexibility, though: it frees us up to have more stateless API layers that aren't giving us the convenience of cookies and sessions. We can get rid of most of the universal middleware—if you take a look, the only universal middleware in 5.2 is the "check for maintenance mode" middleware.
Note as well that any APIs that rely on cookies or sessions (or CSRF) will not work if they're stuck under this api group, so if you have stateful APIs, you'll need to make some tweaks to this default apigroup.

Using middleware groups #

OK, so we know how to define a middleware group. How do we use it?
It'll be clear when you look at the default routes.php in 5.2:
Route::get('/', function () {
    return view('welcome');
});

Route::group(['middleware' => ['web']], function () {
    //
});
As you can see, you use it just like any route middleware like auth: just put the key either as the direct value of middleware, or in an array that's the value of middleware. So, here's our admin middleware group in use:
Route::group(['middleware' => 'admin'], function () {
    Route::get('dashboard', function () {
        return view('dashboard');
    });
});
That's it! Enjoy!
Note: Later in Laravel 5.2, all routes in routes.php are now wrapped with the webmiddleware group by default. I'll try to write that up more later, but take a look at the RouteServiceProvider to see how it's all working.

from : https://mattstauffer.co/blog/middleware-groups-in-laravel-5-2

API rate limiting in Laravel 5.2

More and more of my work in Laravel lately has been creating APIs. I have a manual rate limiter class I've been using, but I've had a sense that there's a cleaner way to do it. Unsurprisingly, when Taylor set out to write a rate limiter middleware for Laravel, he did it cleaner and better than I had.

Brief introduction to rate limiting #

If you're not familiar with it, rate limiting is a tool—most often used in APIs—that limits the rate at which any individual requester can make requests.
That means, for example, if some bot is hitting a particularly expensive API route a thousand times a minute, your application won't crash, because after the nth try, they will instead get a 429: Too Many Attempts. response back from the server.
Usually a well-written application that implements rate limiting will also pass back three headers that might not be on another application: X-RateLimit-LimitX-RateLimit-Remaining, and Retry-After(you'll only get Retry-After if you've hit the limit). X-RateLimit-Limit tells you the max number of requests you're allowed to make within this application's time period, X-RateLimit-Remaining tells you how many requests you have left within this current time period, and Retry-After tells you how many seconds to wait until you try again. (Retry-After could also be a date instead of a number of seconds).
Note: Each API chooses the time span it's rate limiting for. GitHub is per hour, Twitter is per 15-minute segment. This Laravel middleware is per minute.

How to use Laravel's rate-limiting middleware #

So, on to the new feature in Laravel 5.2. There's a new throttle middleware that you can use. Let's take a look at our API group:
Route::group(['prefix' => 'api'], function () {
    Route::get('people', function () {
        return Person::all();
    });
});
Let's apply a throttle to it. The default throttle limits it to 60 attempts per minute, and disables their access for a single minute if they hit the limit.
Route::group(['prefix' => 'api', 'middleware' => 'throttle'], function () {
    Route::get('people', function () {
        return Person::all();
    });
});
If you make a request to this api/people route, you'll now see the following lines in the response headers:
HTTP/1.1 200 OK
... other headers here ...
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
Remember, this response means:
A) This request succeeded (the status is 200)
B) You can try this route 60 times per minute
C) You have 59 requests left for this minute
What response would we get if we went over the rate limit?
HTTP/1.1 429 Too Many Requests
... other headers here ...
Retry-After: 60
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
And the actual content of the response would be a string: "Too Many Attempts."
What if we tried again after 30 seconds?
HTTP/1.1 429 Too Many Requests
... other headers here ...
Retry-After: 30
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
Same response, except the Retry-After timer that's telling us how long to wait has ticked down by 30 seconds.

Customizing the throttle middleware #

Let's do a bit of customization. We want to limit it to 5 attempts per minute.
Route::group(['prefix' => 'api', 'middleware' => 'throttle:5'], function () {
    Route::get('people', function () {
        return Person::all();
    });
});
And if we want to change it so that, if someone hits the limit, they can't try again for another 10 minutes?
Route::group(['prefix' => 'api', 'middleware' => 'throttle:5,10'], function () {
    Route::get('people', function () {
        return Person::all();
    });
});
That's all there is to it!
You can see the code that's supporting this here: ThrottlesRequests.php

from : https://mattstauffer.co/blog/api-rate-limiting-in-laravel-5-2

2016年11月23日 星期三

Implicit route model binding in Laravel 5.2

If you've never used it, Laravel's route model binding has been around for a while, but Laravel 5.2 is about to make it even easier.

The basics of route model binding #

Let's assume that a common pattern for binding a URL route is something like this:
Route::get('shoes/{id}', function ($id) {
    $shoe = Shoe::findOrFail($id);
    // Do stuff
});
This is something I do a lot. Wouldn't it be nice if you could drop the findOrFail line and just teach Laravel's router that this route represents a Shoe? You can. In your route service provider, just teach the router: $router->model('shoe', 'App\Shoe'); That means, "any time I have a route parameter named shoe, it's an ID representing an instance of App\Shoe". This allows us to re-write the above code like this:
Route::get('shoes/{shoe}', function ($shoe) {
    // Do stuff
});

Implicit route model binding #

In Laravel 5.2, it's even easier to use route model binding. Just typehint a parameter in the route Closure (or your controller method) and name the parameter the same thing as the route parameter, and it'll automatically treat it as a route model binding:
Route::get('shoes/{shoe}', function (App\Shoe $shoe) {
    // Do stuff
});
That means you can now get the benefits of route model binding without having to define anything in the Route Service Provider. Easy!
That's it for implicit route model binding! Everything past this point is already around in 5.1.

Little known features of route model binding #

These features are not new with 5.2, and therefore not specific to implicit route model binding, but they seem to be not commonly known, so I thought I would throw them in here.

Custom binding logic for route model binding #

If you want to customize the logic a route model binding uses to look up and return an instance of your model, you can pass a Closure as the second parameter of an explicit bind instead of passing a class name:
$router->bind('shoe', function ($value) {
    return App\Shoe::where('slug', $value)->where('status', 'public')->first();
});

Custom exceptions for route model binding #

You can also customize the exceptions that the route model bindings throw (if they can't find an instance of that model) by passing a Closure as the third parameter:
$router->model('user', 'App\User', function () {
    throw new NotFoundHttpException;
});

Changing an Eloquent model's "route key" #

By default, Laravel assumes an Eloquent model should map to URL segments using its id column. But what if you expect it to always map to a slug, like in my shoe custom binding logic example above?
Eloquent implements the Illuminate\Contracts\Routing\UrlRoutable contract, which means every Eloquent object has a getRouteKeyName() method on it that defines which column should be used to look it up from a URL. By default this is set to id, but you can override that on any Eloquent model:
class Shoe extends Model 
{
    public function getRouteKeyName()
    {
        return 'slug';
    }
}
Now, I can use explicit or implicit route model binding, and it will look up shoes where the slugcolumn is equal to my URL segment. Beautiful.

from : https://mattstauffer.co/blog/implicit-model-binding-in-laravel-5-2

Form array validation in Laravel 5.2

It's time to start writing about the new features in Laravel 5.2! You'll notice that many of these features are quicker and easier to learn and write up, so it may seem that it's a smaller release. But many of the features in 5.2 will have a big impact on the simplicity and convenience of the code we write day-to-day.

A quick introduction to HTML Form arrays #

Form array validation simplifies the process of validating the somewhat abnormal shape of data HTML forms pass in when the array syntax is used. If you're not familiar with it, a common use case is when you allow a user to add multiple instances of the same type on one form.
Let's imagine you have a form where a user is adding a company, and as a part of it they can add as many employees to the company as they want. Each employee has a name and a title.
Here's our HTML; imagine that we have some JavaScript that creates a new "employee" div every time you press the "Add another employee" button so they user can add as many employees they want.
<form>
    <label>Company Name</label>
    <input type="text" name="name">

    <h3>Employees</h3>
    <div class="add-employee">
        <label>Employee Name</label>
        <input type="text" name="employee[1][name]">
        <label>Employee Title</label>
        <input type="text" name="employee[1][title]">
    </div>
    <div class="add-employee">
        <label>Employee Name</label>
        <input type="text" name="employee[2][name]">
        <label>Employee Title</label>
        <input type="text" name="employee[2][title]">
    </div>
    <a href="#" class="js-create-new-add-employee-box">Add another employee</a>

    <input type="submit">
</form>
If you fill out that form and submit it, this is the shape of the $_POST:
array(2) {
  ["name"]=>
  string(10) "Acme, Inc."
  ["employee"]=>
  array(2) {
    [1]=>
    array(2) {
      ["name"]=>
      string(10) "Joe Schmoe"
      ["title"]=>
      string(11) "Head Person"
    }
    [2]=>
    array(2) {
      ["name"]=>
      string(18) "Conchita Albatross"
      ["title"]=>
      string(21) "Executive Head Person"
    }
  }
}
As you can see, we get an employee "object". And it contains an array of the IDs that we passed in, with the key/value pairs of "fieldname" => "user provided field value".
Note: It used to be common to just set every instance of the "employee name" field, for example, to be just employee[][name] without setting the ID manually. Don't do this.It'll make every aspect of working with the code more complex.
But how do we validate this? Prior to 5.2, it's a bunch of manual work. Now, Laravel understands this nesting structure and can validate against it uniquely.

Writing form array validation rules #

So, how do we do it? Let's take a look at a normal validator:
    // CompaniesController.php
    public function store(Request $request)
    {
        $this->validate($request, [
            'name' => 'required|string'
        ]);
        // Save, etc.
    }
And now let's add validation for our company employee fields:
    // CompaniesController.php
    public function store(Request $request)
    {
        $this->validate($request, [
            'name' => 'required|string',
            'employee.*.name' => 'required|string',
            'employee.*.title' => 'string',
        ]);
        // Save, etc.
    }
Now we're validating every employee[*][name] and employee[*][title] uniquely, with pretty much no effort on our part. Beautiful.

Postscript #

You may have noticed that the shape of the validation is employee.*.name, with an asterisk in the middle, which almost indicates that you could put something else there.
What if, instead of an asterisk to indicate "all", you put a specific number there? Turns out it'll only validate the entities with that ID. So if you put employee.1.name in the validation array instead of employee.*.name, only the employee with the ID of 1 will be validated according to those rules.
I don't know why or when you would do it, but you could actually set completely separate validation rules for each ID:
    $this->validate($request, [
        'employee.1.name' => 'required|string',
        'employee.2.name' => 'integer', // Not sure *why* you would do this, but, it's possible
    ]);
That's it. Enjoy!

from : https://mattstauffer.co/blog/form-array-validation-in-laravel-5-2

wibiya widget