John Doe
John Doe

Reputation: 31

Laravel API Resources response grouping

UPD: Solved. I Guess (check below)

I'm trying to group my API response by specific column with Laravel Resources

Like this

{
   "data":{
      "1":[ <=== (!!) To have this groups
         {
            "id":63,
            "eve_id":95924195,
            "name":"John",
            "group":1, <== (!!!) from this column
            "is_active":1,
            "is_editable":1,
            "created_at":"2018-11-08 05:26:20",
            "updated_at":"2018-11-08 05:26:20"
         },
         {
            "id":64,
            "eve_id":95824961,
            "name":"Doe",
            "group":1,
            "is_active":1,
            "is_editable":1,
            "created_at":"2018-11-08 05:41:10",
            "updated_at":"2018-11-08 05:41:10"
         }
      ]
   }
}

My resource instance

    class SomeResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'group' => $this->group,
            'is_active' => $this->is_active,
            'is_editable' => $this->is_editable,
            'created_at' => (string) $this->created_at,
        ];
    }
}

my collection instance:

    class SomeCollection extends ResourceCollection
{
    public $collects = 'App\Http\Resources\SomeResource';


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

Using a basic eloquent method

Some::where('is_active', '1')->get()->groupBy('group');
return ['entries' => $entries];

can`t solve a problem at least if I somehow can pass these entries and iterate them at ResourceCollection

Thanks.

UPD: Okay, the issue was solved by creating new resource instance SomeGroupCollection and grouping was from the basic eloquent method

    $entries = Some::where('is_active', '1')->get()->groupBy('group');
    return new SomeGroupCollection($entries);

SomeGroupCollection

class SomeGroupCollection extends ResourceCollection
{

    public $collects = 'App\Http\Resources\SomeCollection';

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

SomeCollection

class SomeCollection extends ResourceCollection
{

    public $collects = 'App\Http\Resources\SomeResource';


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

SomeResource

class SomeResource extends JsonResource
{

    public function toArray($request)
    {

        return [
            'id' => $this->id,
            'name' => $this->name,
            'group' => $this->group,
            'is_active' => $this->is_active,
            'is_editable' => $this->is_editable,
            'created_at' => (string) $this->created_at,
        ];
    }
}

Upvotes: 3

Views: 4285

Answers (2)

Ravi Patel
Ravi Patel

Reputation: 2191

Although you can directly use the groupBy on result of collection method, you will lose any metadata about the pagination as well as any wrapping using the data key.

I solved this issue by creating a new class for this, which extends from the existing JsonResource class. Basically, it injects the groupBy logic after the resources are filtered. Just create GroupedResources.php and GroupedResourceCollection.php in App directory and enter following code in them.

App\GroupedResourceCollection

<?php

namespace App;

use Illuminate\Http\Resources\Json\AnonymousResourceCollection;

class GroupedResourceCollection extends AnonymousResourceCollection
{
    /**
     * Resolve the resource to an array.
     *
     * @param  \Illuminate\Http\Request|null  $request
     * @return array
     */
    public function resolve($request = null): array
    {
        return isset($this->groupBy)
            ? collect(parent::resolve($request))->groupBy($this->groupBy, $this->preserveKeys ?? false)->all()
            : parent::resolve($request);
    }
}

As you can see from above code parent::resolve($request) method returns the filtered data, so it is a perfect place for grouping the data.

App\GroupedResource

<?php

namespace App;

use Illuminate\Http\Resources\Json\JsonResource;

/**
 * @property boolean $preserveKeys
 * @property array $groupBy
 */
class GroupedResource extends JsonResource
{
    /**
     * Create a new grouped resource collection.
     *
     * @param  mixed  $resource
     * @return \App\GroupedResourceCollection
     */
    public static function collection($resource): GroupedResourceCollection
    {
        return tap(new GroupedResourceCollection($resource, static::class), function ($collection) {
            if (property_exists(static::class, 'preserveKeys')) {
                $collection->preserveKeys = (new static([]))->preserveKeys === true;
            }
            if (property_exists(static::class, 'groupBy')) {
                $collection->groupBy = (new static([]))->groupBy;
            }
        });
    }
}

JsonResource class returns an instance of AnonymousResourceCollection class, so to use the GroupedResourceCollection class as well as to instantiate the groupBy property on that class we have to modify the signature of the JsonResource class. We can do that by creating a new class named GroupedResource that extends from JsonResource class as shown above.

You can now use it just by creating resource as you normally do but this time instead of extending it from the JsonResource class you have to extend it from GroupedResource class. You can assign groupBy variable to group it.

App\Http\Resources\UserResource

<?php

namespace App\Http\Resources;

use App\GroupedResource;

class UserResource extends GroupedResource
{
    protected array $groupBy = ['country', 'state'];

    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'address' => $this->address,
            'state' => $this->state,
            'country' => $this->country
        ];
    }
}

You will get all the benefit of collection grouping, so you can create groupBy array as you can do it for collection groupBy method as shown here.

Upvotes: 1

zjbarg
zjbarg

Reputation: 679

This worked for me in Laravel 8, but I suspect it will work the same in pervious versions.

SomeResource::collection($models)->groupBy('group');

but without the data key.

Upvotes: 2

Related Questions