Laravel - extending Eloquent where clauses depending on dynamic parameters - php

I would like to construct a series of eloquent WHERE clauses dependent on the search parameters I collect from a json object.
Something like this (never mind the syntax of object,,, it is an interpretation only to demonstrate):
$searchmap = "
{
"color": "red",
"height": "1",
"width": "2",
"weight": "",
"size": "",
}";
I then take the object and decode to get a search array...
$search = json_decode($searchmap, true);
If my weight and size are set to null or are an 'empty string' I would have eloquent code that looks like this..
$gadgets = Gadget::where('color', '=', $search['color'])
->where('height', '=', $search['height'])
->where('width', '=', $search['width'])
->paginate(9);
If they have a value then eloquent code would look like this..
$gadgets = Gadget::where('color', '=', $search['color'])
->where('height', '=', $search['height'])
->where('width', '=', $search['width'])
->where('weight', '=', $search['weight'])
->where('size', '=', $search['size'])
->paginate(9);
Is there a way to accomplish this dynamically.
I suppose the question should be ins there a way to chain eloquent where clauses dynamically based on a given parameter?
In a pseudo context I am looking to do something like this
$gadgets = Gadget::
foreach ($search as $key => $parameter) {
if ( $parameter <> '' ) {
->where($key, '=', $parameter)
}
}
->paginate(9);
Can chaining of where clauses be created in some way similar to this?
Thank you for taking the time to look at this!
UPDATE:
I also came up with something like this that seems to work well but i would like to welcome suggestions if improvement is a good idea.
$gadgets = New Gadget();
foreach ($search as $key => $parameter) {
if($parameter != ''){
$gadgets = $gadgets->where($key, '=', $parameter);
}
}
$gadgets = $gadgets->paginate(9);
FINAL
And thanks to #lukasgeiter below I think I will go with this
$gadgets = Gadget::whereNested(function($query) use ($search) {
foreach ($search as $key => $value)
{
if($value != ''){
$query->where($key, '=', $value);
}
}
}, 'and');
$gadgets = $gadgets->paginate(9);

That's easy. Laravel's where function allows you to pass in an array of key value pairs.
$searchmap = array(
'color' => 'red',
'height' => '1'
// etc
);
$gadgets = Gadget::where($searchmap)->paginate(9);
If you are curious, that's the relevant part of the source (\Illuminate\Database\Query\Builder)
public function where($column, $operator = null, $value = null, $boolean = 'and')
{
// If the column is an array, we will assume it is an array of key-value pairs
// and can add them each as a where clause. We will maintain the boolean we
// received when the method was called and pass it into the nested where.
if (is_array($column))
{
return $this->whereNested(function($query) use ($column)
{
foreach ($column as $key => $value)
{
$query->where($key, '=', $value);
}
}, $boolean);
}
// many more lines of code....
}
Edit
To have more control over it (e.g. changing the "=" to another comparison operator) try using the code laravel uses internally directly:
$gadgets = Gadget::whereNested(function($query) use ($searchmap)
{
foreach ($searchmap as $key => $value)
{
if($value != ''){
$query->where($key, '=', $value);
}
}
}, 'and')->paginate(9);

For anyone who needs it, here's a modified version of lukasgeiter's answer that solves the 'variable number of wheres' problem while also allowing (1) different operators for each where clause and (2) the capacity to also use whereIn for when one of your "wheres" must be able to match one of multiple values (the function below detects when an array of values is passed and, thus, uses whereIn instead of where).
The $paramSets variable assignment at the beginning (below) essentially describes how to use it.
$paramSets = [
"size"=>["op"=>"=","values"=>"large"],
"production_date"=>["op"=>">=","values"=>"2015-12-31"],
"color"=>["op"=>"=","values"=>["red","blue"]],
"name"=>["op"=>"like","values"=>"M%"]
];
$items = db::table('products')
->whereNested(function($query) use ($paramSets) {
foreach ($paramSets as $k => $v)
{
if ($v != ''){
if (is_array($v["values"])){
$query->whereIn($k,$v["values"]);
}
else {
$query->where($k, $v["op"], $v["values"]);
}
}
}
}, 'and');
dd($items->get());

Related

Laravel execute where condition based on variable null or not

I am new to laravel. I want to execute my where condition if my variable value is not null. I tried the below code exactly not getting an idea of what to do.
$search = $array['search'];
$id = $array['id'];
$Data = Model::where('id', '=', $id)
if($search != '') {
//I want to include where condition here
}
Use Conditional Clauses
Model::when($search !== null, function ($query) use ($search) {
return $query->where('column', $search);
})
Your $Data variable is an Eloquent Query Builder object so you can add conditions to it for the query you are building:
if ($search) {
$Data->where('field', $search);
}

