Optional query with a lot of parameter - laravel

I must have build a query from my store items. my store items have 10 field . I just let customer search in my item's optional.
for example first one maybe want to filter on field1 , second one maybe want to filter on field1 and field2,third one maybe want to filter on field6 and field8 and filde9,... .
how can i make a query more short more efficient for this ?
note 1: I don't want to use Raw method because of its vulnerability.
note 2: i see some answers in link 1 and link 2 but I think first one can not be use for
condition like : where('field1','>=','somevalue') or where('field2','like','%somevalue%') or any sort of condition with some complexity and second one has more "if" chaining and i want to have shorter than this if it's possible

You can do this in several ways depending on the syntax you'd like. One possible way is to use a separation symbol to pass multiple arguments:
/api/user?where[]=user_id|>=|5
/api/user?where[]=user_id|2
/api/user?where[]=user_id|2&where[]=status|activated
This simply allows you to pass multiple where options where the pipe | operator separates the arguments. Note that this may cause issues if you want the pipe character to be available as search argument for instance.
Then you could simply parse this url into your query like so:
foreach($request->get('where') as $i => $values) {
$values = explode('|', $values);
// Allow the third argument to be exchanged with the second one,
// similar to how the Laravel `where()` method works.
$query->where($values[0], $values[1], $values[2] ?? null);
}
Optionally, you can add a validation method so that the syntax will be properly checked beforehand. You can add this snippet to some boot() method of a service provider:
\Illuminate\Support\Facades\Validator::extend('query_where', function($attribute, $value) {
// Require minimum length of 2 parameters.
return count(explode('|', $value)) >= 2;
});
Next, your validation in your controller action would look like this:
$this->validate($request, [
'where' => 'nullable|array',
'where.*' => 'query_where',
]);
The possibilities are endless.

Related

How to have a CakePHP model field requirement checked manually?

QUESTION UPDATED: I found out more information and therefor changed my question.
I want to save my user with his license number, but only if he selects that he has a license. I have three possible values for the pilot_type field (ROC, ROC-light and recreative) and only the last option should allow for an empty string to be submitted.
In order to achieve this, I wrote the following validation function:
$validator
->add('license_nr', 'present_if_license', [
'rule' => function ($value, $context) {
return $context['data']['pilot_type'] == 'recreative' || !empty($value);
},
'message' => "If you don't fly recreatively, a license number needs to be supplied"
]);
The problem is that setting any validation rule on a field will trigger an additional check in the CakePHP Model class that will reject the value if it's empty. I tried to fix this by adding ->allowEmpty('license_nr'), but that rule makes for the model to accept an empty string without even running my custom function. Even putting them in order and using 'last' => true on my custom rule doesn't resolve this problem:
$validator
->add('license_nr', 'present_if_license', [
'rule' => function ($value, $context) {
return false;
// return $context['data']['pilot_type'] == 'recreative' || !empty($value);
},
'last' => true,
'message' => "If you don't fly recreatively, a license number needs to be supplied"
])
->allowEmpty('license_nr');
How do I make CakePHP run my custom function in order to see if the field can be empty, rather than just assuming that it can never be empty or must always be empty?
By default fields aren't allowed to be empty, so that's the expected behavior and you'll have to explicitly allow empty values.
In order to achieve what you're trying to do, instead of using an additional rule, you should customize the allowEmpty() rule, use the second argument to supply a callback (note that unlike rule callbacks it will receive a single argument that provides the context).
So you'd do something like this, which you may need to modify a bit, for example depending on whether you need it to work differently for creating ($context['newRecord'] = true) and updating ($context['newRecord'] = false) records:
->allowEmpty(
'license_nr',
function ($context) {
return $context['data']['pilot_type'] === 'recreative';
},
"If you don't fly recreatively, a license number needs to be supplied"
)
As of CakePHP 3.7 you can use allowEmptyString(), it will work the same way, you just need to swap the second and third arguments, as allowEmptyString() takes the message as the second argument, and the callback as the third argument.
See also
Cookbook > Validation > Conditional Validation

When to use codeigniter where() function and when get_where()

