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!
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:
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']);
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.
Then you can add the params to your resource like so:
Hope that helps