Laravel dynamic whereHas - php

I am trying to run a query through a relationship path. In human readable format:
Get collection of orders where $order->orderItem->product->sku is LIKE 'red-jumper';
I can create this manually using a whereHas query as follows:
$query->whereHas('orderItems', function($query) use($request){
$query->whereHas('product', function ($query) use($request){
$query->where('sku', 'LIKE', '%' . $request->search . '%');
});
});
However, if I want this to be dynamic and do not know the amount of levels in the relationships, how can I do this?
I would be looking for something lik:
$paths = [
0 => 'orderItems'
1 => 'product,
2 => 'sku'
];
$query->whereHas($paths[0], function($query) use($request, $paths){
$query->whereHas($paths[1], function ($query) use($request, $paths){
$query->whereHas($paths[2], function ($query) use($request, $paths) {
$query->whereHas($paths[3], function ($query) use ($request, $paths) {
$query->where('sku', 'LIKE', '%' . $request->search . '%');
});
});
});
});
Maybe there is a better way to do this all together?

There is a slightly better way in my opinion. You can use dot (.) notation for relationships and collections to turn your $path array into what you want. First we reduce the $path to a dotted relationship and separate the attribute you want to filter by later
$path = ['orderItems', 'product', 'sku']
$param = array_pop($path)
// $path = ['orderItems', 'product']
// $param = 'sku'
$dotPath = collect($path)->reduce(function ($c, $i) {
return $c . $i . '.';
});
// $dotPath = 'orderItems.product.'
$dotPath = substr($dotPath, 0, -1)
// $dotPath = 'orderItems.product'
And then we check with whereHas
$query->whereHas($dotPath, function ($query) use ($request, $param){
$query->where($param, 'LIKE', '%' . $request->search . '%');
})->get();

Related

Laravel Query Builder Join vs With Function, Which One is Better?

I want to ask about Laravel Query using Join or With which is better.
In this case there is a short query that I have tried. But there are some things that make me wonder.
In my case, I'm trying to create a list of users using the API. The problem lies in sorting the data.
The problem is divided into several.
If I use With.
The advantage of using with is that I can call the attributes in the model without rewriting the attributes I want to use. But I was confused when calling data related to other tables for me to sort. example query:
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index(Request $request)
{
$sortBy = $request->query('sortBy');
$sortDesc = (is_null($request->query('sortDesc'))) ? $request->query('sortDesc') : ($request->query('sortDesc') == 'true' ? 'desc' : 'asc');
$page = $request->query('page');
$itemsPerPage = $request->query('itemsPerPage');
$search = $request->query('search');
$starDate = $request->query('start');
$endDate = $request->query('end');
$start = ($page - 1) * $itemsPerPage;
$query = MemberRegular::query();
$query->with(['users' => function ($subQuery) {
$subQuery->select('id', 'name', 'email', 'phone');
}]);
$query->select(
'id',
'code'
);
if ($search) {
$query->where(function ($subQuery) use ($search) {
$subQuery->where('code', 'like', '%' . $search . '%');
$subQuery->orWhere(function ($q) use ($search) {
$q->whereHas('users', function ($j) use ($search) {
$j->where('name', 'like', '%' . $search . '%');
$j->orWhere('email', 'like', '%' . $search . '%');
})
});
});
}
if ($sortBy && $sortDesc) {
$query->orderBy($sortBy, $sortDesc)->orderBy('id', 'desc');
} else {
$query->orderBy('created_at', 'desc')->orderBy('id', 'desc');
}
if ($starDate && $endDate) {
$query->whereBetween('created_at', [$starDate, $endDate]);
}
$data['totalItems'] = $query->count();
$data['items'] = $query->skip($start)->take($itemsPerPage)->get();
return HResource::collection($data['items'])->additional(['totalItems' => (int) $data['totalItems']], true);
}
If I use Join.
The advantage of using Join is that I can sort data easily if the data is related to other tables. But I have to re-create a new attribute in a collection. example query:
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index(Request $request)
{
$sortBy = $request->query('sortBy');
$sortDesc = (is_null($request->query('sortDesc'))) ? $request->query('sortDesc') : ($request->query('sortDesc') == 'true' ? 'desc' : 'asc');
$page = $request->query('page');
$itemsPerPage = $request->query('itemsPerPage');
$search = $request->query('search');
$starDate = $request->query('start');
$endDate = $request->query('end');
$start = ($page - 1) * $itemsPerPage;
$query = MemberRegular::query();
$query->join('users', 'users.id', '=', 'member_regulars.user_id');
$query->select(
'member_regulars.id',
'member_regulars.code',
'users.name',
'users.email',
'users.phone'
);
if ($search) {
$query->where(function ($subQuery) use ($search) {
$subQuery->where('member_regulars.code', 'like', '%' . $search . '%');
$subQuery->orWhere('users.name', 'ilike', '%' . $search . '%');
$subQuery->orWhere('users.email', 'ilike', '%' . $search . '%');
$subQuery->orWhere('users.phone', 'ilike', '%' . $search . '%');
});
}
if ($sortBy && $sortDesc) {
$query->orderBy($sortBy, $sortDesc)->orderBy('member_regulars.id', 'desc');
} else {
$query->orderBy('member_regulars.created_at', 'desc')->orderBy('member_regulars.id', 'desc');
}
if ($starDate && $endDate) {
$query->whereBetween('member_regulars.created_at', [$starDate, $endDate]);
}
$data['totalItems'] = $query->count();
$data['items'] = $query->skip($start)->take($itemsPerPage)->get();
return HResource::collection($data['items'])->additional(['totalItems' => (int) $data['totalItems']], true);
}
If using Query With The problem lies in sending the sortBy parameter like the following users.name it will be an error because the table is not found in the query I made, but I can immediately call attributes that can be used directly without needing to create a new custom attribute.
If using Query Join, the problem is that I have to re-create custom attributes to be used in data collections, but I don't need to worry about sorting data.
Both are equally important to me. However, if anyone is willing to give advice on the best way I have to use Join or With for this case.
Thank you.
Finally I found the best solution to the problem I was facing. I hope this can help others.
Here I choose to use Join why? because it turns out that I can call the function relations users() in the model that I created so that I can still retrieve custom attributes in the Users model. I don't really know if this is the right way or not. I hope this helps others.
Thank you.

