Now it's time to take care of the second part of the client's description of the Tours List: filtering and ordering.
Task Description From Client
- Users can filter (search) the results by
priceFrom,priceTo,dateFrom(from thatstartingDate) anddateTo(until thatstartingDate). - User can sort the list by
priceasc and desc. They will always be sorted, after every additional user-provided filter, bystartingDateasc.
Filtering Tours: Eloquent when()
First, let's take care of the filter.
Here's our Controller at the moment:
app/Http/Controllers/Api/V1/TourController.php:
public function index(Travel $travel){ $tours = $travel->tours() ->orderBy('starting_date') ->paginate(); return TourResource::collection($tours);}
And our goal is to process the endpoint like this:
/api/v1/travels/{slug}/tours?priceFrom=123&priceTo=456&dateFrom=2023-06-01&dateTo=2023-07-01
So, we have four possible parameters:
- priceFrom
- priceTo
- dateFrom
- dateTo
And all of them are optional, so there may be only one of them passed, or all four.
So, our goal is to build a dynamic query based on different request() parameters.
We will add a Request $request parameter, which will be auto-resolved by Laravel and contain all the GET parameters.
And then, we will use the Eloquent method when(), which has two parameters:
- a condition
- and how to transform the query in case of that condition is
true
use Illuminate\Http\Request; class TourController extends Controller{ public function index(Travel $travel, Request $request) { $tours = $travel->tours() ->when($request->priceFrom, function ($query) use ($request) { $query->where('price', '>=', $request->priceFrom * 100); }) ->when($request->priceTo, function ($query) use ($request) { $query->where('price', '<=', $request->priceTo * 100); }) ->when($request->dateFrom, function ($query) use ($request) { $query->where('starting_date', '>=', $request->dateFrom); }) ->when($request->dateTo, function ($query) use ($request) { $query->where('starting_date', '<=', $request->dateTo); }) ->orderBy('starting_date') ->paginate(); return TourResource::collection($tours); }}
You can read more about the when() method in...
FAILED Tests\Feature\ToursListTest > tour list filters by staring date correctly
Failed asserting that actual size 1 matches expected size 2 at:
If someone else is having this test failing it is because now() is creating a complete datetime string and not just YYYY-mm-dd and by the time my/your computer gets to return now() one or more seconds have passed and $earlierTour entry no longer gets included. Solution is to use variable like $earlyStartingDate = date('Y-m-d', now()) and use it throughout the test function or modifying $request->dateFrom in TourController.
We can use now()->format('Y-m-d') instead of $earlyStartingDate = date('Y-m-d', now()). I also encountered this test failure.
Encountered the same problem, solved with
now()->toDateString()