Laravel Collection, replace where method with filter method

I have a collection on which I use the where method in order to get only the element that match like this:
myCollection->where('akey', $value);
When I try to transform this with the filter method, it fails:
myCollection->filter(function($value, $key){
if($key === 'akey' && $value === $aValue)
return true;
});
I try to use filter because I want to select items in the collection if theirs values are equals to multiple value. (where or Where or Where or Where basically).
I assume that $aValue is defined outside of the filter function, you should pass it to the callback function like this:
myCollection->filter(function($value, $key) use ($aValue) {
if($key === 'akey' && $value === $aValue)
return true;
});
-- EDIT --
Based on your example using where I think this should work.
myCollection->filter(function($item) use ($aValue) {
return $item->aKey === $aValue;
});
I write a simple PHP script to describe your problem, Please check out the inline comments for details.
<?php
require __DIR__ . '/vendor/autoload.php';
use Illuminate\Support\Collection;
$data = [
[
'akey' => 'the answer',
'avalue' => 'vote up',
],
];
$filterKey = 'the answer';
$c = new Collection($data);
$c = $c->filter(function($val, $key) use ($filterKey) {
var_dump($key); // $key is indexed number, It will output "int(0)"
// return $key == $filterKey; // XXX Here is problem, Try to comment out to see the different.
return $val['akey'] == $filterKey; // Get value from $val, which is associative array.
});
print_r($c->all());
Output:
/Users/gasolwu/Code/laravel/demo.php:18:
int(0)
Array
(
[0] => Array
(
[akey] => the answer
[avalue] => vote up
)
)

Laravel query whereIn - show results from which search

I have an array that comes to controller's action.
$arrOfTags = $request['position'];
That array looks like :
['manager', 'consultant'];
Next, I am querying the DB for CV's where position is one of these.
$query = Cv::query();
$query->whereIn('position', $arrOfTags);
...
->get();
Now the question :
If $request['position'] = ['manager','consultant']; and whereIn clause finds result just for position = 'consultant' and none for 'manager', how can I programmatically discover that results are found for 'consultant' and/or didn't found for 'manager' ?
EDIT
All my query's code :
$arrOfTags = explode(',', $request['position']);
$query = Cv::query();
$query->whereIn('position', $arrOfTags)
if($request['salary']) {
$query->whereIn('salary', $request['salary']);
}
if($request['skill']) {
$query->join('skills', 'cvs.id', '=', 'skills.cv_id')
->join('allskills', 'skills.allskills_id', '=', 'allskills.id')
->select('cvs.*', 'allskills.name AS skillName')
->whereIn('skills.allskills_id', $request['skill']);
}
if($request['language']) {
$query->join('languages', 'cvs.id', '=', 'languages.cv_id')
->join('alllanguages', 'languages.alllanguages_id', '=', 'alllanguages.id')
->select('cvs.*', 'alllanguages.name as languageName')
->whereIn('languages.alllanguages_id', $request['language']);
}
$cvs = $query->distinct()->get();
Imagine that $arrOfTags values are ['manager', 'consultant', 'sales']
I want somehow to discover that results was found for position =
manager and consultant, and didn't found for position = 'sales'
You can load the data from DB:
$cvs = CV::....;
And then use the partition() method:
list($manager, $consultant) = $cvs->partition(function ($i) {
return $i['position'] === 'manager';
});
Or the where() method:
$manager = $cvs->where('position', 'manager');
$consultant = $cvs->where('position', 'consultant');
Both partition() and where() will not execute any additional queries to DB.
You can do this way too:
$managers = $collection->search(function ($item, $key) {
return $item['position'] === "manager";
});
$consultants = $collection->search(function ($item, $key) {
return $$item['position'] === "consultant";
});
You could use count().
if(($query->count)==($query->where('position','consultant')->count())){
///all are coming for position=consultants
}
Or you could use groupBY-
$query = $query->groupBy('position')->toArray();
And retrieve by-
$consultants = $query['consultant'];

Laravel 5 Database Like query on a array