I've been reading docs regarding these 2 functions, but I still can't quite get the difference between these two.
I get it that get_where selects data from DB, but when should we use where() function and not get_where()?
get_where()
There are tons of other ways to get data using CodeIgniter’s ActiveRecord implementation, but you also have full SQL queries if you need them:
Example:
$query = $this->db->get_where('people', array('id' => 449587));
Ultimately, get_where() is the naive case, and certainly the most commonly-used in my code anyway — I can’t think of an another framework in any other language that enables you to be this productive with data with a single line of code.
get_where([$table = ''[, $where = NULL[, $limit = NULL[, $offset = NULL]]]])
Parameters:
$table (mixed) – The table(s) to fetch data from; string or array
$where (string) – The WHERE clause
$limit (int) – The LIMIT clause
$offset (int) – The OFFSET clause
This function is working as get() but with also allows the WHERE to be added directly.
Identical to the $this->db->get(); except that it permits you to add
a where clause in the second parameter, instead of using the
db->where() function.
where()
This function enables you to set WHERE clauses in your query.
You can also add where clauses, sort conditions and so forth:
$this->db->select('first_name', 'last_name');
$this->db->from('people');
$this->db->where('id', 449587);
$this->db->order_by('last_name, first_name');
$query = $this->db->get();
It’s possible to chain all these conditions together on a single line, but I prefer putting them on separate lines for readability.
In simple word, get_where is a luxury to use but where() gives you more flexibility to use.
The get_where is a combined function -so to speak- of the both where() and get() functions,
according to the documentation :
$this->db->get_where()
Identical to the above function except that it permits you to add a
"where" clause in the second parameter, instead of using the
db->where() function
also by take a quick look at the source code of the get_where() method you will notice that
if ($where !== NULL)
{
$this->where($where);
}
where $where is the second parameter of get_where() method.
In simple terms, $this->db->get_where('table name', 'where clause') is an alias for $this->db->where('where clause')->get('table name');

Eloquent Collections Where Condition

I want to get alternative products pictures.
dd($alternativeProduct->pictures);
When die and dump i get this result.
I need to get only the picture which is main. If main equals to 1. It is main picture.
When I write
dd($alternativeProduct->pictures->where('main', 1))
I got an empty array.
Here is my relation with Product and Picture relation
public function pictures(){
return $this->hasMany('App\Models\ProductPicture');
}
What can i do ?
The where method in a collection has three parameters: $key, $value and $strict, the last one defaults to true if not passed when calling the method. When $strict is true the comparison is done via === which does not do type coercion, meaning "1" === 1 is false.
From your dump data, I can see that "main" => "1" which means it's a string and you're passing an integer 1 to the where method, resulting in what I described above as a false condition and returning an empty result set. You can fix that by disabling strict comparison:
$alternativeProduct->pictures->where('main', 1, false);
// or the equivalent helper whereLoose
$alternativeProduct->pictures->whereLoose('main', 1);
Or passing a string as the value:
$alternativeProduct->pictures->where('main', "1");
That being said, if that's the only place you're using that collection in that request's context, I suggest that you filter the results at the database level, not after they are fetched, like so:
$alternativeProduct->pictures()->where('main', 1)->get();
Accessing the relations as a method ->pictures(), instead of as a property ->pictures, will return a Query Builder instance that allows you to add conditions to the database query and only fetch the actual items you need from the database, instead of fetching them all and filtering them out afterwards.
You may want to use whereLoose instead:
dd($alternativeProduct->pictures->whereLoose('main', 1))
From the Laravel docs:
The where method uses strict comparisons when checking item values. Use the whereLoose method to filter using "loose" comparisons.

Query Strings and Laravel (with inequalities)

