Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel 5.6 - Pass additional parameters to API Resource?

A Laravel API Resource can be either a single resource or a collection. In some cases, additional parameters are required to be passed to the resource/collection from the controller. Below is a simple example demonstrating the issue using User as a single/collection resource, and a custom $apple parameter to be passed to the resource for output. The issue can be seen in the final Output (Collection) below, where for the fruit value, we get an incorrect value of banana for the first user, instead of the correct apple value (which all other users get). It works perfectly for the single output, just not the collection. See below:

Controller with UserResource (Single)

    $user = User::first();
    return new UserResource($user, $apple = true); // $apple param passed

Controller with UserResource (Collection)

    $users = User::limit(3)->get();
    return UserResource::collection($users, $apple = true); // $apple param passed

UserResource

    <?php

    namespace App\Http\Resources;
    use Illuminate\Http\Resources\Json\JsonResource;

    class UserResource extends JsonResource {
        private $apple;

        public function __construct($resource, $apple = false) {
            // Ensure we call the parent constructor
            parent::__construct($resource);
            $this->resource = $resource;
            $this->apple = $apple; // $apple param passed
        }

        public function toArray($request) {
            return [
                'id'     => (int) $this->id, 
                'name'   => $this->name,
                'fruit'  => $this->apple ? 'apple' : 'banana',
            ];
        }
    }

Output (Single)

    {
        "data": {
            "id": 1,
            "name": "Peter",
            "fruit": "apple" // correct param!
        }
    }

Output (Collection)

    {
        "data": [
            {
                "id": 1,
                "name": "Peter",
                "fruit": "banana" // INCORRECT param!
            },
            {
                "id": 2,
                "name": "Lois",
                "fruit": "apple" // correct param!
            },
            {
                "id": 3,
                "name": "Brian",
                "fruit": "apple" // correct param!
            }
        ]
    }

Please note that this is just an example, it can be any amount of random parameters (unrelated to the User collection, but must be passed for output logic), such as a single value read_at timestamp from a different table I want to pass once, and do some logic on it in the resource collection before output (like comparison to a user timestamp), or other parameters passed for additional logic if/else to be performed in the resource file in general to manipulate output of collection. How can this be done?

like image 879
Wonka Avatar asked Jun 01 '18 07:06

Wonka


4 Answers

The following approach worked for me:

UserResource

class UserResource extends Resource{

    protected $foo;

    public function foo($value){
        $this->foo = $value;
        return $this;
    }

    public function toArray($request){
        return [
            'id' => $this->id,
            'name' => $this->name,
            'foo' => $this->foo,
         ];
    }

    public static function collection($resource){
        return new UserResourceCollection($resource);
    }
}

UserCollection

class UserResourceCollection extends ResourceCollection{

    protected $foo;

    public function foo($value){
        $this->foo = $value;
        return $this;
    }

    public function toArray($request){
        return $this->collection->map(function(UserResource $resource) use($request){
            return $resource->foo($this->foo)->toArray($request);
    })->all();

        // or use HigherOrderCollectionProxy
        // return $this->collection->each->foo($this->foo)->map->toArray($request)->all()

        // or simple
        // $this->collection->each->foo($this->foo);
        // return parent::toArray($request);
    }
}

Different ways to pass the additional parameter

(new UserResource($user))->foo('bar');
(new UserResourceCollection($user))->foo('bar');

UserResource::make($user)->foo('bar');
UserResourceCollection::make($users)->foo('bar');
UserResource::collection($users)->foo('bar');
like image 199
Wonka Avatar answered Oct 19 '22 19:10

Wonka


This simple trick worked for me in Laravel :)

Controller

$user = User::find($user->id);
$user->access_token = $tokenResult->accessToken; // Add additional data
return new ProfileResource($user);

Resource

public function toArray($request)
{
    return [
        'id'            => $this->id,
        'picture'       => $this->picture,
        'first_name'    => $this->first_name,
        'last_name'     => $this->last_name,
        'active'        => $this->active,
        'access_token'  => isset($this->access_token) ? $this->access_token : '', // Additional data
    ];
}

Updated (June 2022) The same logic working for collections too

User.php (model)

