-
Notifications
You must be signed in to change notification settings - Fork 28
[Proposal] Local query scopes as classes #636
Comments
What's the gain? |
Just an alternative way to write query scopes. Keeps the models clean. Also nice when sharing the same scope between models. So far I have just written a scope trait or just duplicated the code. Maybe easier to break the scope logic into smaller pieces. class ActiveScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
$this->verified($builder);
$this->emailed($builder);
$this->notOld($builder);
// ...
}
} |
ya but other than |
The current disadvantage of the current local scope is that in IDE like in sublime, when you f12 or find symbol for the method, it could not really find it because its method is actually |
This issue is years old and the problem you described isn't specific to Sublime, any IDE has this issue. Laravel is simple full of wonderful magic and some if it you can't escape if you want to use certain patterns. I'd also say any project seriously using laravel should use https://github.com/barryvdh/laravel-ide-helper which, AFAIK, support scopes by creating phpdoc on the classes to allow at least autocomplete in modern IDEs. |
I still think this would be a cool feature. However I do see the point of not adding every single cool feature to the core. |
A semi hacky way to use Scope classes for local queries: public static function getNumberOfAdults(): int
{
return self::query()
->tap(function (Builder $query) {
(new AgeScope(18))->apply($query, $query->getModel());
})
->count();
} As you can see there are to ugly parts:
ideally it should looks like: public static function getNumberOfAdults(): int
{
return self::query()
->scoped(new AgeScope(18))
->count();
} or for scopes without arguments: public static function getNumberOfAdults(): int
{
return self::query()
->scoped(AgeScope::class)
->count();
} |
@lptn I think that will not work if your queries have other chained where conditions because afaik the public static function adults()
{
return self::query()
->scope(new AgeScope(18));
}
User::adults()->where('is_active', 1)->get(); // this will work
User::where('is_active', 1)->adults()->get(); // will not work Remember that query scopes design should be chainable anywhere in the method chain. My approach is something like this: class User
{
public function newEloquentBuilder()
{
return new UserQueryBuilder;
}
}
class UserQueryBuilder extends \Illuminate\Database\Eloquent\Builder
{
public function adults()
{
$this->query->where('age', '>=', 18);
return $this;
}
public function active()
{
$this->scope(new ActiveScope); // Reusable object query scopes if you want reusable queries
return $this;
}
}
class ActiveScope
{
public function apply($query) // maybe we can also use __invoke()
{
$query->where('is_active', 1);
}
}
// All query scopes will work the usual way, anywhere in the method chain
User::active()->adults()->where(...)->orderBy(...)->get();
User::where(...)->where(...)->active()->adults()->get(); The find symbol will work now because we have exact methods names, for easier code tracing. |
The only disadvantage of this feature is that users must know what scopes are applicable to the model. It's very unclear unlike to the trait approach; in the trait case users don't have to know but implementers have to. |
// The macro should be called only in model definitions
Builder::macro('scoped', function ($scope, ...$parameters): Builder {
/** @var Builder $query */
$query = $this;
if (is_string($scope)) {
$scope = new $scope(...$parameters);
}
if (!$scope instanceof Scope) {
throw new LogicException('$scope must be an instance of Scope');
}
$scope->apply($query, $query->getModel());
return $query;
}); // Class scope should be re-defined as a method scope
class User extends Model
{
public function scopeActive(Builder $query): Builder
{
return $query->scoped(ActiveScope::class);
}
} |
I've published as a package: |
https://laravel.com/docs/5.4/eloquent#query-scopes
Currently
Proposed
The text was updated successfully, but these errors were encountered: