Handling route with multiple optional parameters in Laravel - php

I am trying to implement a filtering method for some products.
This is the route:
Route::get('/TVs/{type?}/{producer?}', 'Product\AllProducts#getTVs')->where(['type' => '4kTV|curved|lcd|oled|plasma'], ['producer'=>'Samsung'])->name('TVs');
And this is the controller function:
public function getTVs($type = null, $producer = null)
{
$products = DB::table('products')->paginate(16);
if($type!=null) {
$products = Product::where('type', $type)->paginate(16);
}
if($producer!=null) {
$products = Product::where('description','like', '%'.$producer.'%')->paginate(16);
}
return view('product.TVs', ['products' => $products]);
}
If I select the type, the page refreshes and shows the results. Then if i enter the producer, again it works. How can i make the route in such a way, that the order of the optional parameters does not matter and i can filter the results no matter the order ?

Chain your queries; right now, you're running 3 queries, with ->paginate() being a closure and triggering a DB call. Try this:
$baseQuery = DB::table("products");
if($type){
$baseQuery->where("type", "=", $type);
}
if($producer){
$baseQuery->where("description", "like", "%".$producer."%");
}
$products = $baseQuery->paginate(16);
return view("products.TVs"->with(["products" => $products]);
As you can see, we add ->where clauses as required based on the input, and only run a single ->paginate() right before the return. Not this is additive searching, so it's WHERE ... AND ... and not WHERE ... OR ...; extra logic would be required for that.

Related

Laravel - Nested relation causes previous filters to be ignored

I do a specific relation query all over the application, where I only need the User's subscriptions that have active column set to true.
And I have a scope method in User model, which applies said filter, to avoid copy/paste, like:
public function scopeWithActiveSubscriptions($query)
{
$query->with([
'subscriptions' => function ($query) {
$query->where('active', true);
},
]);
}
Now sometimes I want to eager-load the plan of each subscription, too.
For that I tried something like:
$user = User::where('id', 1)
->withActiveSubscriptions()
->with('subscriptions.plan')
->first();
$subscriptionList = $user->subscriptions;
But query results to all subscriptions,
in other words, ignores the ->where('active', true) part (of scope method).
How can I make this work correctly?
A quick solution would be modifying the scopeWithActiveSubscriptions method to allow it to accept another optional parameter that tells it which additional relations should also be included and thus you don't loose your filtering.
public function scopeWithActiveSubscriptions($query, array $with = [])
{
// just merges hard coded subscription filtering with the supplied relations from $with parameter
$query->with(array_merge([
'subscriptions' => function ($query) {
$query->where('active', true);
}
], $with));
}
Now you can tell that scope which nested relations you want to include and you no longer need to call with to include them by yourself.
$user = User::where('id', 1)
->withActiveSubscriptions(['subscriptions.plan'])
// ->with('subscriptions.plan') // no longer needed as we're telling the scope to do that for us
->first();
$subscriptionList = $user->subscriptions;
With that you can pass custom relations to the scope something like (am improvising here just for demo purposes)
$user = User::where('id', 1)
->withActiveSubscriptions([
'subscriptions.plan' => fn($q) => $q->where('plans.type', 'GOLD')
])->first();
Learn more about Laravel's Eloquent Scopes.
Hope i have pushed you further.
Seems Laravel does not have yet any chainable (Builder-style) solution (for asked situation), and we ended up editing the scope filter.
Into something like:
public function scopeWithPendingSubscriptions(Builder $query, $subRelations = null)
{
$query->with([
'subscriptions' => function (HasMany $query) use ($subRelations) {
$query->where('active', '=', true);
if ($subRelations) {
$query->with($subRelations);
}
},
]);
}
Which allows me to do query like:
// ...
->withActiveSubscriptions('plan');
Instead of my old (not working) code, which was:
// ...
->withActiveSubscriptions()
->with('subscriptions.plan');
Note that even passing nested-filters is now possible, like:
// ...
->withActiveSubscriptions(['plan' => function ($query) {
$query->where('name');
}]);
(Basically same as Laravel's ->with(...) method.)

Pass chain function as paramter as parameter in php

I have a function. It has method chaining that needs to be performed.
public function someFunction()
{
$query=$this->model;
$query->select($columns)
->skip($request->get('start') * $request->get('length'))
->take($request->get('length'))
->orderBy(
$request->get('sort_column'),
$request->get('sort_direction')
)
->get();
//Some other task
}
It was working fine but I need a slight modification in that function what I want is I want to pass a join in that function for method chaining.
public function someFunction($join_as_parameter)
{
$query=$this->model;
$query->select($columns)
//Join should be executed here as a parameter in method chaning .
->skip($request->get('start') * $request->get('length'))
->take($request->get('length'))
->orderBy(
$request->get('sort_column'),
$request->get('sort_direction')
)
->get();
//Some other task
}
So that final function execution will be like this
public function someFunction($join_as_parameter)
{
$query=$this->model;
$query->select($columns)
->join('table','sometable.id', '=', 'other_table')
->skip($request->get('start') * $request->get('length'))
->take($request->get('length'))
->orderBy(
$request->get('sort_column'),
$request->get('sort_direction')
)
->get();
//Some other task
}
Is there any way to do this? Any help would be appreciated. Thanks.
This way you can achieve what you need.
use DB;
use Closure;
use Illuminate\Database\Query\JoinClause;
public function someFunction(Closure $join_clauser)
{
//create Query Builder object
$query = DB::query();
//Add the `$join` object to the table joins for this query
$join_as_parameter = call_user_func($join_closure, $query);
$query->joins = array_merge((array) $query->joins, [$join_as_parameter]);
$query->select($columns)
->skip($request->get('start') * $request->get('length'))
->take($request->get('length'))
->orderBy(
$request->get('sort_column'),
$request->get('sort_direction')
)
->get();
//Some other task
}
//create Query Builder object
$query = DB::query();
And execute the functions as,
someFunction(function($query){
// return JoinClause object with joining conditions
return (new JoinClause($query, 'inner', 'table'))
->on('table.id', '=', 'othe_table.table_id');
});
Furthermore, you can modify this to pass array of joins to add multiple joins your the query.
To use this with eloquent models, replace
$query = DB::query();
with
$query = Model::query()->getQuery();
NOTE : ->getQuery() is used to retrieve the Query\Builder object since JoinClause expects it as the first param.

Can I chain a variable number of query scopes using Laravel 5

My users need to be able to query the database with up 5 different parameters. I think the best way to handle this is with query scopes. Then just chain together the query scopes. But I cannot figure out how to do this based on an unknown number (0-5) of search parameters.
I have it working with one specific search parameter.
$thirtyDaysAgo = Carbon::now()->subDays(30)->toDateString();
$orders = Order::DateRange($thirtyDaysAgo)->get();
return view('orders/browse', compact('orders'));
Any help would be appreciated.
Edit: More info
Parameters are posted to the page from a form:
$input = Input::all();
dd($input);
yields
array:7 [▼
"_token" => "MX4gVmbON56f9Aa88kgn2Re68GoDrtDeR6phEJ30"
"orderType" => "1"
"orderNumber" => "1"
"timePeriod" => "0"
"orderStatus" => "0"
"sku" => ""
"customer" => "0"
]
Edit: Adding query scopes
public function scopeDateRange($query, $range){
return $query->where('created_at', '>=', $range);
}
public function scopeOrderNumber($query, $orderNumber){
return $query->whereOrderNumber($orderNumber);
}
public function scopeCustomer($query, $customer){
return $query->whereCustomerId($customer);
}
public function scopeStatus($query, $status){
if($status == 'active'){
return $query->where('orderStatus_id', '!=', 15)->where('orderStatus_id', '!=', 10);
}elseif($status == 'complete'){
return $query->whereOrderStatusId(15);
}elseif($status == 'cancelled'){
return $query->whereOrderStatusId(10);
}
}
By the looks of it, you are going to want to just check to see if your parameters are empty, and if so, you can just return the query and not perform the scope check:
public function scopeDateRange($query, $range){
if (!empty($range)) {
return $query->where('created_at', '>=', $range);
}
else {
return $query;
}
}
Then, you can just chain them all together and the scope functions will sort out whether or not to filter the query all by themselves.
$orders = Order::dateRange($range)->orderNumber($orderNumber)->customer($customer)->status($status)->get();
Yes you can, just loop the user input fields, for example:
// You have this right now, so until get is called you can chain
$thirtyDaysAgo = Carbon::now()->subDays(30)->toDateString();
// Remove the get call
$order = Order::DateRange($thirtyDaysAgo); // Or use Order::query()
// Loop other fields (exclude fields which are not required in query)
foreach(Request::except(['_token', 'other_field_name']) as $field => $value)
{
// Make sure $field (form fields) match with database filed names
$order->where($field, $value);
}
$result = $order->get(); // Run the query and get the result
This is an idea and you may need to tweak to make it fit according to your need. Try it by yourelf or post most relevant information. This is not using scopeMethods but you can do it to get what you are up to.

Method Chaining based on condition

How can you do method chaining based on condition in laravel 4 ? Say if one value is not false then the method inside will be chained to the method called before the if statement.
Is it possible in laravel?
$data = User::where('username', $somevariable );
if(isset( $somevar_again ))
{
$data->where('age', 21);
}
$data->orderBy('reg_date', 'DESC')->get();
return $data->first();
// tried code above and its giving me wrong result
in codeigniter I can do this
$this->db->select('e.*, v.name_en as v_name_en')
->from($this->table_name . ' e, ' . $this->ptc_venues . ' v');
$this->db->where('e.venue_id_en = v.id');
if(isset($search)){
$this->db->where('(v.name_en LIKE "%'.$search.'%")');
}
$this->db->limit($limit, $start);
$this->db->order_by('e.added_date_en', 'DESC');
I believe your problem happened because you didn't store back the resulting query after each query builder method call.
$query = User::query();
// Checking for username if exists
if (!empty($username)) {
$query = $query->where('username', $username);
}
// Check for age if exists
if (isset($age)) {
$query = $query->where('age', $age);
}
// Ordering
$query = $query->orderBy('reg_date', 'DESC');
// Get the first result
// After this call, it is now an Eloquent model
$user = $query->first();
var_dump($user);
From Laravel 5.2 and onward, you can utilise Conditional Clauses/Statements:
Sometimes you may want statements to apply to a query only when
something else is true. For instance you may only want to apply a
where statement if a given input value is present on the incoming
request. You may accomplish this using the when method
The when method only executes the given Closure when the first parameter is true. If the first parameter is false, the Closure will not be executed.
You can use the code as follows:
$data = User::where('username', $somevariable)
->when( isset($somevar_again), function ($query) {
return $query->where('age', 21);
})
->orderBy('reg_date', 'DESC')
->get();
return $data->first();
Also, note that Laravel 5.3+, it has further been extended as documented below:
You may pass another Closure as the third parameter to the when
method. This Closure will execute if the first parameter evaluates as
false

Only query "Where" if there is a value in Laravel eloquent

I have a search query that needs to be done. However, a search doesn't always have all values set, like in this case.
$aEvents = DB::table('events')
->where('client_id', '=', $client_id);
The question is, how can I make this where statement depend on the value of $client_id. So if the value is empty I don't want the Where statement to occur.
Also, I do not want to write several complete queries with if statements in PHP. To many variables. Ideally I'd like something like this:
$aEvents = DB::table('events')
->(($client_id != "") ? where('client_id', '=', $client_id) : "");
Using eloquent is (really!) nice and save, but I'm not yet up to speed with if statements in std Class objects I guess. Any help is appreciated.
You may try something like this:
$query = DB::table('events');
if(!empty($client_id)) {
$query->where('client_id', $client_id);
}
$aEvents = $query->get(); // Call this at last to get the result
If you are passing client_id to the server via a form/query string(user input) then you may try something like this:
if($client_id = Input::get('client_id')) {
$query->where('client_id', $client_id);
}
Update: For pagination try this:
$aEvents = $query->paginate(10); // For 10 per page
So you may call links() method in your view if you pass it like this:
return View::make('viewName')->with('aEvents', $aEvents);
In the view for pagination links:
$aEvents->links()
You can also use query scopes in the model for this purpose. Scopes allow you to easily re-use query logic in your models. In the model Event, you can add the following query scope:
public function scopeClientID($query, $client_id)
{
if ($client_id != '') {
return $query->where('client_id', '=', $client_id);
} else {
return $query;
}
}
Then from your controller or wherever you're calling it from, you can do the following:
$aEvents = Event::clientID($client_id);
If you want to get all the results, then you can do:
$aEvents = Event::clientID($client_id)->get();
Or if you want pagination, you can do:
$aEvents = Event::clientID($client_id)->paginate();
You can also chain it with other methods like you'd do in a eloquent query.
You can read more about model query scopes at http://laravel.com/docs/eloquent#query-scopes

Categories