21

I have been working with laravel api resource. By default laravel provides links and meta as shown below.

"links": {
    "first": "https://demo.test/api/v2/taxes?page=1",
    "last": "https://demo.test/api/v2/taxes?page=4",
    "prev": null,
    "next": "https://demo.test/api/v2/taxes?page=2"
},
"meta": {
   "current_page": 1,
   "from": 1,
   "last_page": 4,
   "path": "https://demo.test/api/v2/taxes",
   "per_page": 2,
   "to": 2,
   "total": 8
} 

But I don't want this, insted i want something like

"pagination": {
  "total": 8,
  "count": 8,
  "per_page": 25,
  "current_page": 1,
  "total_pages": 1
}

I'm able to get this info but if I do return TaxResource::collection($taxes);, I won't get this. Even I have custom collection method

 public static function collection($resource)
    {
       $resource->pagination = [
        'total' => $resource->total(),
        'count' => $resource->count(),
        'per_page' => $resource->perPage(),
        'current_page' => $resource->currentPage(),
        'total_pages' => $resource->lastPage()
       ];
        return parent::collection($resource);
    }

It is not giving what I want. But if I reference through (TaxResource::collection($taxes))->pagination; I'm able to get that. But I want it to be returned when I do return TaxResource::collection($taxes);

vikas hemanth
  • 235
  • 1
  • 3
  • 9
  • If everyone wondering of what are the available method/properties of pagination instance, check this out: https://laravel.com/api/8.x/Illuminate/Pagination/LengthAwarePaginator.html – Dendi Handian Feb 23 '21 at 09:45

7 Answers7

32

I was interested in your question and spent some time resolving it. I guess there are a lot of work to be done to improve Eloquent: API Resources' functionality in the future.

In order to resolve it I must use Resource Collections instead of Resources:

However, if you need to customize the meta data returned with the collection, it will be necessary to define a resource collection

php artisan make:resource Tax --collection

or

php artisan make:resource TaxCollection

Route:

Route::get('/get-taxes', function () {
    $taxes = Taxes::paginate();
    return new TaxCollection($taxes);
});

TaxCollection.php:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class TaxCollection extends ResourceCollection
{        
    public function toArray($request)
    {
        return [
        'data' => $this->collection,
        'pagination' => [
            'total' => $this->total(),
            'count' => $this->count(),
            'per_page' => $this->perPage(),
            'current_page' => $this->currentPage(),
            'total_pages' => $this->lastPage()
        ],
    ];
    }  

    // Using Laravel < 5.6
    public function withResponse($request, $response)
    {
        $originalContent = $response->getOriginalContent();
        unset($originalContent['links'],$originalContent['meta']);
        $response->setData($originalContent);
    }

    // Using Laravel >= 5.6
    public function withResponse($request, $response)
    {
        $jsonResponse = json_decode($response->getContent(), true);
        unset($jsonResponse['links'],$jsonResponse['meta']);
        $response->setContent(json_encode($jsonResponse));
    }
}

This solve the problem but now there are new one: Unlike Resources I don't know how to modify toArray fields in Resource Collections, the manual shows only example with 'data' => $this->collection where we send not modified collection (Resource Collections allows us change meta data). So If we use just Resource then we can modify collection data but not meta data.

Marcin Orlowski
  • 72,056
  • 11
  • 123
  • 141
yrv16
  • 2,205
  • 1
  • 12
  • 22
  • 1
    Thank you so much, your idea works. Well I have solution for that issue also. Instead of ` 'data' => $this->collection` , use transform like so, `'data'=> $this->collection->transform(function ($data) {return[ 'id' => $data->id,..] })` . This will solve the issue but problem is with single model it gives error. Anyway Thank you so much. – vikas hemanth Jan 05 '18 at 06:36
  • You forgot to `json_encode` the `$originalContent` while setting. – ssi-anik Jan 20 '18 at 21:42
  • Thanks for the question/answer, I rewrap it after spent sometimes understanding it and get it working as what I expected: https://ask.osify.com/qa/11425 – Osify Feb 03 '20 at 07:22
12

The accepted answer did not work for me (in Laravel 5.6), but I found a better way IMHO.

Save the pagination informations in your ResourceCollection constructor and replace the Paginator resource with the underlying Collection.

