-
Notifications
You must be signed in to change notification settings - Fork 11.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[11.x] Add dynamic "where(Any|All|None)*" clauses to the query builder #52272
[11.x] Add dynamic "where(Any|All|None)*" clauses to the query builder #52272
Conversation
7c97394
to
09cc149
Compare
Make sure to mark the PR as ready if you need a review. |
Should they not be added as proper methods instead of using a regex in |
I agree with you in principle, but I think it can get messy really fast if we do so, since we have 6 methods for every "base-query"; whereAny, whereAll, whereNone, orWhereAny, orWhereAll and orWhereNone. By quickly screening at all the "one-column" queries in For these reasons, I like the simplicity of having this super simple and small implementation that uses the magic whereBetween |
I was also playing around with an idea of updating the original whereAll and whereAny methods to accept a callback as the second parameter, then the method could execute the callback on all the column name strings, looking something like this. $builder->whereAll(['cost_price', 'selling_price'], function(Builder $query, string $column) : void {
$query->whereBetween($column, [0, 100]);
}); But it would be very verbose, which is against the point of the whereAny and whereAll methods. |
I see what you want to do now, I misunderstood what you were doing at first.
This idea sounds very elegant 🚀 Can it be implemented in a way that makes the original signature still valid? |
Sure, add a check for /**
* Add a "where" clause to the query for multiple columns with "and" conditions between them.
*
* @param string[] $columns
* @param mixed $operator
* @param null $callback
* @param mixed $value
* @param string $boolean
* @return $this
*/
public function whereAll($columns, $operator = null, $value = null, $boolean = 'and')
{
if ($operator instanceof Closure) {
$this->whereNested(function ($query) use ($columns, $operator) {
foreach ($columns as $column) {
$operator($query, $column);
}
}, $boolean);
return $this;
}
// Below is unchanged
[$value, $operator] = $this->prepareValueAndOperator(
$value, $operator, func_num_args() === 2
);
$this->whereNested(function ($query) use ($columns, $operator, $value) {
foreach ($columns as $column) {
$query->where($column, $operator, $value, 'and');
}
}, $boolean);
return $this;
} Existing tests, plus this new one would pass with these changes: $builder = $this->getBuilder();
$builder->select('*')->from('users')->whereAll(['price', 'cost'], function($query, $column): void {
$query->whereBetween($column, [0, 100]);
});
$this->assertSame('select * from "users" where ("price" between ? and ? and "cost" between ? and ?)', $builder->toSql());
$this->assertEquals([0, 100, 0, 100], $builder->getBindings()); What I don't like It would be ideal to be able to use something like in the example below, skipping the $column variable. But it would require a new Builder class for these queries, which introduces a lot of complexity and extra code to maintain. So I would say that it would not be worth it. $builder->whereAll(['sales_price', 'cost_price'], fn($columnQuery) => $columnQuery->whereBetween([0, 100]));
$builder->whereAll(['first_name', 'last_name'], fn($columnQuery) => $columnQuery->whereLike('%einar%')); Considering all of this, I think it is better with a little magic and solve it like suggested in the PR. It reads betters and is more intuitive/familiar for the developer as well. User::query()
->whereAllLike(['first_name', 'last_name'], '%John%')
->orWhereAnyDate(['created_at', 'updated_at'], '2024-07-25')
->get(); |
09cc149
to
74d94ce
Compare
@einar-hansen How about adding the @ method phpdoc instead? |
I've been sleeping a bit on this one, and I've decided to explore the path with a NestedBuilder that we could use to achieve signatures like this:
I think this will be the cleanest and easiest solution.
I would love some feedback on this before I start coding 😉 |
74d94ce
to
516bf8d
Compare
I'm creating a new PR this weekend to solve it like suggested in the last comment ;) Thanks @chu121su12 and @johanrosenson |
This PR introduces a new feature to the Laravel Query Builder that allows for dynamic "where(Any|All|None)*" clauses that will call a query on multiple columns. The idea of this came from the comments in PR#52147 and from the comments on yesterdays Whats new in Laravel video.
Key features:
where
clauses, for examplewhereLike
andwhereIn
etc.Implementation details:
dynamicWhereColumns
to handle the dynamic where clauses. The implementation is inspired by thedynamicWhere
method in the__call
magic method.where
andorWhere
clause types (e.g., whereBetween, whereIn, whereLike, whereDate, etc.). on multiple columns.BadMethodCallException
if you try to call a non-existing method, like for examplewhereAllHelloWorld
Example usage:
Lets search for a user:
Translates to
The PR includes a comprehensive test suite covering various scenarios:
Considerations:
white-list
the supported methods, but I like the flexibility that a developer can call all of the query methods that start withwhere
ororWhere
whereNone
andwhereNot
clauses, likewhereNoneNotIn
orWhereNoneNotLike
.Let me know what you think. Marking this as draft until I know how the PR#52260 goes.