Posted in Laravel 7 Jul 2018 @ 09:45 AM~7 min read

Some quick, useful Laravel tips

Since November, I’ve been working with a small web-dev firm that uses Laravel for all its clients’ 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.

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.

use Illuminate\Database\Schema\Blueprint;

// ...

Blueprint::macro('belongsTo', function (string $type, string $table = null) {
  $this->unsignedInteger("{$type}_id")->index()->nullable();
  $this->foreign("{$type}_id")->references('id')->on(
    $table ?? str_plural($type)
  );
});

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

Schema::create('posts', 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');

Looks so much clearer, doesn’t it?

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, 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',
  DetachedService\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;

// ...

$kernelInstance = $this->app[Kernel::class];
$kernelInstance->pushMiddleware(
  DetachedService\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

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

$this->app->router->model('post', Post::class);
$this->app->router->bind('post_slug', function ($value) {
  return Post::whereSlug($value)->firstOrFail();
});

$this->app->router->model('category', Category::class);
$this->app->router->bind('category_slug', function ($value) {
  return Category::whereSlug($value)->firstOrFail();
});

$this->app->router->model('tag', Tag::class);
$this->app->router->bind('tag_slug', 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)
{
  if (!is_subclass_of($class, Model::class)) {
    return false;
  }

  return $this->app->make($class);
}

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.

/**
 * Bind models to observers using an associative model => observer array.
 */
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([
  DetachedService\Models\Post::class =>
    DetachedService\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 (DetachedService\Something\OrTheOther), or importing it and then referencing it (use DetachedService\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 middleground 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 DetachedService\Models;
use DetachedService\Observers;

// Our binding example from above:

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

With this approach, we have two use clauses for the namespaces being imported, and the model/​observer references don’t look too bad. 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.

That’s all, folks!

Hope this helps you in some way – feel free to pop a note in the comments below if you think anything here can be done better. We’re all here to learn, after all.

Loading Comments...