TaxCollection.php:

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class TaxCollection extends ResourceCollection
{
    private $pagination;

    public function __construct($resource)
    {
        $this->pagination = [
            'total' => $resource->total(),
            'count' => $resource->count(),
            'per_page' => $resource->perPage(),
            'current_page' => $resource->currentPage(),
            'total_pages' => $resource->lastPage()
        ];

        $resource = $resource->getCollection();

        parent::__construct($resource);
    }

    public function toArray($request)
    {
        return [
            'data' => $this->collection,
            'pagination' => $this->pagination
        ];
    }
}
cho
  • 121
  • 1
  • 5
  • This works fine, but what if you want to customise the returned data (filter out something). More over you will not be able to use the same resource for single row. – vikas hemanth Apr 28 '18 at 11:58
  • 1
    Just replace `'data' => $this->collection` with `'data' => TaxResource::collection($this->collection)`. That way you can customize each item in a `TaxResource` class. – cho Apr 29 '18 at 13:46
  • please change `$this->total()` to `$resource->total()` – rkj Aug 09 '18 at 17:11
  • using laralve 5.6.25, in toArray lost `$this->collection`, so in constructor just store selected items in custom variable `$this->items =$resource = $resource->getCollection();` and in `toArray` use `$this->items` instead of `$this->collection` – Simon Pasku Sep 07 '18 at 09:23
  • This approach worked best for me. Thanks for sharing! – Tyson Nero Sep 25 '19 at 22:33
5

So I've discovered that in PHP you can actually call a grandparent function without reflection or other workarounds.

Given that TaxCollection extends ResoureCollection, which in turn extends JsonResource we can actually bypass the ResourceCollection method that handles the pagination.

class TaxCollection extends ResourceCollection
{        
    public function toArray($request)
    {
        return [
        'data' => $this->collection,
        'pagination' => [
            'total' => $this->total(),
            'count' => $this->count(),
            'per_page' => $this->perPage(),
            'current_page' => $this->currentPage(),
            'total_pages' => $this->lastPage()
        ],
    ];
    }  

    public function toResponse($request)
    {
        return JsonResource::toResponse($request);
    }
}

the toResponse method call is NOT static, but instead calling the grandparent JsonResource::toResponse method, just as parent::toResponse would call the ResourceCollection toResponse(..) instance method.

This will remove all extra pagination fields from the JSON response (links, meta, etc) and allow you to customize the response as you'd like in toArray($request)

Jake
  • 407
  • 4
  • 13
2

you could also extends JsonResource, AnonymousResourceCollection, ResourceCollection and finally PaginatedResourceResponse

Sébastien R
  • 66
  • 1
  • 4
  • Agreed and then inherit form those bases to apply changes to all other extensions of the new extended bases instead of having to put global changes into each one. – D Durham Sep 02 '18 at 02:01
0

@yrv16 Laravel 5.6 version:

public function withResponse($request, $response)
{
    $jsonResponse = json_decode($response->getContent(), true);
    unset($jsonResponse['links'],$jsonResponse['meta']);
    $response->setContent(json_encode($jsonResponse));
}
Luciano Nascimento
  • 2,600
  • 2
  • 44
  • 80
0

JsonResource class comes with an additional() method which lets you specify any additional data you’d like to be part of the response when working with a resource:

   Route::get('/get-taxes', function () {
    $taxes = Taxes::paginate();
    return new TaxCollection($s)->additional([
       'pagination' => [
          'total' => $taxes->total,
           ...
        ]
      ]);
   });
lostpaper
  • 1
  • 3
0

The most upvoted answer is not working for me (Laravel 10). I need to remove json_encode before setData in TaxCollection.php

use Illuminate\Http\Resources\Json\ResourceCollection;

class TaxCollection extends ResourceCollection
{        
    public function toArray($request)
    {
        return [
        'data' => $this->collection,
        'pagination' => [
            'total' => $this->total(),
            'count' => $this->count(),
            'per_page' => $this->perPage(),
            'current_page' => $this->currentPage(),
            'total_pages' => $this->lastPage()
        ],
    ];
    }  

    // Using Laravel < 5.6
    public function withResponse($request, $response)
    {
        $originalContent = $response->getOriginalContent();
        unset($originalContent['links'],$originalContent['meta']);
        $response->setData($originalContent);
    }

    // Using Laravel >= 5.6
    public function withResponse($request, $response)
    {
        $jsonResponse = json_decode($response->getContent(), true);
        unset($jsonResponse['links'],$jsonResponse['meta']);
        $response->setContent(json_encode($jsonResponse));
    }

    // Using Laravel 10
    public function withResponse($request, $response)
    {
        $jsonResponse = json_decode($response->getContent(), true);
        unset($jsonResponse['links'],$jsonResponse['meta']);
        $response->setContent($jsonResponse); // remove json_encode
    }
}
Makhele Sabata
  • 582
  • 6
  • 16
agungkes
  • 1
  • 1