Brotzka
Brotzka

Reputation: 2785

How to sort Laravels API-Resource

I'm retrieving a list of vehicles from the api-backend of my app using VueJS with Axios on frontend and Laravels API-Resources on the backend. I use local dynamic scopes on the Vehicle-model in order to accomplish the filtering.This all works fine.

Now I want to sort the result before it is sent to the client. Sorting the result before passing it to the VehicleCollectionResource results in an error, that several functions are not available in my VehicleResourceCollection (e.g. $this->total() or $this->count()).

Passing the the query-result to the result as shown below, the ResourceCollection receives a valid paginated collection (which is unsorted). Trying to sort the collection within the CollectionResource only sorts small part (one "page") of the whole collection.

I'm not able to make the sorting directly on the database because some of the sort-parameters require additional calculating or information from other models.

So how can I query entries from my db, sort them and then paginate them so that other the next? Do I have to implement my own pagination-logic?

My toArray-function within VehicleCollectionResource:

public function toArray($request)
{
    $sortParameters = $this->sortParameters;
    $collection = $this->collection->sortBy(function ($vehicle) use ($sortParameters) {
        return $this->getSortingAttribute($vehicle, $sortParameters);
    })->toArray();

    return [
        'data' => $collection,
        'links' => [
            'self' => 'link-value'
        ],
        'pagination' => [
            'total' => $this->total(),
            'count' => $this->count(),
            'per_page' => 5,
            'current_page' => $this->currentPage(),
            'total_pages' => $this->lastPage()
        ]
    ];
}

My ApiVehicleController (receiving the request):

public function filter(Request $request)
{
    $query = Vehicle::available();

    // chain scopes to the query
    if ($request->has('producer') && $request->get('producer') !== null) {
        $query = $query->producer([$request->get('producer')]);
    }

    // other scopes ...

    $sortParameters = [
        'sortAfter' => $request->get('sort') ?? 'priceAsc',
        'mileage' => Mileage::where('id', $request->get('mileage'))->first() ?? NULL,
        'months' => Month::where('id', $request->get('duration'))->first() ?? NULL,
        'location' => $request->get('location')
    ];

    // Pass result to the CollectionResource
    return new VehicleCollectionResource($query->paginate(5), $sortParameters);
}

Heading over to sorting

Upvotes: 2

Views: 5959

Answers (2)

Adem Tepe
Adem Tepe

Reputation: 768

I solved my problem like this:

$activityResourceCollection = ActivityResource::collection($activities);
$activities = $activityResourceCollection->toArray($request);
$activities = collect($activities)->sortBy('distanceInMeters');

distanceInMeters is calculated and added in the resource.

Upvotes: 0

gbalduzzi
gbalduzzi

Reputation: 10176

The fact is:

  • You can't sort it after the pagination, because at that point you only have one page of data and not the whole collection

  • You can't use functions such as count() or total() before you execute the query.

As a consequence, there are only 2 valid possibilities:

  1. You use orderBy() and you order your collection in the SQL query (even using DB::raw() to build complex conditions). SQL is super efficient and almost everything could be accomplished using it, but it may not been trivial to build the proper query.
  2. If you can't use SQL orderBy, you can't use the paginate() utility. Instead of using it, you get the whole collection ($query->get()), you sort it and then you paginate it manually. A useful method on the collection would be forPage.

Upvotes: 3

Related Questions