Laravel - Group multiple where clause as orWhere - php

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);
});
}

Related

Laravel eloquent query using local scope with conditional statement and arguments

I've written the following function in my Car model that does the following:
Gets the related reservations based on two dates(pickup/dropoff)
Checks if the amount of these reservations are equal or exceed the quantity of the car
Finally returns a boolean depending on the output
/**
* Custom Functions
*/
public function isAvailableFor($from, $to) {
$reservationsCount = $this->reservations->where('pickup_date', '>=', $from)->where('dropoff_date', '<=', $to)->count();
if($reservationsCount >= $this->quantity) {
return false;
}
return true;
}
The function is working as expected but I want to implement this in a more elegant way using local scopes so I can actually use it efficiently when querying the Car model in my controllers but I can't find the correct way to do it and my code becomes a complete mess.
For example I have the following scope that I am using by just typing Car::active()->get();
/**
* Scopes
*/
public function scopeActive($query)
{
return $query->where('status', 'active');
}
The main problem is the count() function that doesn't let me implement my function in a scope-way or at least I am not that experienced to come up with a solution.
Thanks in advance.
Update
As correctly pointed by OsDev since my function returns a boolean it can not be implemented directly in the scope function. I can alternatively do this in my scope function but I guess it is pretty much an overkill:
public function scopeAvailable($query, $from, $to) {
$excludedId = array();
$cars = Car::whereHas('reservations')->get();
foreach($cars as $car) {
if(!$car->isAvailableFor($from, $to)) {
array_push($excludedId, $car->id);
}
}
return $query->whereNotIn('id', $excludedId);
}
You have to return the $query instead the count result because that way you don't break the Query Builder chain
You can't combine scopes and Model functions because scopes are supposed to return the $query builder object and in that example, your function is returning a boolean.
You can do something like this
/**
* Scopes
*/
public function scopeIsAvailableFor($query,$from,$to)
{
return $query->where('pickup_date', '>=', $from)->where('dropoff_date', '<=', $to);
}
Then you can chain it and call count if you want
$count = Car::active()->isAvailableFor('2020-05-03','2020-05-06')->count();
Maybe you can wrap your new scope into your model method
public function isAvailableFor($from, $to) {
$reservationsCount = $this->reservations->isAvailableFor($from,$to)->count();
return !$reservationsCount >= $this->quantity;
}

Laravel Query Builder returning all rows despite 'where' statement

I'm making a search service using the following blog post:https://m.dotdev.co/writing-advanced-eloquent-search-query-filters-de8b6c2598db
This seems to work except for the fact that my queries are returning every row in the table rather than specific ones.
For example, I have a filter such as this:
public static function apply(Builder $builder, $value)
{
return $builder->whereHas('items', function ($q) use ($value) {
$q->where('item_id', $value);
});
}
This works on the items relationship on my model. Running this query conventionally seems to work, but it fails in relation to the code in the above blog post.
The same is true for an even simpler query:
public static function apply(Builder $builder, $value)
{
return $builder->where('name', $value);
}
When I run my test it just gives me every single item in the table rather than those matching my condition.
My search code looks like this and I can't see any obvious errors:
public static function search(Request $filters)
{
$query =
static::applyDecoratorsFromRequest(
$filters,
(new User)->newQuery()
);
return static::getResults($query);
}
private static function applyDecoratorsFromRequest(Request $request, Builder $query)
{
foreach ($request->all() as $filterName => $value) {
$decorator = static::createFilterDecorator($filterName);
if (static::isValidDecorator($decorator)) {
$query = $decorator::apply($query, $value);
}
}
return $query;
}
Any help appreciated!
it's because you should use local scopes instead : https://laravel.com/docs/5.8/eloquent#local-scopes
public static function scopeWhereItem(Builder $query, $value)
{
return $query->whereHas('items', function (query) use ($value) {
$query->where('item_id', $value);
});
}
Then in your controller :
// Initiate the queryBuilder
$query = YourModel::query();
// your request is like : ['item' => '2'];
foreach($request->all()sas $filterName => $value) {
// Build the scope Name (whereItem())
$scope_name = 'where' . ucFirst($filterName);
$query->$scopeName($value);
}
$results = $query->get();
you can add some checks if the method 'scope' . ucFirst($scopeName) exists if you want
it will generate a query like this : YourModel::query()->whereItem(2)->get()'

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.

Building complex where statement in Laravel Eloquent using

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.

Categories