Filtering via Query Strings0:00
We need to provide the ability for clients to filter results, and, of course, the way that we can do that is through the query string. And we can have query string parameters that map directly to the attributes on our Ticket. So for example, if we want just the completed tickets, we could use the status query string parameter, set the value to C, and that would give us our completed tickets. If we wanted the title or the created_at, you know, any of the attributes that our Ticket has, we could filter based upon that by just using the query string. We could also do something like this, to where if we wanted both the completed and the canceled tickets, then we could do that. It adds flexibility to our API, but, of course, with flexibility comes complexity.
tickets, then we could do that. It adds flexibility to our API, but, of course, with flexibility comes complexity. But it's easy enough to do, so we'll do that. Now, the most straightforward way that we can implement this is to just check to see if we have those values in the query string, and if so, then we can make that stuff work. But no, we don't want to do that because that is going to make our controllers very, very messy. And I don't even like this code that we have as far as including the author information. I would prefer to just have a single line of code so that we could do something like this, to where we would use our ticket resource, and we would call a filter method on our model,
Designing a Filter API1:24
I would prefer to just have a single line of code so that we could do something like this, to where we would use our Ticket resource, and we would call a filter method on our model, and then we could pass in the filters. So this filters would have all of the query string parameter stuff, so that if it's a status, or if it's the title, or if it's the status and the title, all of that stuff would be here inside of the filters that we would pass to this filter method. And then we could just paginate it from there, and that would be it. All of the filtering, all of the including stuff would just be automatically done. And we could say that those filters will come from this TicketFilter object that we would receive as an argument to the index method.
And we could say that those filters will come from this ticketFilter object that we would receive as an argument to the index method. And we could even do something like this, to where this ticketFilter class would have methods that map directly to our query string parameters, so that we would have this status query string parameter that would actually effectively call a status method, and it would work with whatever value was set for that query string parameter. So there's a lot of ways that we could do that, and I actually like that. So let's do this. Let's create this ticketFilter class. I'm going to do this inside of the HTTP folder, and we'll create a new folder called filters.
Creating TicketFilter Class2:41
Let's create this TicketFilter class. I'm going to do this inside of the Http folder, and we'll create a new folder called filters. We need to version this with V1, and then that was ticketfilter.php. The namespace will be app\Http\filters\v1, and then we will have our class of TicketFilters, so that here we would have the status method, we'll have the value passed to it, and then we could do something like this. We could say that we would have access to the query builder, and we would say where the status is the provided value. This doesn't take into account multiple status values, which we will address that later, but for right now, let's just make this work.
This doesn't take into account multiple status values, which we will address that later, but for right now, let's just make this work. So this means that we need this builder, but we can get this builder from here, this locally scoped filter method. So let's go to our Ticket model, and let's add that method. So we'll have public function scopeFilter, and we will get the queryBuilder as the first argument here, and then from here, we'll get the ticketFilter, and then we could say that our filters is going to have an apply method, and that's how we will supply the builder to our ticketFilter. But you know, we don't necessarily want to use ticketFilter here, because this filtering
Building a Base QueryFilter4:01
to our ticket filter. But you know, we don't necessarily want to use ticket filter here, because this filtering functionality is something that we would want to use for our tickets, maybe for our users, for really any resource that we would want to provide filtering capabilities for. So really, we should have a base class, which we could just call QueryFilter. So let's go ahead and let's create that as well. So of course, this needs to be inside of the same folder as our ticket filter, but we will have that. Let's go ahead and add the namespace, HTTP\Filters\V1. Then we will have our QueryFilter class, but this needs to be abstract, because we
Let's go ahead and add the namespace, HTTP filters V1. Then we will have our QueryFilter class, but this needs to be abstract, because we don't want to create just a QueryFilter object, we want to use one of our specific versions like TicketFilter. Let's go ahead and let's extend that QueryFilter now that we have that. So our QueryFilter, we know that we need the builder. So let's have protected $builder. Then we will have that public function apply method where we will get the builder supplied. And so here we will simply set $this->builder to the $builder argument, and then we will return $builder.
And so here we will simply set this builder to the builder argument, and then we will return builder. And then in between these two statements is where the magic is going to happen. Because we could say that we are going to iterate over the request, and we could just do all here as key and value. So the keys are going to be the query stream parameters, the values are of course going to be the values for those parameters. And then we could say that if the method exists on this object, and the method name would be the key, the query stream parameter, then we will simply just call that method and then pass in the value.
be the key, the query stream parameter, then we will simply just call that method and then pass in the value. The only thing is that we need access to the request here. So let's go ahead and let's add that request there. We can get this from the constructor. So we could say that we will get the request there, and then we will set this request equal to that request object. So that should work. I mean, we have all of the plumbing done. We do need to add use statements here.
I mean, we have all of the plumbing done. We do need to add use statements here. So let's do that for inside of our Ticket. We also need that inside of our Controller, but that should get us done. Let's go to Postman and let's get just the tickets that are completed. So we should see status is C. That's great. Status is C. Status is C. That's perfect. That's awesome. I love it. So that works.
Adding Include Relationships6:35
I love it. So that works. Now let's include, because we want to have that functionality as well, the User. Now of course we don't have that yet, but we could go back to our ticket filter and we could simply add that include method to where then we would simply return the builder with and then that provided value. So now we should get not only the completed tickets, but we should get the User information there. And sure enough, we do. Let's make sure I'm getting the status of C. Yes, that's working just fine.
And sure enough, we do. Let's make sure I'm getting the status of C. Yes, that's working just fine. Of course, however, we didn't say User, did we? We said include Author, and that's going to make us make a few changes. But really, you know, all we would need to do is change User to Author here. We would specify the userId as the foreign key. We do need to go to our Ticket resource because we also said to load the User when it's loaded. So here we will say Author. So with that done, let's try that now. We should see the same results.
Namespacing Filters in URLs7:41
So with that done, let's try that now. We should see the same results. We have includes. Okay, so that's great. The only thing is now our query stream parameters are a little muddied because we have status, which is of course a filter, and then we have include, which isn't necessarily a filter. This is just adding extra information to the payload. So I want to be a little more explicit as to what is and what isn't a filter. So for example, for our status, I would like to do something like this, to where we have an actual filter query stream parameter, and it would look like an array to where we would
So for example, for our status, I would like to do something like this, to where we have an actual filter query stream parameter, and it would look like an array to where we would be setting the filter array with the key of status equals C. And I know that it seems like we are adding a lot of complexity, and we are making things more complex, but a lot of that is being handled automatically for us because what this is actually going to do is something like this. We will have an array of our query stream parameters. So we will have one that has a key of include, which would be author. Then we would have another that would be filter. But this would be an array where we would have key value pairs.
Then we would have another that would be filter. But this would be an array where we would have key value pairs. So we would have status would be C. Then if we wanted to also have a filter for the title, then we would have the title filter value there. If we also wanted to filter based upon the created_at field, then we could do that just fine. So it's going to organize it so that, yes, we have our query string parameters, but for our filter, it's going to be an array. So we can essentially do the same thing that we did inside of queryFilter, where we iterated over all of these.
So we can essentially do the same thing that we did inside of queryFilter, where we iterated over all of these. So this is iterating over all of our query string parameters. So we could just add a protected method, which we could call filter, because we have a filter query string parameter, and we know that it is getting an array. So then we could just essentially do this same thing, except that instead of iterating over all of the query string parameters, we just iterate over everything inside of this array. We check if the method exists, and if so, then great. If not, then we don't care.
We check if the method exists, and if so, then great. If not, then we don't care. So we will return builder there. So that means that our apply method is going to iterate over this array. It's going to look for an include method on our filter. It's going to find it, and it's going to include the author. It's then going to look for a method called filter. It's going to find that right inside of our query filter, and then that is going to iterate over this array of key value pairs. So we should be good to go.
over this array of key value pairs. So we should be good to go. It's a simple change, but it's going to make our URL much more clear as to what is and what isn't a filter. So with that change, we should be able to send this. We should get the same result so that we are getting the completed tickets and only the completed tickets, and we are including the User information. So that's all well and good. Let's go back to our ticketFilter and let's finish implementing all of these things. So we want to be able to include multiple statuses so that if we wanted the completed
Implementing Advanced Filters10:43
Let's go back to our ticket filter and let's finish implementing all of these things. So we want to be able to include multiple statuses so that if we wanted the completed and the canceled, we would just separate those status types or those status codes with a comma, which means we could do something like this where we are going to have a like string, not a like string. This is an in query, isn't it? So we're going to change this to whereIn the status, and since our value is now a comma separated value, we can call explode and we will explode this string into an array based upon that comma, and that should give us the ability to find the tickets in the specified status codes.
upon that comma, and that should give us the ability to find the tickets in the specified status codes. So now let's do the title where we will have our value, but our title is a little bit different because we don't want to just say that the client has to know the full title. That really doesn't make sense. We should be able to allow them to specify some wildcards. So let's say that we want this E-U-M. I'm not going to try to pronounce that. I haven't had to do any Latin in a very, very long time, but let's say that for our filter, as long as it contains those three characters, we want that ticket in the list.
I haven't had to do any Latin in a very, very long time, but let's say that for our filter, as long as it contains those three characters, we want that ticket in the list. So we can very easily do that. All we would need is to simply replace the * signs with % signs. So we can call string_replace. We're changing the * to the % signs because that's what we need in order for a like query. And then we will return this builder where the title is like the like_string. So that would work there. We could do something similar to the status for the created_at.
So that would work there. We could do something similar to the status for the created_at. So we could say that the client could provide a single date or they could provide a comma separated list of dates for a range. So we could do something like this to where we will explode based upon a comma, the value. And if we have more than one date, then we know that we have a range that we need to filter. So we will return this builder whereBetween and we want the created_at field between the provided dates. However, if we just have one date, then we just need to return this builder where date and then we specify created_at is the provided date.
However, if we just have one date, then we just need to return this builder where date and then we specify created_at is the provided date. And that should work there. And since we have that, we can go ahead and we can also implement the updated_at. I'm putting these in alphabetical order just because that's just me. So we have our updated_at. We need to change the field here. So that is updated_at and that is updated_at. But everything else should be fine. So let's try the title filter.
But everything else should be fine. So let's try the title filter. We should get just the completed and canceled. So the title there, the status that looks like that works. Title Status. Yeah, we are good there. OK, so let's look at the dates. They probably have the same date since these were all generated on the same date. That's a little difficult. So let's change this to id.
That's a little difficult. So let's change this to id. So, OK, we have that was created on the second. I tell you what, let's just do this. Let's get rid of all of the other filters and let's just filter the created_at. And we want the date of 2024-02-01. And we should have one or two there. And it looks like we do. So that works fine. Let's do a range of 2024-02-03, 2024-02-05.
So that works fine. Let's do a range of 2024-02-03, 2024-02-05. That should give us one or two. But we see that we have a created_at date of the third, of the fourth. And great, that worked fine as well. So we have our basic filters all set up and ready to go. We are able to filter all of the attributes on our Ticket. We are also able to include the author information. And we are able to do all of that from our controller with one line of code. So now the question, at least to me,
Filtering by Related Users15:07
And we are able to do all of that from our controller with one line of code. So now the question, at least to me, becomes how do we filter the tickets based upon the user information? So let's say, for example, we wanted the tickets that have a status of completed, but we only want those completed tickets that were submitted by the user with an id of five. Well, we could add more filters here based upon the user information. But we also need to think about this, because if we have the id of the user, we really shouldn't be going through the tickets. We should be going through the users. So if we wanted the tickets by the user with an id of five, we should start there.
We should be going through the users. So if we wanted the tickets by the user with an id of five, we should start there. And yes, we can include the tickets, but I think it would be great if we could also have relationships or have just tickets. I think tickets is going to be fine. That would give us the tickets that were submitted by that user, and then we could filter it from there. And we will look at how to do that in the next episode.