how to prevent specific word to search result laravel

I need to prevent some specific searches like a username.
the scenario is when I search auth username I get all friends but if I search auth username I don't want to search result,
here's my query
$searchVal = $request->input('search');
$friend_data = Friend::with('getAccept', 'getRequest')->where('status', '1')->where(static function ($q) use ($user) {
$q->where('user_id', $user->id)->orWhere('ref_id', $user->id);
})->where(function ($q1) use ($searchVal) {
if($searchVal) {
$q1->whereHas('getRequest', function ($q) use ($searchVal) {
$q->where('username', 'LIKE', '%' . $searchVal . '%');
});
$q1->orWhereHas('getAccept', function ($q) use ($searchVal) {
$q->where('username', 'LIKE', '%' . $searchVal . '%');
});
}
})->get();
Thanks in Advance.
Just replace if($searchVal) in your where function with if($searchVal !== Auth::user()->username) and that should exclude the auth username results.

Trying to search in a model via eloquent - laravel with optimal parameters

Im trying to figure out why my query is ignoring everything except the title and the description. The search button leading to the controller, is for filtering different type of ads , by category, by region, by price.
For example if now i search for existing ad and its found by title / keyword -> will always show, even if i choose a different region / category/ price range
Im trying to use something that will save me a lot of if statements to check if they exist in the request. Maybe other option si to use https://github.com/mohammad-fouladgar/eloquent-builder to build my query
public function index(Request $request)
{
$keyword = $request['keyword'];
$category_id = $request['category_id'];
$type_id = $request['type_id'];
$region_id = $request['region_id'];
$min_price = $request['min_price'];
$max_price = $request['max_price'];
$result = Ad::when($keyword, function ($q) use ($keyword) {
return $q->where('title', 'like', '%' . $keyword . '%')->orWhere('description', 'like', '%' . $keyword . '%');
})
->when($category_id, function ($q) use ($category_id) {
return $q->where('category_id', $category_id);
})
->when($region_id, function ($q) use ($region_id) {
return $q->where('region_id', '=', $region_id);
})
->when($type_id, function ($q) use ($type_id) {
return $q->where('adtype_id', '=', $type_id);
})
->when($min_price, function ($q) use ($min_price) {
return $q->where('price', '>=', $min_price);
})
->when($max_price, function ($q) use ($max_price) {
return $q->where('price', '<=', $max_price);
})
->paginate(8);
My get param url looks like that:
search?keyword=&category_id=0&region_id=0&type_id=0&min_price=&max_price=
The produced query in mysql when i search for existing ad by its name and i look for a different category is:
select * from `ads` where `title` like '%test test%' or `description` like '%test test%' and `category_id` = '2' limit 8 offset 0
The ad is found, but the actual category is 1, not 2, same for all others optimal parameters.
You can edit your query to look for specific relations, using whereHas. This method will allow you to add customized constraints to a relationship constraint, such as checking the content of a comment.And to check max/min price, use where method. So, you can use it like this:
$result = Ad::when($keyword, function ($q) use ($keyword) {
return $q->where('title', 'like', '%' . $keyword . '%')->orWhere('description', 'like', '%' . $keyword . '%');
})
->whereHas('category_relation_name', function ($q) use ($category_id) {
return $q->where('category_id', $category_id);
})
->whereHas('region_relation_name', function ($q) use ($region_id) {
return $q->where('region_id', $region_id);
})
->whereHas('type_relation_name', function ($q) use ($type_id) {
return $q->where('adtype_id', $type_id);
})
->where('price', '>=', $min_price);
->where('price', '<=', $max_price);
->paginate(8);

Laravel - AJAX Search allow Multiple Filters to be applied

I have a controller that works on an AJAX jQuery call when I need to search/filter the database:
$launchsitesatellite = DB::table('satellites')
->where(function($q) use ($request) {
if(empty($request->type) && empty($request->rocket_type)) {
$q->orWhere('satname','LIKE','%'.$request->search.'%')
->orWhere('norad_cat_id','LIKE','%'.$request->search.'%')
->orWhere('country','LIKE','%'.$request->search.'%')
->orWhere('object_id','LIKE','%'.$request->search.'%');
} else {
if(!empty($request->type)) {
$q->orWhere($request->type,'LIKE','%'.$request->search.'%');
}
if(!empty($request->object_type)) {
$q->orWhere('object_type','LIKE','%'.$request->object_type.'%');
}
if(!empty($request->launch_year)) {
$q->orWhere('launch','LIKE','%'.$request->launch_year.'%');
}
}
})
->where('site', $site_code)->Paginate(300);
This controller can search/filter my database with no problem. The only thing I would like to fix is to allow multiple filters to be applied. For example, currently when I filter by Object Type and then decide to filter by Country, it resets the Object Type.
What I want to be able to do is to allow it to filter by Object Type AND Country, not only one.
There was a lack of examples/documentation so I could not find any examples of how this is done.
EDIT: JS AJAX Call
$("#filter-type").change(function() {
$value=$(this).val();
$.ajax({
type: "get",
url: "{{$launchsitename->site_code}}",
data: {'search':$value, type:'object_type'},
success: function(data){
$('#launchsatdisplay').html(data);
}
});
});
I think the reason you're having this issue is because you're using orWhere rather than where so in theory the more filters you use the more results you will have returned (rather than limiting the results).
$launchsitesatellite = DB::table('satellites')
->where(function ($q) use ($request) {
if (!$request->has('type') && !$request->has('rocket_type')) {
$q->orWhere('satname', 'LIKE', '%' . $request->search . '%')
->orWhere('norad_cat_id', 'LIKE', '%' . $request->search . '%')
->orWhere('country', 'LIKE', '%' . $request->search . '%')
->orWhere('object_id', 'LIKE', '%' . $request->search . '%');
} else {
if ($request->has('type')) {
$q->where($request->type, 'LIKE', '%' . $request->search . '%');
}
if ($request->has('object_type')) {
$q->where('object_type', 'LIKE', '%' . $request->object_type . '%');
}
if ($request->has('launch_year')) {
$q->where('launch', 'LIKE', '%' . $request->launch_year . '%');
}
}
})
->where('site', $site_code)
->Paginate(300);
Also, just FYI, Laravel Query Builder comes with a when() method which is an alternative to using multiple if statements. So the main else section would look like:
$q
->when($request->has('type'), function ($q) use ($request) {
$q->where($request->type, 'LIKE', '%' . $request->search . '%');
})
->when($request->has('object_type'), function ($q) use ($request) {
$q->where('object_type', 'LIKE', '%' . $request->object_type . '%');
})
->when($request->has('launch_year'), function ($q) use ($request) {
$q->where('launch', 'LIKE', '%' . $request->launch_year . '%');
});
Obviously, you don't have to do this though (I just thought I'd mention it).
Hope this helps!