I am creating an API in Laravel and using query strings to handle complex queries.
It is easy to handle queries like url/item/?color=red&age=3... to collect all items that are red and 3 years old.
But this is because these are discrete variables being queried for equality. Say for example I want to retrieve all users who registered after a certain date. How would I handle this?
I was thinking maybe:
url/item/?registered_later_than=DDMMYYYY
Is there a better way?
I'd suggest something like this:
url/item/?registered=>:DDMMYYYY
The parameter name is the name of the attribute
Right at the beginning of the parameter value is the operator
Operator and value is separated by a : (it actually can be any separation character you want)
Other examples:
url/item/?name=like:foo
url/item/?email==:foo.bar#example.com
I agree email==:foo looks a bit weird. You could also use words or abbreviations ("eq", "gt", etc) instead of operator signs.
How to parse it
$filters = Input::all();
$query = Model::newQuery();
foreach($filters as $attribute => $filter){
$parts = explode(':', $filter, 2);
$operator = $parts[0];
$value = $parts[1];
$query->where($attribute, $operator, $value);
}
I hope this gives you an idea how you could do it ;)
Not really a "better way" but you can try something like this :
url/item/?operator=lt&date=20141223
Operator can be :
lt : lesser than
gt : greater than
eq : equals to
etc.
Or whatever you want (maybe it's more readable with in full text: "greater_than", etc.). Hope I understood well your question and it will help you.

CakePHP Pagination sort() on Related Models

I have two models: Plans and PlanDetails.
Relationship is: PlanDetails hasMany Plans. Plans belongTo PlanDetails.
In the PlanDetails view.ctp, I am pulling in related Plans.
I am trying to sort the Plans by ANY field (I've tried them all), and I cannot get it working. I assume I am overlooking something very simple.
Here is my base code:
PlanDetail >> view.ctp:
...foreach ($planDetail['Plan'] as $plan_edit) :
$class = null;
if ($i++ % 2 == 0) {
$class = ' class="altrow"';
}...
<?php echo $this->Paginator->sort('Plan ID', 'Plan.id'); ?>...
...<?php echo $plan_edit['id']; ?>
plan_details_controller.php:
...function view($id = null) {
if (!$id) {
$this->Session->setFlash(__('Invalid plan detail', true));
$this->redirect(array('action' => 'index'));
}
$this->PlanDetail->recursive = 2; // To run the editable form deeper.
$this->set('planDetail', $this->PlanDetail->read(null, $id));
$this->set('plan', $this->paginate('Plan'));
}...
I should add, no errors are being thrown and the sort() arrows on the ID field are showing as expected, but the sort order DOES not change when clicked either way.
Sorry, I'm not able to comment on the question itself, but I've noticed that in your action, you set planDetail to be the PlanDetail record you read (with recursive set to 2), and then you set plan to be the result of the paginate call.
Then, in your view template, you're iterating over $planDetail's contained Plan association, like this:
foreach ($planDetail['Plan'] as $plan_edit):
But in order to get the sorting and pagination done, you need to be displaying the results of the paginate call i.e. iterate over the records contained in $plan.
Do a debug($plan) in your view template to see what results you get there and to see if the records' ordering changes when you sort by different fields.
Also, perhaps you're using syntax I'm not aware of, but if you simply call $this->paginate('Plan') in your controller, I don't know that you're going to get only the related Plan records for your particular PlanDetail record. (There's nothing tying the $id passed into your view action with the Plan records.) You might need to add some conditions to the paginate call, like so:
$this->paginate['Plan'] = array('conditions' => array('Plan.plan_detail_id' => $id));
$this->set('plans', $this->paginate('Plan'));
Here is what I did to solve this. Based on some helpful direction from johnp & tokes.
plan_details/view.ctp:
...$i = 0;
foreach ($plan as $plan_edit) : // note $plan here.
}...
In my plan_details_controller.php view action:
$conditions = array("Plan.plan_detail_id" => "$id");
$this->set('plan', $this->paginate('Plan', $conditions)); // note "plan" in the first argument of set (this is required to pass the results back to the view). Also. The $condition is set to return ONLY plans that match the plan_detail_id of id (on the plan_details table).
And in my view, in order to get my results (because I changed the array name), I had to change the way I was getting the values to:
$plan_edit['Plan']['modified'] // note I placed ['Plan'] in front of these calls as the array called for this to get the data...
Well until the next problem! SOLVED.

Resources