Framework: Pass additional parameter to API Resource

Created on 6 Apr 2018  路  17Comments  路  Source: laravel/framework

  • Laravel Version: 5.5
  • PHP Version: 5.7
  • Database Driver & Version:

Description:

What if my api resource class need additional parameters besides the resource itself, from outside like caller controller class? How could I pass parameter to the api resource class(both single resource and resource collection), should I declare a new Json Resource class to extends?

Any comments or solutions are welcome, Thank you in advance!

Most helpful comment

@yanhao-li This is very much possible - you just have to overload the constructor of the Resource you want to pass extra params to e.g.

class PostResource extends Resource
{
    /**
     * @var
     */
    private $foo;

    /**
     * Create a new resource instance.
     *
     * @param  mixed  $resource
     * @return void
     */
    public function __construct($resource, $foo)
    {
        // Ensure you call the parent constructor
        parent::__construct($resource);
        $this->resource = $resource;

        $this->foo = $foo;
    }

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

Then you can add the params to your resource like so:

return new PostResource($post, 'my variable');

Hope that helps

All 17 comments

Hi there,

Welcome to Laravel and we are glad to have you as part of the community.

Unfortunately this GitHub area is not a support area for general application issues. This is only for issues/bugs with the framework code itself.

Can I suggest closing your issue ticket here? Instead try asking your question on one of the many great community support areas that will likely give you a better answer more quickly:

  • Laravel Slack (https://larachat.co/)
  • Laravel.io Forum (https://laravel.io/forum)
  • Laracasts Forum (https://laracasts.com/discuss)
  • StackOverflow (http://stackoverflow.com/questions/tagged/laravel)

Thanks in advance.

@yanhao-li This is very much possible - you just have to overload the constructor of the Resource you want to pass extra params to e.g.

class PostResource extends Resource
{
    /**
     * @var
     */
    private $foo;

    /**
     * Create a new resource instance.
     *
     * @param  mixed  $resource
     * @return void
     */
    public function __construct($resource, $foo)
    {
        // Ensure you call the parent constructor
        parent::__construct($resource);
        $this->resource = $resource;

        $this->foo = $foo;
    }

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

Then you can add the params to your resource like so:

return new PostResource($post, 'my variable');

Hope that helps

@fourstacks This worked for single resource, however, how should I pass additional parameters to Post static collection method? In other words, how could I use it with resource collections? Any hints are welcomed, thank you in advance!

@yanhao-li i have some issue.

This is my approach.

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);
    }
}

```php
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);
}

}

```php
(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');

Any good idea?

@yanhao-li Did you ever figure this out?
@lokielse I see this works, but is repeating code, is there any better way anyone? Ideally passing from the constructor like @fourstacks solution but for a collection. Any better way to solve this?

Why not override the constructor in the ResourceCollection class instead of the Resource class and use Resource Collections instead (https://laravel.com/docs/5.6/eloquent-resources#generating-resources)?

For example, you could create a new ResourceCollection class

php artisan make:resource NoteCollection

The above command will create a NoteCollection for you to use in the /resources folder. Inside this class, just override the constructor with parameters you want to pass in:

class NoteCollection extends ResourceCollection
{
    protected $user;
    /**
     * Create a new resource instance.
     *
     * @param  mixed  $resource
     * @return void
     */
    public function __construct($resource, User $user = null)
    {
        $this->user = $user;

        parent::__construct($resource);
    }
    /**
     * Transform the resource collection into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return [
            'data' => $this->collection->map(function($n){
                return [
                    'note' => $n->note,
                    'public' => $this->when($this->user->isAwesome(),'secret'),
                    'created_at' => $n->created_at->timestamp,
                    'updated_at' => $n->updated_at->timestamp
                ];
            })
        ];
    }
}

Why not override the constructor in the ResourceCollection class instead of the Resource class and use Resource Collections instead (https://laravel.com/docs/5.6/eloquent-resources#generating-resources)?

For example, you could create a new ResourceCollection class

php artisan make:resource NoteCollection

The above command will create a NoteCollection for you to use in the /resources folder. Inside this class, just override the constructor with parameters you want to pass in:

class NoteCollection extends ResourceCollection
{
    protected $user;
    /**
     * Create a new resource instance.
     *
     * @param  mixed  $resource
     * @return void
     */
    public function __construct($resource, User $user = null)
    {
        $this->user = $user;

        parent::__construct($resource);
    }
    /**
     * Transform the resource collection into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return [
            'data' => $this->collection->map(function($n){
                return [
                    'note' => $n->note,
                    'public' => $this->when($this->user->isAwesome(),'secret'),
                    'created_at' => $n->created_at->timestamp,
                    'updated_at' => $n->updated_at->timestamp
                ];
            })
        ];
    }
}

I tried this with typehinting to inject a service and it doesn't work. Error is it expects two arguments and only 1 given. How to inject a service into a Resource collection?

@yanhao-li Did you ever figure this out?
@lokielse I see this works, but is repeating code, is there any better way anyone? Ideally passing from the constructor like @fourstacks solution but for a collection. Any better way to solve this?

did you solve this somehow?

@Almusamim Nope, just went with @lokielse solution as it worked and it was taking too long to figure out another way.

I created a pull request that adds a function to pass extra data to an API resource: https://github.com/laravel/framework/pull/28358

I am using another solution. Its definitely not the best one but since taylor already declined your pull request i would like to give my answer to this problem.

I extended the JsonResource and added a static variable named using:

<?php

namespace App\Responses;

use Illuminate\Http\Resources\Json\JsonResource as BaseResource;

class JsonResource extends BaseResource
{
    protected static $using = [];

    public static function using($using = [])
    {
        static::$using = $using;
    }
}

Now if you use this JsonResource in your Resource you may set this variable by calling:

MyResource::using(['my_extra_data' => 123]);
MyResource::collection($resources);

and in your Resource:

$this->merge(static::$using)

Hi @alkin ,
your solution is good but it can only be applied with 1 fixed variable, if each resource returned in Resource::collection needs a variable containing different values, your method cannot be used.
Can you fix it?

This is my approach.

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);
    }
}
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);
    }
}
(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');

Any good idea?

thanks, your solution works with me

You can use this method.

<?php

namespace App\Http\Resources;

use App\Http\Resources\Child as ChildResource;
use App\Http\Resources\Event as EventResource;
use Illuminate\Http\Resources\Json\JsonResource;

class School extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'events' => $this->when(request()->get('events'), function () {
                return EventResource::collection($this->events);
            }),
            'children' => $this->when(request()->get('children'), function () {
                return ChildResource::collection($this->children);
            }),
        ];
    }
}

what about "hacking" the request:
$request->request->add(['foo' => 'foovalue']);

This works for me:

    $collection = new UserCollection($users);
    return $collection->additional(['some-key' => 'some-value']);
Was this page helpful?
0 / 5 - 0 ratings

Related issues

JamborJan picture JamborJan  路  3Comments

jackmu95 picture jackmu95  路  3Comments

RomainSauvaire picture RomainSauvaire  路  3Comments

SachinAgarwal1337 picture SachinAgarwal1337  路  3Comments

klimentLambevski picture klimentLambevski  路  3Comments