Where and If Statements Laravel Eloquent

I have built a multi filter search for my website but I cant seem to find any documentation on multiple if statements inside of where for my search.
Returns Lots of Results
$data = Scrapper::paginate(15);
Returns none.. need it to be this way to have where statements with IF see bellow.
$database = new Scrapper;
$database->get();
Example of what I want to do..
$database = new Scrapper;
if (isset($_GET['cat-id'])) {
$database->where('cat_id', '=', $_GET['cat-id']);
}
if (isset($_GET['band'])) {
$database->where('price', 'BETWEEN', $high, 'AND', $low);
}
if (isset($_GET['search'])) {
$database->where('title', 'LIKE', '%'.$search.'%');
}
$database->get();
Very similar to this: Method Chaining based on condition
You are not storing each query chains.
$query = Scrapper::query();
if (Input::has('cat-id')) {
$query = $query->where('cat_id', '=', Input::get('cat-id'));
}
if (Input::has('band')) {
$query = $query->whereBetween('price', [$high, $low]);
}
if (Input::has('search')) {
$query = $query->where('title', 'LIKE', '%' . Input::get($search) .'%');
}
// Get the results
// After this call, it is now an Eloquent model
$scrapper = $query->get();
var_dump($scrapper);
Old question but new logic :)
You can use Eloquent when() conditional method:
Scrapper::query()
->when(Input::has('cat-id'), function ($query) {
$query->where('cat_id', Input::get('cat-id'));
})
->when(Input::has('band'), function ($query) use ($hight, $low) {
$query->whereBetween('price', [$high, $low]);
})
->when(Input::has('search'), function ($query) {
$query->where('title', 'LIKE', '%' . Input::get('search') .'%');
})
->get();
More information at https://laravel.com/docs/5.5/queries#conditional-clauses

Categories