public static function getUserList(Request $request)
{
    $output = [];

    $query = User::query();

    if ($request->has('search')) {
        $query->where('first_name', $request->input('search'));
        $query->orWhere('last_name', $request->input('search'));
        $query->orWhere('email', $request->input('search'));
    }

    $queryActive   = clone $query;
    $queryInactive = clone $query;

    $output = $query->paginate(config('services.PAGINATE_PER_PAGE'));

    $statistics = [
        'total'    => $query->count(),
        'inactive' => $queryInactive->where('status', 0)->count(),
        'active'   => $queryActive->where('status', 1)->count(),
    ];

    $output->statistics = $statistics;

    return $output;
}

UserController.php

public function index(Request $request): UserCollection
{
    $users = User::getUserList($request);

    return new UserCollection($users);
}

UserResource.php

namespace App\Http\Resources;

use Carbon\Carbon;
use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'first_name'                => $this->first_name,
            'last_name'                 => $this->last_name,
            'full_name'                 => $this->first_name . '' . $this->last_name,
            'email'                     => $this->email,
            'mobile'                    => $this->mobile,
            'gender'                    => $this->gender,
            'status'                    => $this->status,
            'status_title'              => $this->status ? 'Active' : 'Inactive',
            'created_at_simple'         => Carbon::parse($this->created_at)->format('Y-m-d'),
            'created_at_simple_string'  => Carbon::parse($this->created_at)->toFormattedDateString(),
            'created_at'                => Carbon::parse($this->created_at)->format('Y-m-d H:i:s'),
            'created_at_ago'            => Carbon::parse($this->created_at)->diffForHumans(),
            'created_at_string'         => Carbon::parse($this->created_at)->toDayDateTimeString(),
            'updated_at_simple'         => Carbon::parse($this->updated_at)->format('Y-m-d'),
            'updated_at_simple_string'  => Carbon::parse($this->updated_at)->toFormattedDateString(),
            'updated_at'                => Carbon::parse($this->updated_at)->format('Y-m-d H:i:s'),
            'updated_at_ago'            => Carbon::parse($this->updated_at)->diffForHumans(),
            'updated_at_string'         => Carbon::parse($this->updated_at)->toDayDateTimeString(),
            'roles'                     => RoleResource::collection($this->roles),
        ];
    }
}

UserCollection.php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class UserCollection extends ResourceCollection
{
    public function toArray($request)
    {
        return [
            'data'             => $this->collection,
            'statistics'       => [
                'filters'    => request()->all(),
                'active'     => $this->statistics['active'],
                'inactive'   => $this->statistics['inactive'],
                'total'      => $this->collection->count(),
            ],
        ];
    }
}
like image 26
zarpio Avatar answered Oct 19 '22 19:10

zarpio


This is how I made it on laravel 8.



class PatientResource extends JsonResource
{

  private static $data;
  /**
   * Transform the resource into an array.
   *
   * @param \Illuminate\Http\Request $request
   * @return array
   */
  public function toArray($request)
  {
    //access $data
    //self::$data
    return [
      'id' => $this->id,
      'first_name' => $this->first_name,
      'middle_name' => $this->middle_name,
      'last_name' => $this->last_name,
      'contact_number' => $this->contact_number
    ];
  }

  //I made custom function that returns collection type
  public static function customCollection($resource, $data): \Illuminate\Http\Resources\Json\AnonymousResourceCollection
  {
   //you can add as many params as you want.
    self::$data = $data;
    return parent::collection($resource);
  }
}

Then on my controller I called that custom function.

$data = PatientResource::customCollection($query->get(),$medicines);
like image 12
Darwin Marcelo Avatar answered Oct 19 '22 20:10

Darwin Marcelo


You can pass the extra parameters in as part of the call to the API endpoint. You can then access the parameters with the $request object (for your example) in the UserResource.

For example, if you call the endpoint from a client such a web browser, axios, etc. using something like:

http://localhost:3000/api/users?apple=true

this will make the parameter apple with a value of true available in the controller. Without any other action on your part it will then also be accessible in the toArray($request) of the UserResource. You can access it similar to:

public function toArray($request) {
      $isApple = $request->apple;

        return [
            'id'     => (int) $this->id, 
            'name'   => $this->name,
            'fruit'  => $isApple ? 'apple' : 'banana',
        ];
    }
like image 9
SnapShot Avatar answered Oct 19 '22 19:10

SnapShot