Mike Rockétt

Some quick and useful Laravel tips

Since November 2017, I’ve been working with a small web-dev firm that uses Laravel for all its client API backends. Over time, we’ve been taking new approaches to solving problems, and simplifying our code to make life easier in the long run. Here are some quick and useful tips we’ve been following as part of this process.

#Laravel — 20 December 2020

This post has been updated from the original post on my old blog. I’ve thrown in some static references to methods that don’t require awareness of context, and cleaned one or two class references for brevity.

Blueprint Macros

Not many people know this, but quite a bit of the underlying functionality in Laravel is macroable. This means that you can extend core functionality with your own, resulting in less lines of code that make more sense. Case in point: foreign key constraints. In migrations, we often repeat the same two lines of code to relate one entity to another and set constraints on that relation. In our case, it’s always almost identical – we don’t cascade on delete or anything like that, nor do we change the approach from table to table (unless we absolutely need to). The macro below, registerable in your service provider, helps shorten the code you use in your migrations, making it easier to read.

Update, December 2020: Laravel 7 and 8 have got new methods to help shorten the path to creating these definitions. They have documented foreignId here, but they haven’t documentd foreignIdFor, which takes a model class name.

use Illuminate\Database\Schema\Blueprint;
 
Blueprint::macro('belongsTo', function (string $type, string $table = null) {
$definition = $this->unsignedInteger("{$type}_id")->index();
$this->foreign("{$type}_id")->references('id')->on($table ?? str_plural($type));
 
return $definition;
});

In a migration, you simply call the macro by name:

Schema::create('posts', static function (Blueprint $table) {
$table->increments('id');
$table->timestamps();
$table->belongsTo('user');
});

If your table names won’t match up to that, you can specify the name of the table in the second argument:

$table->belongsTo('user', 'blog_users');

Polymorphic class-mapping

You’d be surprised how this one isn’t used very much, but it is super-useful and should always be carried out at the start of a project that uses polymorphic relationships.

When such a relationship is created, Laravel uses the absolute name of the related model class in the database. For example, if we have a polymorphic addresses table with the columns addressable_type and addressable_id, the type will use App\User as the type when attaching addresses to your users.

What happens if you decide to move your user model, or if you decide to break up your user into multiple types for the purposes of using multiple guards? You guessed it, things will break.

Relation::morphMap([
'user' => App\User::class,
]);

That little snippet, which can be dropped in the boot method of your service provider, simply tells Laravel to use user in the database when saving the polymorphic relationship. If you ever change up the model’s location, simply change its reference in the service provider, and you’re good to go.

Update: This one’s also covered nicely over at Joseph Silber’s blog post.

Service provider middleware

When using a service provider as a detached service (we’ll call this “Service”), that is you’re decoupling your app’s code from the default namespace for easy transportability to another Laravel instance, you’ll likely want to register your global and router middleware in the service provider itself. Not many people know this, but it can actually be done, and Laravel doesn’t hold you to ransom with the HTTP kernel.

In your service provider, you simply need to register the middleware (in the boot method of your provider) atop whatever the kernel registered.

Router Middleware

This one’s simple enough – the router is available in the IoC container, so we can access it directly:

$this->app->router->aliasMiddleware(
'guardian',
Service\Middleware\Guardian::class
);

Global Middleware

This one’s a little tricker, but you only have to do it once. Simply grab the kernel instance from the app container (it isn’t aliased, so far as I know, so we need to reference it by its contract):

use Illuminate\Contracts\Http\Kernel;
 
$this->app[Kernel::class]->pushMiddleware(
Service\Middleware\AddRobotsHeaders::class
);

These two examples are just shortened for brevity. You’d likely want to declare your middleware in a protected array property of the provider’s class (or, even better, in its config file) and iterate over it.

DRY up your model bindings

Update: Laravel 7 now includes this out of the box for implicit binding, just without the boilerplate. 🎉

All too often, we see this:

use DetachedService\Models\Post;
use DetachedService\Models\Category;
use DetachedService\Models\Tag;
 
$this->app->router->model('user', App\User::class);
 
// Posts
$this->app->router->model('post', Post::class);
$this->app->router->bind('post_slug', static function ($value) {
return Post::whereSlug($value)->firstOrFail();
});
 
