Building complex where statement in Laravel Eloquent using - php

I have an Eloquent\Builder $query that I want to use additional where() calls on, where the amount of the calls is indefinite and is taken from an array $filter, example below:
$filter = [
'or:email:=:ivantalanov#tfwno.gf',
[
'or:api_token:=:abcdefghijklmnopqrstuvwxyz',
'and:login:!=:administrator',
],
];
The strings, when parsed, produce valid SQL conditions, but the problem lies into sticking them into closures where a group is present (like strings 2 and 3 in the example - the array is their 'group').
I know of Laravel's functionality that allows sticking a Closure function into $query->where() to achieve what I want, but the problem I'm facing is actually building those complex closures. I have to iterate through every string in the group and pass it into the closure generated like so (where $item is the result of parsing a condition string):
$closure = function ($query) use ($item)
{
call_user_func_array(
[$query, $item['function']], [$item['field'], $item['operator'], $item['values']]
);
};
Now the obvious problem with this is while it makes simple closures easily, passing more than one condition is plain impossible.
My question is, what could I use to prepare a complex statement to be executed on a query inside a closure?

Okay, I think I figured it out.
This is the method that will return the end result.
public function parse_filter(Builder &$query, array $filter)
{
$groups = $this->_prepare_groups($filter);
return $this->_parse_groups($query, $groups);
}
These methods will parse the initial array into something more usable.
private function _prepare_groups(array $filter)
{
foreach ($filter as $key => $item) {
if (is_array($item)) {
$groups[] = $this->_prepare_groups($item);
}
if (is_string($item)) {
$simple_filter = $this->_parse_simple_filter($item);
$groups[] = $simple_filter;
$simple_filter = null;
}
}
return $groups;
}
private function _parse_simple_filter(string $filter)
{
$filter_data = explode(':', $filter);
$simple_filter['function'] = $filter_data[0] === 'and' ? 'where' : 'orWhere';
$simple_filter['field'] = $filter_data[1];
$simple_filter['operator'] = $filter_data[2];
$simple_filter['values'] = $filter_data[3];
return $simple_filter;
}
And here is where the most of the magic happens. Closures are recursive calls to this method, as you can see.
private function _parse_groups(Builder &$query, array $groups)
{
foreach ($groups as $operator => $group) {
if (!array_key_exists('function', $group)) {
$closure = function ($query) use ($group)
{
$this->_parse_groups($query, $group);
};
$query->where($closure);
} else {
$query->{$group['function']}($group['field'], $group['operator'], $group['values']);
}
}
return $query;
}
Using this, you can modify an Eloquent\Builder object however you like with deeply nested filters that are declared dynamically (for example, received within a GET/POST request).

I guess this will help for you:
In model create a scope:
public static function scopeGetResultList($query) {
return $query->where(function ($query) use ($item) {
$query->where('group_user_holder_type', '=', 1)
->orWhere('group_user_holder_type', '=', 0);
});
}
OR
Example:
public static function getSearchedUserAuto($search_key, $user_id)
{
$users = DB::table((new User)->getTable().' as U')
->select('U.*', 'CT.city_name', 'C.nicename')
->leftJoin((new Country)->getTable().' as C', 'C.country_id', '=', 'U.user_country')
->leftJoin((new City)->getTable().' as CT', 'CT.city_id', '=', 'U.user_city')
->where(function($query) use ($search_key){
$query->where('U.user_full_name', 'like', '%'.$search_key.'%')
->orWhere('U.user_email', 'like', '%'.$search_key.'%');
})
->where(function($query) use ($search_key){
$query->where('U.user_full_name', 'like', '%'.$search_key.'%')
->orWhere('U.user_email', 'like', '%'.$search_key.'%');
})
->where('U.status', '=', 1)
->where('U.user_id', '!=', $user_id)
->get();
return $users;
}
See if it is work for you.

Related

Laravel - Group multiple where clause as orWhere

