eristic's avatar

Filter through an array (not Eloquent model)

I'm building an array for an API, but I'm having difficulty figuring out how to filter the results of that array.

Is there a way to filter an array so that it returns all the data associated with the filter and not just the filter? I've tried array_filter() to no avail.

public function getFilteredEvents(Request $request)
    {

        $events = $this->event->generateAllEvents();

        if($request->has('primary_type')) {
           return $request->input('primary_type');
        }
 
    }

$events is the array and I'm grabbing the primary_type from a query string. I have no idea how to associate the $event data with the request. Right now I'm just returning a string.

Thanks in advanced.

0 likes
17 replies
davielee's avatar

@eristic You're going to need to provide a bit more information. For example, what do these events look like? What do the filters look like? What do you want to filter by, etc.

eristic's avatar

@craigpaul Thanks for the reply. The events look like below, except that there are many, many of them.

[
  [
    {
      "event_id": 24535,
      "name": "Really fun event",
      "start": "2016-11-29 16:00:00",
      "end": "2016-11-29 18:00:00",
      "description": "Event description here! Here is the event description!",
      "primary_location": "Hall of Champions",
      "primary_type": "Exhibition",
      "event_ticket_url": "",
      "event_url": ""
    },
    {
      "event_id": 24535,
      "name": "Really fun event",
      "start": "2016-11-30 16:00:00",
      "end": "2016-11-30 18:00:00",
      "description": "Event description here! Here is the event description!",
      "primary_location": "Hall of Champions",
      "primary_type": "Exhibition",
      "event_ticket_url": "",
      "event_url": ""
    }

As a start, I am trying to filter by "primary_type". The URL has "?primary_type=Exhibition" in the above example. I'm not sure what the filter would look like... that's the part that I need help with: filtering on an array.

Any nudge or guidance would be appreciated.

Thanks!

davielee's avatar
davielee
Best Answer
Level 11

For sure, this could totally be done with array_filter, you might have run into slight trouble because it looks like you're filtering on an array of an array of events (unless that was a typo), but be sure to check out that you're filtering a single array of events.

Ex. (Using PHP arrays. Notice it isn't 3 levels deep)

$events = [
    [
        'event_id' => 1,
    ],
    [
        'event_id' => 2,
    ],
]

Either way, after you have your correct array of events, you could filter them like this.

$type = $request->input('primary_type');

$events = array_filter($events, function ($event) use ($type) {
    return $event['primary_type'] === $type;
});

That will return an array of event arrays where the primary_type matches the provided input. Hope that helps you out.

2 likes
eristic's avatar

@craigpual Thank you so much for the reply! I corrected the array so it wasn't an array of arrays, but I'm still getting an undefined index on "primary_type"... Any ideas on this?

davielee's avatar

@eristic Sounds to me like one of them might just be missing that key. You should dd() the array and just make sure they all contain primary_type. If there are like hundreds or something that wouldn't make sense to check, you could check the result of the following filter.

$missing = array_filter($events, function ($event) {
    return !isset($event['primary_type']);
});

If there are any results in that $missing variable, then your events are missing a primary_type key and therefore it's not the PHP filter causing the problem.

eristic's avatar

Many thanks @craigpaul ! The undefined index was caused by an irregularity in my array. I fixed it and it's working as expected!

Is there a way to chain other requests using array_filter()? For example:

?primary_type=Exhibition&event_id=24535
davielee's avatar

@eristic Not nicely. When using plain php functions like those, you're stuck with doing one of two things.

$events = array_filter(
    array_filter($events, function ($event) use ($type) {
        return $event['primary_type'] === $type;
    }), function ($event) use ($id) {
        return $event['event_id'] === $id;
    });

Which is just....bleh. The other option is to essentially do it procedurally.

$events = array_filter($events, function ($event) use ($type) {
    return $event['primary_type'] === $type;
});

$events = array_filter($events, function ($event) use ($id) {
    return $event['event_id'] === $id;
});

That's better, but only ever so slightly. By this point, in my opinion, it would be best to switch to a Collection pipeline. You could rewrite your logic like this.

$events = collect($events)
    ->filter(function ($event) use ($type) {
        return $event['primary_type'] === $type;
    })
    ->filter(function ($event) use ($id) {
        return $event['event_id'] === $id;
    });

That provides a much clearer view of what your code is trying to achieve, plus you get all the benefits of the Collection class.

1 like
eristic's avatar

@craigpaul Thank you for such thorough responses! I will look into making it a Collection. Seriously, you're the best!

1 like
eristic's avatar

@craigpaul The collection is requiring the query string contain all elements on the chained filters. Is there a way around this? I am looking to call one or two or all filters at a given moment.

davielee's avatar

@eristic Do you mean that the filtered events have to include all the given values from the query string to be correct? Just need to know before I go wildly writing probably incorrect code haha.

eristic's avatar

@craigpaul That's what currently happening:

$events = collect($events)
    ->filter(function ($event) use ($type) {
        return $event['primary_type'] === $type;
    })
    ->filter(function ($event) use ($id) {
        return $event['event_id'] === $id;
    });
return $events->all();

requires that type and id be set in the query string in order to return a result. In this example, I had to return $events->all() to get any results to show at all.

Ideally, I'd like to put just one of the filters in the query string and it would still work as it would with two or three filters.

Thank you so much for taking time with this.

davielee's avatar

@eristic Ah gotcha! Thanks for explaining. So you would have to grab all your filters from the request and then loop through them essentially. Something like this should do the trick.

$filters = $request->only(['primary_type', 'event_id']);
$events = collect($events);

foreach($filters as $name => $value) {
    $events = $events
        ->filter(function ($event) use ($name, $value) {
            return $event[$name] === $value
        });
}

return $events->all();

When using $request->only($array) you will get back an array like the following.

[
    'primary_type' => 'value_1',
    'event_id' => 234567,
]

That is what allows you to loop over them like we did above and make it only include the ones you include in the query string. Hope that helps you out!

1 like
eristic's avatar

@craigpaul That too requires the query string to contain all the $request->only()parameters.

For example:

$filters = $request->only(['primary_type', 'pricing_type']);

        $events = collect($events);



        foreach ($filters as $name => $value) {
            $events = $events->filter(function ($event) use ($name, $value) {
                    return $event[$name] === $value;
            });

        }

        return $events->all();

I need to have the query string as:

?primary_type=Exhibition&pricing_type=pay

in order to have the results appear. Otherwise I get: []

Any thoughts? I am learning so much from you in this thread.

davielee's avatar

Oh geez, I wrote that out too fast and wasn't thinking @eristic, sorry about that! I forgot to add a array_filter() around the $request->only() call, that would have fixed it, but that's ok, I thought of a better solution anyway. This one uses collections for everything instead.

$filters = collect($request->only(['primary_type', 'pricing_type']));
$events = collect($events);

$filters->filter()->each(function ($value, $name) use (&$events) {
    $events = $events
        ->filter(function ($event) use ($name, $value) {
            return $event[$name] === $value;
        })
        ->values();
});

return $events->all();

So the main differences here are that we use a Collection on the $filters array and then call ->filter() on it to get rid of any missing (aka null) values. Then we pass $events in by reference so we modify the original collection within that each callback.

That will apply only the filters that exist from the query string by the names supplied in the $request->only() call. I'm glad you're learning a lot by the way, glad to help any way I can.

Sidenote: The triple equals sign might cause problems if you're filtering by integers or floats like this cause your $value will be a string type instead of an integer or float, so keep that in mind if you need that ability.

eristic's avatar

@craigpaul Thank you so much for you help on this. I'm able to successfully create the optional filtering as you wrote above. The problem I'm running into now is the date range.

This works:

$location = $request->input('location');
        $type = $request->input('type');
        $pricing = $request->input('pricing');
$from_date = strtotime($request->input('start'));
        $to_date = strtotime($request->input('end'));

        $event = collect($events)->filter(function ($event) use ($type) {
            return $event['primary_type'] === $type;
        })->filter(function ($event) use ($pricing) {
            return $event['pricing_type'] === $pricing;
        })->filter(function ($event) use ($location) {
            return $event['primary_location'] === $location;
        })->filter(function ($event) use ($from_date, $to_date){
            $epoch = strtotime($event['start']);
            return $epoch >= $from_date && $epoch <= $to_date;
        });

        return $event->all();

But is not optional and requires all queries to be in the string. How would I combine this with your answer above? I'm banging my head against the desk at this point.

davielee's avatar

@eristic You're welcome, glad it worked. As for date range, you're probably best off just adding another filter onto the example I gave above.

$filters = collect($request->only(['primary_type', 'pricing_type']));
$events = collect($events);

$filters->filter()->each(function ($value, $name) use (&$events) {
    $events = $events
        ->filter(function ($event) use ($name, $value) {
            return $event[$name] === $value;
        })
        ->values();
});

$start = strtotime($request->input('start'));
$end = strtotime($request->input('end'));

if ($start && $end) {
    $events = $events
        ->filter(function ($event) use ($start, $end) {
            $epoch = strtotime($event['start']);

            return $epoch >= $start && $epoch <= $end;
        })
        ->values();
}

return $events->all();

Granted it's not super compact, but because it's not a strict equals, we can't really combine it super easy with the rest of the filters. It is optional though so it will work the way you want.

Please or to participate in this conversation.