mpyw/laravel-local-class-scope
Installation:
composer require mpyw/laravel-local-class-scope
No additional configuration is required—the package auto-registers its macro.
First Use Case:
Create a simple scope class (e.g., app/Scopes/ActiveScope.php):
namespace App\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class ActiveScope implements Scope
{
public function apply(Builder $query, Model $model): void
{
$query->where('active', true);
}
}
Use it in a query:
User::scoped(ActiveScope::class)->get();
Scope interface.app/Scopes/ for existing scope classes in your project.where() clauses into reusable scope classes.// Before (Controller)
User::where('status', 'active')->get();
// After (Scope Class)
User::scoped(ActiveScope::class)->get();
Scope class implementing apply().scoped() in queries.// Scope Class
class AgeScope implements Scope
{
public function __construct(public int $minAge) {}
public function apply(Builder $query, Model $model): void
{
$query->where('age', '>=', $this->minAge);
}
}
// Usage
User::scoped([AgeScope::class, 18])->get();
[Class::class, arg1, arg2]) for clarity with multiple arguments.User::scoped(ActiveScope::class)
->scoped([AgeScope::class, 18])
->get();
// Model
class User extends Model
{
public function scopeActive(Builder $query): Builder
{
return $this->scoped(ActiveScope::class);
}
}
// Usage
User::active()->get();
public function test_active_scope()
{
$scope = new ActiveScope();
$query = User::query();
$scope->apply($query, new User());
$query->getDump(); // Debug the generated query
}
Namespace Organization:
Place scope classes in app/Scopes/ and use fully qualified names (e.g., App\Scopes\ActiveScope) to avoid collisions.
Dependency Injection: Inject services into scopes via constructor:
class TenantScope implements Scope
{
public function __construct(private TenantRepository $tenantRepo) {}
public function apply(Builder $query, Model $model): void
{
$tenantId = $this->tenantRepo->getId();
$query->where('tenant_id', $tenantId);
}
}
Global vs. Local Scopes:
Use global scopes for mandatory filters (e.g., soft deletes) and local scopes for optional ones (e.g., active()).
Performance: For heavy queries, benchmark class-based scopes against closures. The macro adds negligible overhead (~1ms).
IDE Support:
Leverage IDE autocompletion for scope classes (e.g., User::scoped(**)).
Class Not Found Errors:
App\Scopes\ActiveScope::class).Constructor Argument Mismatches:
User::scoped([AgeScope::class, minAge: 18])->get();
Scope Overrides:
where('active', ...)).orWhere() for additive logic.Macro Conflicts:
scoped macro.Macroable::hasMacro('scoped').Global Scope Interference:
User::withoutGlobalScopes()->scoped(ActiveScope::class)->get();
Inspect Generated Queries:
Use $query->toSql() or Laravel Debugbar to verify scope behavior.
Log Scope Execution:
Add logging in apply():
public function apply(Builder $query, Model $model): void
{
logger()->debug('Applying ActiveScope', ['query' => $query->toSql()]);
$query->where('active', true);
}
Test Edge Cases:
Builder::macro('scoped', function ($scope, ...$args) {
// Custom logic
return $this->macroScope($scope, $args);
});
Custom Scope Interface:
Extend the Scope interface for project-specific needs:
interface ProjectScope extends Scope
{
public function getName(): string;
}
Scope Registry: Create a service to register and resolve scopes dynamically:
class ScopeRegistry
{
public function resolve(string $scopeClass, array $args): Scope
{
return new $scopeClass(...$args);
}
}
Scope Groups: Combine multiple scopes into a single class for complex queries:
class AdminUserScope implements Scope
{
public function apply(Builder $query, Model $model): void
{
$query->scoped(ActiveScope::class)
->scoped([AgeScope::class, 21]);
}
}
Dynamic Scope Resolution: Use a factory pattern to resolve scopes by name:
User::scoped('active')->get(); // Resolves to ActiveScope
(Requires custom macro implementation.)
Naming Conventions:
Use CamelCase for scope classes (e.g., ActiveScope) and snake_case for local methods (e.g., scope_active).
Document Scopes: Add PHPDoc to scope classes to clarify usage:
/**
* Filters users by active status.
*
* @param Builder $query
* @param Model $model
*/
class ActiveScope implements Scope { ... }
Leverage Traits for Local Scopes: Share local scope methods across models:
trait ScopesActive
{
public function scopeActive(Builder $query): Builder
{
return $this->scoped(ActiveScope::class);
}
}
Use for API Filtering: Map API query params to scopes dynamically:
$scopeClass = match ($request->filter) {
'active' => ActiveScope::class,
'age' => AgeScope::class,
default => null,
};
if ($scopeClass) {
User::scoped($scopeClass, ...$request->filterArgs)->get();
}
**Avoid Over
How can I help you explore Laravel packages today?