Is it possible to append multiple where clause and make them orWhere clause?
This is what I mean:
public function call()
{
$pageTypes = ["page_user", "guest_user" ...etc];
$appendQuery = [];
// in here, the query is always where making the sql query "page_type LIKE %page_user% and page_type LIKE %guest_user%"
// my written mysql query here might be written wrong but I hope you get the idea.
// I want to change the subsequent query to orWhere
foreach ($pageTypes as $i => $pageType) {
array_push($appendQuery, function ($query) use ($pageType) {
return $this->addPageTypeQuery($query, $pageType);
});
}
}
public function addPageTypeQuery($query, $pageType)
{
return $query->where('page_type', 'LIKE', $pageType);
}
Though I can manually create a function with query where・orWhere, but if there is a laravel/php way to do this, that would help.
Note I am unable to change the contents of addPageTypeQuery function. I know I can add an input parameter inside then create an ifelse/switch statement to determine what the desired return data but I cannot do so since my PM will not allow it.
Extra note I forgot I can use whereIn but currently there is no whereIn function in the current repository.php file I am working and I cannot add/edit functions because of PM. I might create my own function of whereIn clause or hack the appenedQuery and manually change the where to orWhere (Bad practice) Too complicated. Will create a whereIn instead.
orWhere() just uses where() in the background so you should be able to do it. The signature for the where method is
public function where($column, $operator = null, $value = null, $boolean = 'and')
{
...
}
orWhere is pretty similar
public function orWhere($column, $operator = null, $value = null)
{
...
return $this->where($column, $operator, $value, 'or');
}
With this in mind, I think you only need a way to pass an extra argument to your addPageTypeQuery method.
public function addPageTypeQuery($query, $pageType, $boolean = 'and')
{
return $query->where('page_type', 'LIKE', $pageType, $boolean);
}
foreach ($pageTypes as $i => $pageType) {
array_push($appendQuery, function ($query) use ($pageType) {
if (/* should be an OR query */) {
return $this->addPageTypeQuery($query, $pageType, 'or');
}
return $this->addPageTypeQuery($query, $pageType);
});
}

Laravel Query builder condition with current query using when

How can I create a condition using current query with laravel query builder, is it even possible, i guess it would look something like this
$record = $this->repository
->where('value', $value)
->when($thisQuery->get()->isNotEmpty(), static function(){
//execute
});
You can use sub-queries in anonymous function as in the example below:
$record = $this->repository
->where('value', $value)
->where(function($query) {
/** #var $query Illuminate\Database\Query\Builder */
return $query->where('foo', 'LIKE', '%foooo%')
->orWhere('foo', 'bar');
})
->get();
You can as well run multiple where clauses as in the following:
$record = $this->repository
->where('value', $value)
->where('foo', 'bar')
->get();
And depends on the requirements use the appropriate way.

What will the best solution for this multiple optional filter?

These are all optional fields, so will I have to write multiple queries with conditions or is there any way to handle this using Laravel? What will be the query looks like?
Thanks
It depends a bit on how the filters are submitted, but you can do one of the following two things (and probably a gazillion more...):
public function listCars(Request $request)
{
$cars = Car::when($request->get('make'), function ($query, $make) {
$query->where('make', $make);
})
->when($request->get('model'), function ($query, $model) {
$query->where('model', $model);
})
->...
->get();
// do what you have to do
}
So you are basically wrapping your query builder calls in when($value, $callback), which will only execute $callback if $value evaluates to true. When you retrieve a not set parameter with $request->get('parameter'), it will return null and the callback is not executed. But be careful, if $value is 0 it will also not execute the callback. So be sure you don't have this as an index.
As alternative to this, you can also do the same thing but with a bit less eloquent expressions...
public function listCars(Request $request)
{
$query = Car::query();
if($request->filled('make')) {
$query->where('make', $request->get('make'));
}
if($request->filled('model')) {
$query->where('model', $request->get('model'));
}
// some more filtering, sorting, ... here
$cars = $query->get();
// do what you have to do
}
Here is a working example of something similar query i have in my app.
$filters = $vehicle->newQuery();
if (!empty($request->make)) {
$filters->where('make_id', $request->make);
}
if (!empty($request->carmodel)) {
$filters->where('carmodel_di', $request->carmodel);
}
if (!empty($request->year)) {
$filters->where('year_id', $request->year);
}
if (!empty($request->engine)) {
$filters->where('engine_id', $request->engine);
}
if (!empty($request->price)) {
$filters->where('price_id', $request->price);
}
$cars = $filters->latest()->paginate(50);
and now push the $cars variable to view. I hope this works for you or atleast gives you an idea on how to proceed
here is a simple way, you can also make the joins conditional inside the ->when() condition, if you are in Laravel version > 5.4, use $request>filled() instead of $request->has()
public function listCars(Request $request)
{
$cars = Car::when($request->has('make'), function ($query)use($request) {
$query->join('maker','car.makerId','=','maker.id')
->where('make', $request->input('make'));
})
->when($request->has('model'), function ($query)use($request) {
$query->where('model', $request->input('model'));
})
->...
->get();
// you can even make the join conditionaly,
}
$fiterItem = ['make','model','year','engine','price'];
$filters = $vehicle->newQuery();
foreach ($filter as $item) {
if ($r->filled($item)) {
$list->where($item, $r->query($item));
}
}
$list = $filters->paginate(20);

