Framework: API Resources wrapping data incorrectly with pagination (Double wrap to data)

Created on 13 Oct 2017  路  4Comments  路  Source: laravel/framework

  • Laravel Version: 5.5.14
  • PHP Version: 7.1
  • Database Driver & Version: MySQL 5.7

Description:

In docs are written

Of course, you may be wondering if this will cause your outer-most resource to wrapped in two data keys. Don't worry, Laravel will never let your resources be accidentally double-wrapped, so you don't have to be concerned about the nesting level of the resource collection you are transforming:

It is true before we don't use pagination.

Steps To Reproduce:

Source code:

AppServiceProvider:

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Resource::withoutWrapping();
    }
}

Resource:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\Resource;

class Company extends Resource
{
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request
     * @return array
     */
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'reg_number' => $this->reg_number,
            'address' => $this->address,
            'ceo' => $this->ceo,
            'created_at' => $this->created_at->format('Y-m-d\TH:i:s\Z'),
            'updated_at' => $this->updated_at->format('Y-m-d\TH:i:s\Z'),
        ];
    }
}

ResourceCollection:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class CompanyCollection extends ResourceCollection
{
    /**
     * Transform the resource collection into an array.
     *
     * @param  \Illuminate\Http\Request
     * @return array
     */
    public function toArray($request)
    {
        return ['data' => $this->collection];
    }
}

Controller:

class CompanyController extends Controller
{
    public function getCompaniesList()
    {
        $companies = Company::all();

        return new CompanyCollection($companies);
    }

    public function getCompaniesListPaginated()
    {
        $companies = Company::paginate(1);

        return new CompanyCollection($companies);
    }

    public function getCompany($id)
    {
        $company = Company::find($id);

        return new CompanyResource($company);
    }
}

Responses

  1. Action getCompany:
{
    "id": 1,
    "name": "Reinger-Lynch",
    "reg_number": "10526520",
    "address": "2828 Ambrose Coves\nHudsonberg, IA 05000",
    "ceo": "Sibyl Kuhn",
    "created_at": "2013-04-29T10:33:40Z",
    "updated_at": "2013-04-29T10:33:40Z"
}
  1. Action getCompaniesList:
{
    "data": [
        {
            "id": 1,
            "name": "Reinger-Lynch",
            "reg_number": "10526520",
            "address": "2828 Ambrose Coves\nHudsonberg, IA 05000",
            "ceo": "Sibyl Kuhn",
            "created_at": "2013-04-29T10:33:40Z", "2013-04-29T10:33:40Z"
        },
        ...
    ]
}
  1. Action getCompaniesListPaginated:
{
    "data": {
        "data": [
            {
                "id": 1,
                "name": "Reinger-Lynch",
                "reg_number": "10526520",
                "address": "2828 Ambrose Coves\nHudsonberg, IA 05000",
                "ceo": "Sibyl Kuhn",
                "created_at": "2013-04-29T10:33:40Z",
                "updated_at": "2013-04-29T10:33:40Z"
            },
            ...
        ]
    },
    "links": {
        "first": "http:\/\/sahkoukko.dev\/api\/companies?page=1",
        "last": "http:\/\/sahkoukko.dev\/api\/companies?page=10",
        "prev": null,
        "next": "http:\/\/sahkoukko.dev\/api\/companies?page=2"
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 10,
        "path": "http:\/\/sahkoukko.dev\/api\/companies",
        "per_page": 1,
        "to": 1,
        "total": 10
    }
}

When I comment out Resource::withoutWrapping(); all works fine. But this logic is incorrect. It must avoid double wrapping to data with pagination.

All 4 comments

The problem is that, when you use pagination, the resource MUST be wrapped because there is meta data. You are also forcing wrapping by using:

    public function toArray($request)
    {
        return ['data' => $this->collection];
    }

So with pagination, your collection is automatically wrapped and since you're hardcoding a wrap, then it gets double-wrapped.

From the docs

When returning paginated collections in a resource response, Laravel will wrap your resource data in a data key even if the withoutWrapping method has been called. This is because paginated responses always contain meta and links keys with information about the paginator's state

You should just return $this->collection and control wrapping with the 'wrap' method.

Then there are no any easy solution to return:

  • Single item without wrapping
  • Collection always with "data" wrapping
  • Pagination with single "data" wrapping

Sure it is. Watch Taylor's Laracon EU presentation where he covers resources in detail. He talks about each of these.

@vgladimir i have been reading the Laravel Resource code and you should create a public static property named $wrap (public static $wrap = 'results'; for example).
I hope it helps you.
I just create an issue: https://github.com/laravel/internals/issues/862

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ghost picture ghost  路  3Comments

gabriellimo picture gabriellimo  路  3Comments

shopblocks picture shopblocks  路  3Comments

felixsanz picture felixsanz  路  3Comments

PhiloNL picture PhiloNL  路  3Comments