diceline-chartmagnifierquestion-marktwitter-whiteTwitter_Logo_Blue

Today I Learned

How to use Laravel's Tappable trait to achieve fluency

Given the following schemas:

Schema::create('skills', function (Blueprint $table) {
    $table->id();
    $table->string("name");
});
//pivot table
Schema::create('category_skill', function (Blueprint $table) {
    $table->id();
    $table->foreignId("skill_id")->constrained();
    $table->foreignId("category_id")->constrained();
});
//the categories table will be omitted for brevity

Suppose you have to write a method which created a skill, attached multiple categories to it and returned the created skill afterwards:

class CreateSkillAction
{
    public function execute(SkillDTO $skillData): Skill
    {
       ...
    }
}

You could easily achieve this with the following code:

public function execute(SkillDTO $skillData): Skill
{
    $skill = Skill::create($skillData->attributes());
    $skill->categories()->attach($skillData>categories);
    return $skill;
}

But if you wanted to make it a bit more fluent - akin to using fluent interfaces - you could do the following:

1. Add the Tappable trait to you model:

class Skill extends Model
{
    use \Illuminate\Support\Traits\Tappable;
    ...
}

2. Rewrite the execute method to use the tap method - now present on the model:

public function execute(SkillDTO $skillData): Skill
{
    return Skill::create($skillData->attributes())
        ->tap(fn($skill)=>$skill->categories()->attach($skillData->categories));
}

How to trigger model events on pivot tables in Laravel

Suppose we have an app where we have a couple of tasks and users and we want to be able to assign users to tasks.

But what if at the same time we want to know who assigned each user to each task? We can easily achieve this by taking advantage of Laravel's model events - for pivot tables.

We would need 3 tables: one for tasks, one for users and one for the assigning users to tasks:

Schema::create('tasks', function (Blueprint $table) {
    $table->id();
    $table->string("title");
    ...
    $table->timestamps();
});

Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->string("username");
    ...
    $table->timestamps();
});

Schema::create('task_user', function (Blueprint $table) {
    $table->id();
    $table->foreignId("assignee_id")->constrained("users");
    $table->foreignId("skill_id")->constrained();
    $table->foreignId("user_id")->constrained();
    $table->timestamps();
});

1. Create the corresponding TaskUser pivot model.

 php artisan make:model TaskUser --pivot

2. Add your event handlers.

class TaskUser extends Pivot
{
    protected static function booted()
    {
        static::creating(function ($pivot_model) {
            // your implementation here
        });
        ...
    }
}

3. Let Laravel know which model it should use for the pivot table.

This is the key - without this step the callback described above will not be executed!

//in the User model
public function tasks()
{
    return $this->belongsToMany(Task::class)
      ->using(TaskUser::class);
}

Now, you can do:

$user->tasks()->attach($tasks);

That's it! Your event callbacks or corresponding observer methods should now be executed.

How to add a script tag inside a Laravel Blade template

Sometimes you may need to add a little bit of javascript inside a Laravel Blade template. The proper way to do that is to use Laravel's @stack directive:

First, you need to add a @stack on the parent page or the layout:

<script src="{{ asset('js/app.js') }}"></script>
@stack('other-scripts')

Then you need to @push the respective script on the page you need it on.

@push('other-scripts')
<script>
  console.log('do something in js')
</script>
@endpush

How use Laravel's Bootable Eloquent Traits

Given the following bootable trait:

trait WithCreator
{
    //no need to define a 'boot' method
    public static function bootWithCreator()
    {
        self::observe(CreatorObserver::class);
    }
}
class CreatorObserver
{
    public function creating($model)
    {
        $model->creator()->associate(auth()->user());
    }
}

We can use it to register the observer without colliding with existing boot methods inside the model.

class Comment extends Model
{
    use HasFactory;
    use WithCreator;
    
    public static function boot()
    {
        ...
    }
}