dynamic query laravel eloquent with whereHas

I want to create dynamic filters.
for example I want to create this code
$Contact = Contact::whereHas('User', function ($query) use ($searchString) {
$query->where('name', 'like', '%Jhone%')->orwhere('family', '<>' . 'Doe');
})->whereHas('Account', function ($query) use ($searchString) {
$query->where('account_name', '=' , 'test_account' )->orwhere('account_city', 'like', '%test_city%');
})->get();
and all of parameters is variable
name,like,%Jhone%,family,<>,Doe,.....
and I want to pass variables to function and function create above query.
I assume that the relationship functions within your Contact, User and Account models are written in camelCase and not PascalCase like your example shows.
public function getContacts(Request $request)
{
return Contact::when($request->get('username'), function ($query, $val) use ($request) {
$query->whereHas('user', function ($q) use ($val, $request) {
$q->where('name', 'like', '%'.$val.'%');
if ($request->has('familyname')) {
$q->orWhere('family', '<>', $request->get('familyname'));
}
});
})
->when($request->get('accountname'), function ($query, $val) use ($request) {
$query->whereHas('account', function ($q) use ($val, $request) {
$q->where('account_name', $val);
if ($request->has('city')) {
$q->orWhere('account_city', 'like', '%'.$request->get('city').'%');
}
});
})
->get();
}
This function will return all contacts when no GET parameters are given on the request. If a parameter for username is present, it will only return contacts where a user with the given name exists for. If furthermore a familyname parameter is present, it will give contacts with a user that has a matching username or a familyname different from the one given. The very same applies to the account, accountname and city.
In particular, there are two things interesting about this example:
The when($value, $callback) function can be used to build very dynamic queries which only execute the $callback when $value is true. If you use $request->get('something') and something is not available as parameter, the function will return null and the callback is not executed. The callback itself has the form function ($query, $value) { ... }, where $value is the variable you passed to when() as first parameter.
Using $request->has('something') inside the query builder functions to dynamically build constraints on the query is an alternative to when(). I only added it for the purpose of demonstration - in general I'd recomment sticking to one style.
If you would extend on the example, you could also build highly dynamic queries where not only the variable content like Doe for the family name is given as parameters, but also the comparator like =, <> or like. But extending further on this topic is too much for this answer and there are already tutorials about this topic available anyway.
Edit: here an example for a dynamic query with more detailed input
Expected input (slightly different than your request because yours cannot work):
$filters = [
'user' => [
['name','like','%Jhone%'],
['family','<>','Doe'],
],
'account' => [
['account_name','=','test_account'],
['account_city','like','%test_city%'],
]
];
And the function:
public function getContacts(Request $request, array $filters)
{
$query = Contact::query();
foreach ($filters as $key => $constraints) {
$query->whereHas($key, function ($q) use ($constraints) {
if (count($constraints) > 0) {
$q->where($constraints[0][0], $constraints[0][1], $constraints[0][2]);
}
for ($i = 1; $i < count($constraints); $i++) {
$q->orWhere($constraints[$i][0], $constraints[$i][1], $constraints[$i][2]);
}
});
}
return $query->get();
}
This will always use OR for multiple constraints and not AND. Using AND and OR mixed would require a lot more sophisticated system.

Laravel array whereIn()

I have a form to filter items:
and I'm looking for something similar to this in Laravel 5.3:
// some variables get from request()->input('...')
$mode = ['A'];
$type = ['a', 'b'];
$group = [0, 1];
// desirable query
$results = Item::whereIn([
['mode_id', $mode],
['type_id', $type],
['group_id', $group]
])->paginate(10);
I can do this
$results = Item::whereIn('mode_id', $mode)
->whereIn('type_id', $type)
->whereIn('group_id', $group)
->paginate(10);
but it's not a dynamic way. For example, if a user select nothing in mode, the query returns an empty array.
We can use conditional clauses:
$results = Item::
when(!empty($mode), function ($query) use ($mode) {
return $query->where('mode_id', $mode);
})
->when(!empty($type), function ($query) use ($type) {
return $query->where('type_id', $type);
})
->when(!empty($group), function ($query) use ($group) {
return $query->where('group_id', $group);
})
->paginate(10);
You can do:
$qb = Item::newQuery();
if (!empty($mode))
{
$qb->whereIn('mode_id', $mode);
}
if (!empty($type))
{
$qb->whereIn('type_id', $type);
}
if (!empty($group))
{
$qb->whereIn('group_id', $group);
}
$results = $qb->paginate(10);
Or build your whereIn associative array w/o empty where's before passing it on.

Categories