Bodman
Bodman

Reputation: 91

Laravel: Filter Child Collection Back Into Itself

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

Answers (1)

mrhn
mrhn

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

Related Questions