I have been working on a laravel advance search filter where a user can input several field in a form and on submit it generates data table for given fields. and if fields are empty then it just shows all the records from database (unsets the filter).
public function advanced_search(Request $request)
{
//user submitted data in post
$post = $request->all();
/*
filter array keys are defined here
e.g first_name, last_name
*/
$simple_filter = array(
"first_name" => "",
"last_name" => "",
"email" => "",
"company_name" => "",
);
foreach ($simple_filter as $key => $value) {
if (isset($post[$key]) && ($post[$key] != null) && !empty($post[$key])) {
$simple_filter[$key] = $post[$key];
} else {
//user didn't send this field in post data so removing this from filter list
unset($simple_filter[$key]);
}
}
$query = DB::table('contacts')->where($simple_filter)->get();
return DataTables::of($query)
->toJson();
}
Now this code is working fine. But requirements are changed. This code only return the exact details. Now I want to show record even if substring matches. (LIKE %data$). How can I modify this?
Use orWhere() if you want to find contacts where one of the fields is like a given form input. Or use where() if you want to use all the form input elements to filter contacts:
public function advanced_search(Request $request)
{
$query = Contact::query();
$fields = ['first_name', 'last_name', 'email', 'company_name'];
foreach ($fields as $field) {
if ($request->filled($field)) {
$query = $query->orWhere($field, 'like', '%' . $request->get($field) . '%');
}
}
return DataTables::of($query->get())->toJson();
}
Also, you're doing a lot of redundant checks. Just use the ->filled() method instead. It will return false if a field is empty, or doesn't exist or null.
If you would like to determine if a value is present on the request and is not empty, you may use the filled method
https://laravel.com/docs/5.5/requests#retrieving-input
You can create the query before the loop and add all where filters in the loop. The second parameter of the where function can be the comparer, in your case: like.
$query = DB::table('contacts');
foreach ($simple_filter as $key => $value) {
if (isset($post[$key]) && ($post[$key] != null) && !empty($post[$key]))
{
$query = $query->orWhere($key, 'like', '%' . $value . '%');
}
}
return DataTables::of($query->get())->toJson();

I'm using Yii addColumnCondition and want to change the default behaviour from '=' to using LIKE and %

I'm using the addColumnCondition function as I like how it forms the queries for multiple queries. But I can't find anything in the documentation to change it's comparison operation from the simple = needle to a LIKE %needle%. There is a function that does a LIKE in addSearchCondition() but then it means to get the same query formation result, I'll have to do some for loops and merge conditions which I'd like to avoid if there is a better solution.
Here's the code
foreach($query_set as $query){
foreach($attributes as $attribute=>$v){
$attributes[$attribute] = $query;
}
$criteria->addColumnCondition($attributes, 'OR', 'AND');
}
And I'm getting the condition formed like
(business_name=:ycp0 OR payment_method=:ycp1) AND (business_name=:ycp2 OR payment_method=:ycp3)
So is there a way to configure the function to use LIKE %:ycp0% instead of the simple =:ycp0.
It seems, this feature is not provided by Yii's addColumnCondition method.
therefore i would recommend a way of overriding the method of CDbCriteria class and customize it your own way.
you need to create a new class called "AppCriteria", then place it inside protected/models
The code for the new class should look like,
i.e
class AppCriteria extends CDbCriteria {
public function addColumnCondition($columns, $columnOperator = 'AND', $operator = 'AND', $like = true) {
$params = array();
foreach ($columns as $name=>$value) {
if ($value === null)
$params[] = $name.' IS NULL';
else {
if ($like)
$params[] = $name.' LIKE %'.self::PARAM_PREFIX.self::$paramCount.'%';
else
$params[] = $name.'='.self::PARAM_PREFIX.self::$paramCount;
$this->params[self::PARAM_PREFIX.self::$paramCount++] = $value;
}
}
return $this->addCondition(implode(" $columnOperator ", $params), $operator);
}
}
Note: The 4th param of addColumnCondition, $like = true. you can set it to $like = false and allow the function to work with equal conditions. (A = B)
i.e
(business_name=:ycp0 OR payment_method=:ycp1) AND (business_name=:ycp2 OR payment_method=:ycp3)
if $like = true, it will allow you to have like condition. (A like %B%)
i.e
(business_name LIKE %:ycp0% OR payment_method LIKE %:ycp1%) AND (business_name LIKE %:ycp2% OR payment_method LIKE %:ycp3%)
Now Here's the working code,
$criteria = new AppCriteria();
foreach($query_set as $query){
foreach($attributes as $attribute=>$v){
$attributes[$attribute] = $query;
}
$criteria->addColumnCondition($attributes, 'OR', 'AND');
}

Categories