// Categories
$this->app->router->model('category', Category::class);
$this->app->router->bind('category_slug', static function ($value) {
return Category::whereSlug($value)->firstOrFail();
});
 
// Tags
$this->app->router->model('tag', Tag::class);
$this->app->router->bind('tag_slug', static function ($value) {
return Tag::whereSlug($value)->firstOrFail();
});

It’s a silly example, but what a mess. At first sight, we’d want to DRY that up to something like this:

$this->bindModels([
'post' => Post::class,
'post_slug' => [Post::class, 'slug'],
'category' => Category::class,
'category_slug' => [Category::class, 'slug'],
'tag' => Tag::class,
'tag_slug' => [Tag::class, 'slug'],
]);

Much neater, right? Hold your horses though – bindModels doesn’t exist, so we’ll need to create it:

use Illuminate\Database\Eloquent\Model;
 
/**
* Bind models defined in arrays. If the model to be bound is a
* Closure, the bind() method will be used. Otherwise, the
* model() method will be used. Both tyoes are merged
* from the two available arguments to the method.
*/
protected function bindModels(array $bindings): void
{
collect($bindings)->each(function ($model, string $routeKey) {
if (is_array($model) && count($model) === 1) {
[$class, $field] = array_collapse(array_divide($model));
$model = $this->resolveModelBy($class, $field);
}
 
$method = $model instanceof Closure ? 'bind' : 'model';
 
$this->app->router->{$method}($routeKey, $model);
});
}
 
/**
* Given a model class and a field, return a Closure for the purposes
* of binding the model to a route.
*/
protected function resolveModelBy($class, string $field): Closure
{
$function = __FUNCTION__;
 
return function ($value) use ($class, $field, $function) {
// Require that the model be a sub class of an Eloquent model.
if (!$model = $this->getModelFor($class)) {
throw new InvalidModelException(
"Argument 1 passed to $function() must be an instance" .
"of a class that extends Illuminate\Database\Eloquent\Model"
);
}
 
return $model::where($field, $value)->firstOrFail();
};
}
 
/**
* Given a model class string, check if it extends an Eloquent model
* and, if so, return an instance of it so it can be queried.
*/
protected function getModelFor($class)
{
return (is_subclass_of($class, Model::class)) ? app($class) : false
}

Okay so what are we doing here? First off, we collect the bindings passed to the bindModels method and iterate through them. We then determine if the route key needs a normal ID binding or a closure binding, and then bind appropriately with a variable-variable that gets called on the router instance. When it comes to binding a closure, we first need to resolve the class we passed in to check that it is really a model – we do this via getModelFor. If resolved, we can query it by the model key we passed in for the binding.

Of course, the closure binding is only useful in situations where you query by column name. If you need to do some more advanced stuff, then you can obviously stick to $this->app->router->bind.

Bind model observers with an associative array

This one’s really just for clean syntax, and makes use of the getModelFor method above.

protected function bindObservers(array $bindings): void
{
collect($bindings)->each(function (string $observer, string $model) {
$model = $this->getModelFor($model);
$model->observe($observer);
});
}

Quite trivial indeed, but like I said, this is just for cleaning up our syntax:

$this->bindObservers([
Service\Models\Post::class => Service\Observers\PostObserver::class,
]);

Partial namespaces

This is more of a PHP thing than a Laravel thing, but useful nonetheless.

You’ll note throughout these examples that I’m either referring to a class absolutely (Service\Something\OrTheOther), or importing it and then referencing it (use Service\Something\OrTheOther).

Sometimes, you might feel that either of these options can become a little verbose, or a little messy at the least.

I like to find a middle-ground between them, especially in the case of models, where we import the common namespace (Something, in this case) and then use that to reference the classes that live in it.

use Service\{Models, Observers};
 
// Our binding example from before:
 
$this->bindObservers([
Models\Post::class => Observers\PostObserver::class,
Models\Category::class => Observers\CategoryObserver::class,
]);

Update, December 2020: I’ve gone ahead an used {Curly}-style imports here. They group things logically and reduce your line-count. Some time back, I’d stopped using this pattern, mostly because phpfmt expanded each one to its own line. But, phpfmt is archived, and I’ve since switched over to Intelephense formatting, which doesn’t do this.

With this approach, we have two imports and the model/observer references are tidied up. Of course, this is a trivial example, but the objective is to keep code as clean as possible, whilst retaining a level of expression that tells us what’s going on.