5.3 feature announcement notes from Laracon

(This is part of a series of posts on New Features in Laravel 5.3.)

  1. Introducing Laravel Echo: An In-Depth Walk-Through
  2. The new $loop variable in Laravel 5.3
  3. Customizing additional parameters in FirstOrCreate in Laravel 5.3
  4. The new cache() global helper in Laravel 5.3
  5. New JSON-column where() and update() syntax in Laravel 5.3
  6. Advanced operations with Collection::where in Laravel 5.3
  7. Image dimension validation rules in Laravel 5.3
  8. Customizing pagination templates in Laravel 5.3
  9. 5.3 feature announcement notes from Laracon
  10. Routing changes in Laravel 5.3
  11. Introducing Laravel Scout
  12. Introducing Laravel Passport
  13. Introducing Mailables in Laravel 5.3
  14. Directory structure changes in Laravel 5.3
  15. The new Notification system in Laravel 5.3
  16. Update to queue workers in Laravel 5.3
  17. Using Vue in Laravel 5.3, with the Vue bootstrap and sample component
  18. Defining console commands via closure in Laravel 5.3

THIS POST IS STILL IN ACTIVE PROGRESS

Once the videos are released I'll be able to update this post with more info, since I was on an audio-only stream for the majority of this.


I'll be writing my usual longer, in-depth blog posts about each of the new 5.3 features that are releasing during Taylor's Laracon talk today, but I wanted to find a single place to write down my notes about the new features that Taylor is announcing for the first time today, so I figured, why not put it in a single blog post here?

This is just my notes from the live stream. I'll update this later with more info, and then will write full-length posts; this will just be casual notes.


Laravel Scout #

Intro to Scout #

Search/ElasticSearch driver; packaged separately like Cashier. Works best with Algolia but would love community support for other drivers.

Model is going to have a Searchable trait.

Indexing with Scout #

Indexes the toArray() function on the model and puts it up in the search index.

Add ScoutServiceProvider to config/app and Searchable trait to your model.

The trait hooks into Eloquent events. Listens to those events and updates your indexes in response.

Special overrides/etc. #

Closure that allows you to override indexing:

Post::withoutSyncingToSearch(function () {
    // make a bunch of posts, e.g.
    factory(Post::class, 10)->create();
});

... then later update all of those:

// Could just scope down the query to only those which you haven't indexed yet if you want...
Post::all()->searchable();

Also could do: this on a relationship

$user->posts()->searchable();

It's smart enough to be like "upsert"; it updates any that are already there, and inserts any new ones.

Can also remove from search:

// didn't catch the syntax for this one, sorry! probably something like Post::where('a', 'b')->unsearchable();

Queues/etc. #

These interactions feel slow—makes sense; these are HTTP requests going out!

So: in config/scout.php set queue to true so that these updates are set to be synced async.

// not sure what this does or whether i wrote this syntax down right, feed was cutting out
php artisan scout:import App\Post

Searching with Scout #

You can seearch.. something like:

Post::search('Alice')->get();
Post::search('Alice')->paginate(20);
Post::search('Alice')->where('account_id', 2)->get();

It can't do the full range of SQL where clauses, but it handles the basics.

Mailables #

Want to simplify mail, so creating mail objects:

Mail::to($user)->send(new DeploymentCompleted($server));

DeploymentCompleted is a PHP class; it represents an email.

// mailable class
public function construct($server)
{
    $this->server = $server;
}

public function build()
{
    return $this->view('emails.whatever.viewname');

    // second parameter is an optional array of specific data that you want to be available to view:

    return $this->view('emails.whatever', ['explicit_data_passed' => 'abc']);
}

Any public properties on the mailable object are accessible in the view, so you don't have to explicitly pass any data.

Send to multiple #

Mail::to(User::find(1))
    ->cc(User::find(2))
    ->bcc(User::find(3))
    ->send(new etc.);

Mail::to(Users::all())
    ->send(new etc.);

Queueing mailables #

Mail::to($user)->queue(new etc.);

Attachments #

All the same methods you have within your mail closure like attach.

