Blog

Laravel Tip: Bootable Model Traits

TL;DR Use bootNameOfTrait() instead of boot() in a model trait to avoid being overwritten by the base model’s boot() method.

Adding behavior through a boot method

Let’s say you have a HasImage trait for every model that is related to an image:

trait HasImage
{
    public function image()
    {
        return $this->belongsTo(Image::class);
    }
}

…and you need to ensure that the image gets deleted when the model is deleted, so you add a boot() method in the trait.

The static boot() method is automatically run whenever a model is instantiated, so it is often an ideal place to add behavior or event bindings--such as, in this case, automatically deleting an image whenever a deleting event occurs on the model.

public static function boot()
{
    // Delete associated images if they exist.
    static::deleting(function ($model) {
        $model->image->delete();
    });
}

However, this is problematic if the parent model also contains a boot() method, as the trait’s method will be overwritten.

The solution? bootHasImage() instead of boot()

A better way

trait HasImage
{
    public static function bootHasImage()
    {
        // Delete associated images if they exist.
        static::deleting(function($model) {
            $model->image->delete();
        });
    }

    public function image()
    {
        return $this->belongsTo(Image::class);
    }
}

This works because of the following snippet taken from Eloquent’s Model.php.

protected static function boot()
{
    static::bootTraits();
}

/**
 * Boot all of the bootable traits on the model.
 *
 * @return void
 */
protected static function bootTraits()
{
    $class = static::class;

    foreach (class_uses_recursive($class) as $trait) {
        if (method_exists($class, $method = 'boot' . class_basename($trait))) {
            forward_static_call([$class, $method]);
        }
    }
}

We’ve now unlocked the power of hooking into model events in specific traits.

Enjoy!

×

Got an idea? Let's talk.

Leave us a note here, or give us a call at (312) 448-7405.