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();
Related
PHP/Laravel
Hey, I'm moving into abstraction in php and am attempting to validate and store values based on whatever has been submitted, where I expect that the methods should neither know what to validate against and/or which class and method to use to do so -
What I've got works but I can see that there would be issues where classes/methods do not exist. Here lays my question.
If I were to call a method in the following format, which way would be best to 'check' if class_exists() or the method exists()?
public function store(Request $request)
{
$dataSet = $request->all();
$inputs = $this->findTemplate();
$errors = [];
$inputValidators = [];
foreach ($inputs as $input) {
$attributes = json_decode($input->attributes);
if (isset($attributes->validate)) {
$inputValidators[$input->name] = $input->name;
}
}
foreach ($dataSet as $dataKey => $data) {
if (array_key_exists($dataKey, $inputValidators)) {
$validate = "validate" . ucfirst($dataKey);
$validated = $this->caseValidator::{$validate}($data);
if ($validated == true) {
$inputValidators[$dataKey] = $data;
} else {
$errors[$dataKey] = $data;
}
} else {
$inputValidators[$dataKey] = $data;
}
}
if (empty($errors)) {
$this->mapCase($dataSet);
} else {
return redirect()->back()->with(['errors' => $errors]);
}
}
public function mapCase($dataSet)
{
foreach($dataSet as $dataKey => $data) {
$model = 'case' . ucfirst($dataKey);
$method = 'new' . ucfirst($dataKey);
$attribute = $this->{$model}::{$method}($dataKey);
if($attribute == false) {
return redirect()->back()->with(['issue' => 'error msg here']);
}
}
return redirect()->back->with(['success' => 'success msg here'])'
}
For some additional context, an input form will consist of a set of inputs, this can be changed at any time. Therefore I am storing all values as a json 'payload'.
When a user submits said form firstly the active template is found, which provides details on what should be validated $input->attributes, once this has been defined I am able to call functions from caseValidator model as $this->caseValidator::{$validate}($data);.
I do not think that any checks for existence will be needed here as the validation parameters are defined against an input, thus if none exist this check will be skipped using if (array_key_exists($dataKey, $inputValidators))
However, I am dispersing some data to other tables within the second block of code using mapCase(). This is literally iterating over all array keys regardless of if a method for it exists and thus the initial check cannot be made as seen in the first block. I've attempted to make use of class_exists() and method_exists but logically it does not fit and I cannot expect them to work as I'd like, perhaps my approach in mapCase is not correct? I guess if I'm defining a class for each key I should instead use one class and have methods exist there, which would remove the need to check for the class existing. Please advise
Reference:
$attribute = $this->{$model}::{$method}($dataKey);
Solved the potential issue by using class_exists(), considering I know the method names as they are the same as the $dataKey.
public function mapCase($dataSet)
{
foreach($dataSet as $dataKey => $data) {
$model = 'case' . ucfirst($dataKey);
if (class_exists("App\Models\CaseRepository\\" . $model)) {
$method = 'new' . ucfirst($dataKey);
$attribute = $this->{$model}::{$method}($dataKey);
}
if($attribute == false) {
return redirect()->back()->with(['issue' => 'error msg here']);
}
}
return redirect()->back->with(['success' => 'success msg here'])'
}
I have a collection which contain user contacts, I want to search a value in this collection.
I tried $itemCollection->where('username', $search); but it`s showing me only if $search value is fully equal to username but I want to get results which has contains that value too.
For example I have "yunus" value as an username and when I`m searching "yunus" it is working well but I want to see result if I search "yun" or "y" values too.
I searched it and I did found I must to use where 'like' method but I did discovered it is not working for collections :(
My function for get user contacts which has my searched username value
public function index(Request $request)
{
$contacts = [];
$user = request()->user();
$search = $request->search;
Contact::for($user->id)
->orderBy('created_at', 'DESC')
->get()
->each(function ($contact) use ($user, &$contacts) {
$friend = $contact->user1_id === $user->id ? $contact->user2 : $contact->user1;
$contacts[] = $friend->toArray() + ['room' => $contact->room->toArray()];
});
$itemCollection = collect($contacts);
$filtered = $itemCollection->where('username', $search);
$filtered->all();
return response()->json($filtered);
}
Result : Json output
Try using filter() instead of where():
$itemCollection = collect($contacts);
$filtered = $itemCollection->filter(function($item) use ($search) {
return stripos($item['username'],$search) !== false;
});
https://laravel.com/docs/5.7/collections#method-filter
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'];
This might seem like a stupid and trivial question. I am having problem naming functions in PHP. I have two functions that retrieves all the information of a student given its id or name and email.
Since PHP doesn't have function overloading in the same sense as JAVA, I am having difficulty naming the functions.
Here is what I have done. These are the names that I have given them.
get_students_with_id($id) and get_students_with_name_and_email($name, $email)
But the parameters are gonna increase. I need a better and simple solution to name these functions or methods. BTW, they all belong to the same class. So what am I gonna do? Thanks in advance.
In PHP there doesn't exist the concept of method overriding like in JAVA, for example, but you can send default parameters, for example:
get_students($id, $name = null, $email = null)
This means that you don't need to call the function with the three parameters. You can do it by calling it just with one and it will assume it is the id. For example, if you want to have a function working for your example above, you could do something like:
function get_students($id, $name = null, $email) {
if (!empty($id)) {
// Get students by their ids
} else if (!empty($name) && !empty($email)) {
// Get students by their names and emails
}
}
And you can call the function above:
get_students(1); //Will retrieve studen with id 1
get_students(null, "Name", "email#email.com"); //Will retrieve students with name "Name" and email "email#email.com"
A search method could look something like this:
class Student {
public static $columns = ['id', 'name', 'email', 'password', /* ... */];
// Imagine that this method is called with the following array:
// ['name' => 'Joe', 'password' => 'Pa55w0rD']
public static function search(array $queries) {
// We will be appending WHERE clauses to this SQL query
$sql = 'SELECT * FROM students WHERE ';
// Get the column names
$parameters = array_keys($queries);
// Create a parameterized WHERE clause for each column
foreach ($parameters as & $param) {
if ( ! in_array($param, self::$columns)) {
throw "Invalid column";
}
$param = "{$param} = :{$param}";
}
// Squish parameterized WHERE clauses into one
// and append it to the SQL query
$sql .= implode(' AND ', $parameters);
// The query will now look something like this:
// SELECT * FROM students WHERE name = :name AND password = :password
// Prepare the SQL query
$stmt = DB::instance()->prepare($sql);
// Go over the queries and bind the values to the columns
foreach ($queries as $col => $val) {
$stmt->bindValue(":" . $col, $val);
// Internally the query will look something like this:
// SELECT * FROM students WHERE name = 'Joe' AND password = 'Pa55w0rD'
}
// Execute
$result = $stmt->execute();
// ...
}
}
To use the method you would do something like this:
$student = Student::search([
'name' => 'Joe',
'password' => 'Pa55w0rD',
]);
You would want to handle the data in a safer way (making sure the password is hashed, for instance), but the general idea is there.
Why not use get_students($id=0, $name='', $email='') and so on for your other parameters, then have the function do whatever is necessary based on the passed parameters?
If that gets to be too much, pass an array check for keys. So if array('id' => 1) is passed then if (array_key_exists('id', $input)) {...} would catch it and proceed with actual function work, but if other keys/values are passed then a subsequent appropriate elseif would catch it.
Update: I think a format like this might be able to handle most of your use cases, based on some of the comments I read in the question. Not sure what your DB is, so this was done with MySQL in mind.
function get_students($input) {
$where = array();
$allowed_columns = array('id', 'name', 'email');
foreach ($allowed_columns as $key) {
if (!array_key_exists($key, $input)) continue;
$where[] = "`$key` = '" . mysqli_escape_string($input[$key]) . "'";
}
if ($where) {
$query = 'SELECT ... FROM `...` WHERE ' . join(' AND ', $where);
// etc...
} else {
return false;
}
}
I would use a class instead of multiple functions
class Student
{
public static function byName($name)
{
// ...
}
public static function byId($id)
{
// ...
}
}
$student = Student::byName('joe');
This would allow it to be much cleaner and more extendible, as you can put common logic in protected static methods in the class.
If you want to do multiples you can do some chaining which is a little more complicated.
I've mocked up a quick ideone which you can reverse engineer:
http://ideone.com/duafK4
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());