public function build()
{
    return this->view()->subject()->attach();
}

Misc #

Guesses subject from the class name if you don't set it explicitly. E.g. mailable class "DeploymentCompleted" gets auto subject "Deployment Completed".

Laravel's new Notification features #

Intro to Notification #

Quick notifications. Password resets, quick links, etc.

Limited features. No file attachments, CCs, etc. This is not email.

Password reminder in 5.3 will use this out of the box.

$user->notify(new DeploymentCompleted($server));

Basic notification class #

class DeploymentCompleted
{
    public function construct($server)
    {
        $this->server = $server;
    }

    public frunction via($notifiable)
    {
        // $notifiable might be a user.. but who knows, you might want to notify a server or a slack channel or something
        // you could inspect user preferences here to decide which sort of notification they get
        // return a list of notification "drivers"
        return ['mail'];
    }

    public function message()
    {
        $this->line('You have a new deployment!')
            ->action('View Deployment', 'http://laravel.com')
            ->line('Check it out');
    }
}

Notifiable trait.

Mail driver comes with a slick default template, responsive, etc. but you can also export/publish it into your app and customize it yourself.

Different states:

$this->line()->action()->line()->error();

Differentiating notification states #

Some drivers know how to differntiate states; some don't. For example, the error state in the mail driver will get a big red button instead of a big blue button. The success() state gets a green button.

New settings in config/app.php: name and logo for notifications.

Email driver #

Covered above.

Database driver #

Table that holds these notifications. Polymorphic; columns for notifiable type, id, level, intro, outro, action text, action url, has been read or not. Laravel doesn't know how to check whether it's read or not, you handle that.

Just add 'database' to the via() method and all of a sudden it's getting it; your calling code doesn't know or care which via driver it's gonna use.

Slack driver #

Add slack to the via() method. Some drivers require more info. Context:

routeNotificationForSlack() method on the User (or whatever else is notifiable).

Convention is routeNotificationFor{drivernNameHere}.

For Slack, that method should return Slack webhook URL: e.g. return $this->slack_webhook_url.

SMS driver #

Add nexmo or sms, hard to tell from Taylor's audio. Add your Nexmo api keys

Queueing notifications #

Go to the notification class and add the ShouldQueue trait. Now they're all queued. Boom goes the dynamite.

Laravel Passport #

Full OAuth2 Server implementation in Laravel in like 5 minutes!!!!!!!!!

In Laravel 5.2, we got A) the idea of multiple auth drivers and B) the token-based authentication. Token-based auth works, it's fine, but it's more important as the ground layer for this.

Basic installation of Passport #

Steps to use it:

  1. Install Passport via Composer.
  2. Go to config/app, add Laravel\Passport\PassportServiceProvider to your providers list.
  3. Run new migrations using 5.3's multiple migrations paths. Just run php artisan migrate and it'll include the Passport migrations too.
  4. Go to your user and import trait Laravel\Passport\HasApiTokens
  5. Add routes: go to AuthServiceProvider and use Laravel\Passport\Passport, then in the boot() method run Passport::routes()
  6. (optionally) Add scopes in the boot() method of AuthServiceProvider, after Passport::routes(); e.g. Passport::tokensCan(['conference' => 'Access your conference information'])
  7. config/auth.php, guards.api.driver; change the api guard to use passport driver instead of token

Passport frontend #

Passport exposes a JSON API for your frontend to consume to let you manage it.

Comes with Vue components by default to make it easy for you to manage them, if you want to use them. It's just a reference but you could use if you want.

Three default Vue components out of the box:

<!-- let people make clients -->
<passport-clients></passport-clients>

<!-- list of clients people have authorized to access our account -->
<passport-authorized-clients></passport-authorized-clients>

<!-- make it simple to generate a token right in the UI to play with -->
<passport-personal-access-tokens></passport-personal-access-tokens>

For these examples Taylor made an app at http://passport.dev/ that has Passport installed. This app is providing the OAuth API. Then another at http://example.dev/ that is a client, consuming it.

Look by default:

Screenshot of Laravel Passport default components view

Creating a client:

Screenshot of Laravel Passport create client view

