Skip to content

Instantly share code, notes, and snippets.

@MrPunyapal
Created February 7, 2025 09:57
Show Gist options
  • Save MrPunyapal/e76ccd381c8ba0de94afe124eddd88ce to your computer and use it in GitHub Desktop.
Save MrPunyapal/e76ccd381c8ba0de94afe124eddd88ce to your computer and use it in GitHub Desktop.

Tappable Scopes in Laravel

The Problem

  • Laravel's model scopes don't work with Scout (search).
  • Reusing scopes across models means copying code OR using traits (but Scout still breaks).
  • Leads to duplicate query logic.

The Solution: Tappable Scopes

Instead of defining scopes inside models, create scope classes that can be used in both Eloquent & Scout queries!

Step 1: Create a Scope Class

FilterScope.php

<?php

declare(strict_types=1);

namespace App\Models\Scopes;

use Illuminate\Database\Eloquent\Builder;
use Laravel\Scout\Builder as ScoutBuilder;

final class FilterScope {
    /**
     * @template TModel of \Illuminate\Database\Eloquent\Model
     *
     * @param Builder<TModel>|ScoutBuilder<TModel> $query
     * @return Builder<TModel>|ScoutBuilder<TModel>
     */
    public function __invoke(Builder|ScoutBuilder $query): Builder|ScoutBuilder
    {
        return $query->where(...); // Apply your query logic here
    }
}

#Step 2: Use It in Model Scopes

Post.php

/**
 * @param Builder<Post> $query 
 */
public function scopeFilter(Builder $query, User $user): void 
{
    $query->tap(new FilterScope()); 
}

Step 3: Use It in Scout Queries

Post::search($search)
    ->tap(new FilterScope())
    ->get();

Why Use Tappable Scopes?

  • Works with both Eloquent & Scout.
  • Reuses query logic across models.
  • No need for duplicate code or workarounds.
  • Keeps models clean & maintainable.

Final Thoughts

Tappable Scopes help keep Laravel applications clean, flexible & DRY.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment