Apply scopes conditionally when fields with `@scope` are queried
Nobody has claimed this yet.
- Dominant language
- PHP
- Stars
- 3.5k
- Forks
- 468
- Avg merge
- 3h 9m
- Merged PRs (30d)
- 2
Description
What problem does this feature proposal attempt to solve?
There is currently not a solution for invoking a query scope when a particular field is queried. It might not seem intuitive as to WHY someone would do this, but query scopes can be used to overcome the N+1 in a tricky way when the desired value is abstracted or inferred through database relationships.
Consider the example here using "addSelect" inside a query scope to fake out Eloquent.
Here is the implementation of this strategy which I am seeking to use, which looks up when the user is next required to complete a specific deliverable task:
public function scopeWithProjectRecordDue(Builder $query): void
{
$query->addSelect([
'project_record_due' => Project::select('record_due_at')
->whereNotNull('record_due_at')
->hasByNonDependentSubquery('team', fn (Builder $q) => $q
->leftJoin('role_user', 'teams.id', 'role_user.team_id')
->whereColumn('role_user.user_id', 'users.id')
->where('role_user.role_id', 9))
->orderBy('record_due_at')
->orderBy('created_at')
->limit(1)
]);
}
public function getProjectRecordDueAttribute(): ?Carbon
{
if (!array_key_exists('project_record_due', $this->attributes)) {
return $this->attributes['project_record_due'] = self::select([])
->where('id', $this->id)
->withProjectRecordDue()
->applyScopes()
->getQuery()
->first()
->project_record_due
?? null;
}
return $this->attributes['project_record_due'];
}
Note hasByNonDependentSubquery is from another Laravel package which performance optimizes some of Laravel's relationship queries, it can be considered as equivalent to whereHas.
So this makes the query scope available, and in the case where it was not invoked eagerly, the custom accessor notices and runs it before returning the result. When the scope is applied to the original bulk query, this causes the retrieval of this value to be done within the initial batch query, and it much more efficient than running this same query separately for each Eloquent model. This is definitely not the design intent of query scopes but this is the best available solution to solve this problem within Laravel's offerings.
Right now there is not a way to invoke this query scope conditionally only when the related field is requested in the query. The best available solution to emulate this is to include an input field, something like a boolean "includeLastLoginScope" which is bound to the @scope directive.
Which possible solutions should be considered?
Enable the existing @scope directive to be usable on a query field.
Another solution is to cache the value of this on the user model using a queued job which re-computes the value when the relevant data set is updated, I'm just trying to avoid this.
I'm sure the response will be "a PR is welcome" but first I'm just surveying for the appetite to consider this because it is a very off-spec use case.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by tracing Lighthouse's existing @scope directive and the query-field resolution path; the issue names no files or tests. Review how directive arguments are applied, then define tests covering conditional scope application when a field is queried; done means the existing directive works on query fields without requiring an extra input flag.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- graphql, laravel, php
- Domain
- api, backend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 32/100