Once you create a client, you get a secret and a client ID. Go to your consuming client (another site, etc.) and put that key and ID in there.

Showed a sample app that CONSUMES this API (not that provides it); lives at http://consumer.dev/:

// routes/web.php

use Illuminate\Http\Request;

Route::get('/', function () {
    $query = http_build_query([
        'client_id' => 1,
        'redirect_uri' => 'http://consumer.dev/callback',
        'response_type' => 'code',
        'scope' => 'conference'
    ]);

    return redirect('http://passport.dev/oauth/authorize?' . $query);
});

Route::get('callback', function (Request $request) {
    $http = new GuzzleHttp\Client;

    $response = $http->post('http://passport.dev/oauth/tken', [
        'form_params' => [
            'grant_type' => 'authorization-code',
            'client_id' => 1, // from admin panel above
            'client_secret' => 'abc', // from admin panel above
            'redirect_uri' => 'http://consumer.dev/callback`,
            'code' => $request->code
        ]
    ]);

    return json_decode((string) $response->getBody(), true)['access_token'];
});

When you visit http://consumer.dev/ it tries to authenticate, sending you over the Passport site; you get this screen:

Screenshot of Laravel Passport auth approval view

When you authorize, takes you back to http://consumer.dev/callback and you have access to your token now.

To prove, Taylor makes a route in his passport app that just returns the authenticated user, puts it in the routes/api.php routes file. Calls it from Postman, pastes the JWT token from above into the Authorization header and calls the page, and it just works. (Authorization: Bearer TOKENHERE)

Easy to revoke applications in the UI:

Screenshot of Laravel Passport revoke token view

Keeps saying: "This is not what your UI needs to look like; it's just a free reference application."

New in Passport that League package doesn't have: Want it to be easy to create a token in the UI to just play around with the API. Since every token is associated with a client (last one we made was associated with http://consumer.dev), make a personal client: php artisan make passport:client --personal. Then you can go to the Personal Access Tokens component and hit "Create New Token". Creates them with your app (passport.dev) as the listed client.

Scope middleware #

Middlewares to limit users' route access based on your scopes. Add them (whatever they are) in the HTTP kernel. scope and scopes.

The scope middleware authenticates you for a single scope; scopes requires all defined scopes.

// add to Http\Kernel $route<iddleware property
// you can name it whatever you want
'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class,
'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class,

If you want to limit the user to only access a route if they have the conference scope:

Route::get('/whatever', function () {
    // do stuff
})->middleware('scope:conference');

Multiples can be comma separated; allows user through if they have any of the provided scopes: ->middleware('scope:conference,otherScope'). If you want it to only let them through if they have all passed scopes, use scopes:->middleware('scopes:conference,otherScope')`.

Super-powered access to the API for frontend views #

If you have a frontend that's consuming the API, you may not want to do the whole OAuth dance. But you might want the OAuth flow to still be available for external API users.

Trick for your frontend--which has your user already authenticated via Laravel and sessions--to access your API and get around the OAuth flow.

Go to HTTP\Kernel and add new middleware to web:

`Laravel\Passport\Http\Middleware\CreateFreshApiToken::class`,

This adds a JWT token as a cookie to anyone who's logged in. Uses "Synchronized token pattern" to embed the CSRF token into the JWT, and require a CSRF header if you sent that cookie, and they have to match. Some kinda magic.

Safe because other apps can't read your cookies so they can't get your CSRF token out of the JWT token. Boom. Can make API requests if logged in without worrying about OAuth tokens.

Miscellaneous #

"My API doesn't have to be an after thought." Set the whole thing up in 15 minutes with demos.

Latest League package so they're JWT tokens.


Comments? I'm @stauffermatt on Twitter


Tags: laravel | laravel 5.3 | laravel scout | laravel passport | mailable

Matt Stauffer headshot

Hi, I'm Matt Stauffer.

I'm partner & technical director at Tighten Co.

You can find me on Twitter at @stauffermatt


Like what you're reading?

I wrote an entire 450+ page book for O'Reilly: Laravel: Up and Running.

You can order the eBook or print book today.