Reputation: 91
I'm writing a search function that utilises three parameters, make, model, and fueltype. The 'make' parameter is required so I am first selecting the make and its related models into a collection using the code below.
// Find Make and Models
$makeAndModels = Make::where('name', $request->make)->with('carmodels', 'carmodels.fueltype')->first();
At this point the collection looks like this:
{
"id": 2,
"name": "Toyota",
"created_at": "2020-07-26T16:11:06.000000Z",
"updated_at": "2020-07-26T16:11:06.000000Z",
"carmodels": [
{
"id": 3,
"name": "Prius",
"make_id": 2,
"fueltype_id": 2,
"created_at": "2020-07-26T16:44:21.000000Z",
"updated_at": "2020-07-26T16:44:21.000000Z",
"fueltype": {
"id": 2,
"name": "Hybrid",
"created_at": "2020-07-26T16:11:06.000000Z",
"updated_at": "2020-07-26T16:11:06.000000Z"
}
},
{
"id": 4,
"name": "Hilux",
"make_id": 2,
"fueltype_id": 3,
"created_at": "2020-07-26T16:44:21.000000Z",
"updated_at": "2020-07-26T16:44:21.000000Z",
"fueltype": {
"id": 3,
"name": "Diesel",
"created_at": "2020-07-26T16:11:06.000000Z",
"updated_at": "2020-07-26T16:11:06.000000Z"
}
}
]
}
I then want to filter the 'carmodels' child collection by name and fueltypes, returning the filtered data back into its original self, rather than into new collection. These filters should only run if the additional params have been provided, the code I am currently using looks like this.
//Filter Models By Additional Parameters
//Model
if ($request->model) {
$model = $request->model;
$makeAndModels->carmodels = $makeAndModels->carmodels->filter()->where('name', $model);
}
For some reason filtering this way doesn't override the original collection, If I filter into a new child collection, for example,
//Filter Models By Additional Paramters
//Model
if ($request->model) {
$model = $request->model;
$makeAndModels->carmodelsFiltered = $makeAndModels->carmodels->filter()->where('name', $model);
}
It produces the following outcome, so I know the filter should work.
{
"id": 2,
"name": "Toyota",
"created_at": "2020-07-26T16:11:06.000000Z",
"updated_at": "2020-07-26T16:11:06.000000Z",
"carmodelsFiltered": [
{
"id": 3,
"name": "Prius",
"make_id": 2,
"fueltype_id": 2,
"created_at": "2020-07-26T16:44:21.000000Z",
"updated_at": "2020-07-26T16:44:21.000000Z",
"fueltype": {
"id": 2,
"name": "Hybrid",
"created_at": "2020-07-26T16:11:06.000000Z",
"updated_at": "2020-07-26T16:11:06.000000Z"
}
}
],
"carmodels": [
{
"id": 3,
"name": "Prius",
"make_id": 2,
"fueltype_id": 2,
"created_at": "2020-07-26T16:44:21.000000Z",
"updated_at": "2020-07-26T16:44:21.000000Z",
"fueltype": {
"id": 2,
"name": "Hybrid",
"created_at": "2020-07-26T16:11:06.000000Z",
"updated_at": "2020-07-26T16:11:06.000000Z"
}
},
{
"id": 4,
"name": "Hilux",
"make_id": 2,
"fueltype_id": 3,
"created_at": "2020-07-26T16:44:21.000000Z",
"updated_at": "2020-07-26T16:44:21.000000Z",
"fueltype": {
"id": 3,
"name": "Diesel",
"created_at": "2020-07-26T16:11:06.000000Z",
"updated_at": "2020-07-26T16:11:06.000000Z"
}
}
]
}
Anyone know of a way to filter a collection's child collection without creating a new one? Thanks in Advance.
Upvotes: 2
Views: 152
Reputation: 18956
You can filter them using the with()
call using array as first parameter. Where you can query the relation, if you pass keys that are the with includes and closures
that are the queries. These closures
are pretty generic in your case and i have defined the it before the array as it is the same for both cases.
This is not the same as you now are querying the database, but this have way better performance as you do not have to get all the data out.
$model = $request->model;
$nameFilter = function ($query) use ($model) {
$query->when($model, function ($query) use ($model) {
$query->where('name', $model);
});
};
$makeAndModels = Make::when($model, function ($query) use ($model) {
$query->where('name', $model);
})->with(
[
'carmodels' => $nameFilter,
'carmodels.fueltype' => $nameFilter,
]
)->first();
Update
To only filter the database when name is there, Laravel
has the when()
call, that simply only executes the query if a boolean condition is met.
Upvotes: 1