How to avoid infinite loop when nesting api resources in Laravel? - laravel

I have two models: Company and Representative. Representative belongsTo Company, Company hasMany Representative.
Also I have two corresponding resources.
Company resource:
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'representatives' => RepresentativeResource::collection($this->representatives)
];
}
Representative resource:
public function toArray($request)
{
return [
'id' => $this->id,
'company' => $this->company ? new CompanyResource($this->company) : null
];
}
What I want to accomplish is when I get companies I want to get theirs representatives. When I get representative I want to get information about company.
What happens is infinite loop: their include each other infinitely.
So, how could it be fixed?

Did you try to use whenLoaded? It's documented here and I think it fits your needs.
With your code, you shoud have something like this:
class CompanyResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'representatives' => RepresentativeResource::collection(
$this->whenLoaded('representatives')
)
];
}
}
class RepresentativeResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'company' => new CompanyResource(
$this->whenLoaded('company')
)
];
}
}
In your controller then, you'll have to load the relationship with your model.
new RepresentativeResource(Representative::with('company')->first());

Related

Laravel Resource collection showing null field

I'm developing an API with Laravel. In one of the endpoint I'm accessing, some fields are showing a null value, but it should have some information.
Note the "addicionais_descricao" and "valor" fields, both always come with null values when I include them in the attributeitems array, but if I leave it at the initial level, the data is presented, but it doesn't solve my case, because I need this information with the attribute items:
enter image description here
This is where the endpoint calls, I make the query in the "Attribute" table, which has a relationship with the "Attributeitems" table, while the "attributeitems" table is linked to "Attribute" and "product".
public function show($id)
{
$atributos = Atributo::query('atributo')
->select(
'atributo.id',
'atributo.atrdescricao',
'atributoitens.atributo_id',
'atributoitens.produto_id',
'produto.prodescricao',
'produto.provalor'
)
->leftJoin('atributoitens', 'atributo.id', '=', 'atributoitens.atributo_id')
->leftJoin('produto', 'produto.id', '=', 'atributoitens.produto_id')
->where('atributo.id', '=', $id)
->get()->unique('id');
return AtributoResource::collection($atributos);
}
Resource Atributo:
public function toArray($request)
{
return [
'id' => $this->id,
'descricao' => $this->atrdescricao,
'atributoitens' => AtributoitensResource::collection($this->atributoitens),
];
}
Resource Atributo Itens:
public function toArray($request)
{
return [
'id' => $this->id,
'atributo' => $this->atributo_id,
'produtos' => $this->produto_id,
'adicionais_descricao' => $this->prodescricao,
'valor' => $this->provalor
];
}
What is the correct procedure for this situation?
Take this example as a reference :
Controller
$data = $shop->products()
->whereStatus(true)
->where('product_shop.active', true)
->where('product_shop.quantity', '>=', $this->min_product_qty)
->paginate(50);
return (new ProductCollection($data))
->response()
->setStatusCode(200);
ProductCollection
public function toArray($request)
{
return [
'data' => $this->collection
->map(function($product) use ($request) {
return (new ProductResource($product))->toArray($request);
}),
'brand' => $this->when($request->brand, $request->brand)
];
}
ProductResource
public function toArray($request)
{
return [
'type' => 'product',
'id' => (string) $this->id,
'attributes' => [
'uuid' => $this->uuid,
'name' => $this->name,
'slug' => $this->slug,
'description' => $this->description,
'thumb_path' => $this->thumb_path,
'cover_path' => $this->cover_path,
],
'relationships' => [
'brand' => $this->brand
]
];
}
Something like this should help you do what you want. I cant exactly do it for you. by the way why you are not using Eloquent, something like
Attribute::where(...)->with(['relation_1', 'products'])->get();
public function toArray($request)
{
return [
'id' => $this->id,
'attributes' => [...],
'products' => $this->collection
->map(function($this->product) use ($request) {
return (new ProductResource($product))->toArray($request);
}),
];
}

Laravel JetStream get HasRole of user in resource method

I am using Laravel Jetstream teams feature and wants to get list of all team members with their role.
following method from JetStream can return role of user
public function teamRole($team)
{
if ($this->ownsTeam($team)) {
return new OwnerRole;
}
if (! $this->belongsToTeam($team)) {
return;
}
return Jetstream::findRole($team->users->where(
'id', $this->id
)->first()->membership->role);
}
When user hits route /teams/{team} I would like to response with all team members name along with their role in team so I have created a TeamResource like following.
class Team extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'user_id' => $this->user_id,
'owner_name' => $this->owner->name,
'owner_email' => $this->owner->email,
'name' => $this->name,
'personal_team' => $this->personal_team,
'users' => TeamMemberResource::collection($this->allUsers()),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}
Following is my TeamMemberResource which is User model
class TeamMember extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'first_name' => $this->first_name,
'last_name' => $this->last_name,
'role' => //How do I get user's role in this team
'name' => $this->name,
'email' => $this->email,
'active' => $this->active,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}
Relationship is User has many Team.
TeamUser table has the column role.
How do I access role of user in given team?
Thank you
The neatest way to do this is to define a separate model for a user within a specific team. Then you don't have to figure out which team you're dealing with. That's probably out of scope for this question, though.
Assuming you want to achieve this using the default models, you need to figure out how to tell the "team member" collection which team it's dealing with.
Start by defining a separate TeamMemberCollection resource. Override the default ResourceCollection constructor, and pass the team as a second argument.
class TeamMemberCollection extends ResourceCollection
{
public $team;
public function __construct($collection, $team)
{
$this->team = $team;
parent::__construct($collection);
}
}
Then update the TeamMember::toArray method to accept a team, and the TeamMemberCollection::toArray method to pass the team.
class TeamMember extends JsonResource
{
public function toArray($request, $team)
{
return [
// Other properties...
'role' => $this->teamRole($team),
];
}
}
class TeamMemberCollection extends ResourceCollection
{
public function toArray($request)
{
return $this->collection
->map
->toArray($request, $this->team)
->all();
}
}
Finally, don't forget to update the Team resource to pass the team to the TeamMemberCollection constructor.
class Team extends JsonResource
{
public function toArray($request)
{
return [
// Other properties...
'users' => new TeamMemberCollection($this->allUsers(), $this),
];
}
}

How to retrieve data with many to many relationship in Laravel resource

I am using Laravel 7.
I have three table with many to many relationship. The tables are users, task_user and tasks.
The models have the following relationships:
Task.php:
public function users()
{
return $this->belongsToMany('App\User')
->withPivot('user_id', 'task_id')
->withTimestamps();
}
TaskUser.php:
public function task()
{
return $this->belongsTo('App\Task');
}
public function user()
{
return $this->belongsTo('App\User');
}
User.php:
public function tasks()
{
return $this->belongsToMany('App\Task')
->withTimestamps();
}
This is my controller method:
public function get_delayed_tasks() {
$tasks = Task::select('id', 'name', 'description', 'deadline', 'closed_at', 'status_id', 'project_id')
->whereRaw('closed_at > deadline')
->get();
return TaskResource::collection($tasks);
}
This is my resource:
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'description' => $this->description,
'deadline' => $this->deadline,
'closed_at' => $this->deadline,
'status' => $this->status->name,
'project' => $this->project->name,
'devs' => $this->relationLoaded('users')
? $this->users->pluck('users.name')->unique()->all()
: [], //problem
];
}
Unfortunately in the key dev in the resource I receive always [], even if I should receive an array of names.
How can I retrieve all the users that have worked with the task?
You have to load the relationship to use it within the resource
Controller
public function get_delayed_tasks() {
$tasks = Task::with(['users']) // <-- load relationship
->select('id', 'name', 'description', 'deadline', 'closed_at', 'status_id', 'project_id')
->whereRaw('closed_at > deadline')
->get();
return TaskResource::collection($tasks);
}
Resource
'devs' => $this->whenLoaded(
'users',
$this->users->pluck('name')->unique()->all(),
[]
)
Advanced modification for the Resource, if you decide to create a UserResource to get more data:
'devs' => UserResource::collection($this->whenLoaded('users'))

Laravel API Resource returned Undefined property: stdClass::$book",

I have a pivot table Book_Category which store the relationship between book table and category table.
In my Book model I have this
public function categories()
{
return $this->belongsToMany(Category::class);
}
In my Category Model` I have this
public function books()
{
return $this->belongsToMany(Book::class);
}
I don't think I need a model for Book_Category since its a pivot table.
But now I need to create an API Resource. I am trying to return a Book of a particular Category
So I do this this
public function singlepage(Request $request,$book)
$relatedCategory = BookCatResource::collection(DB::table('book_category')
->where('category_id', $request->category_id)->get());
I am using query builder because I don't have a model
In my resource, I have this
public function toArray($request)
{
return [
'book_id' => new BookResource($this->book),
'category_id' => $this->category_id
];
}
But it returned error
Undefined property: stdClass::$book",
In your scenario, you do not need to use the model for pivot table,
what you can do is that
Route::get('/', function () {
return CategoryResource::collection(Category::where('id', 1)->get());
});
CategoryResource.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class CategoryResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->title,
'books' => BookResource::collection($this->books),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}
BookResource.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class BookResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}

How to use same Laravel Resource for different API response

i want to make two different functions on resource so that i can get a two different response .i.e i want the resource to return data without image and with image.
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'area_code' => $this->area_code
];
}
public function toArrayWithImages($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'area_code' => $this->area_code,
'image' => $this->image
];
}
this is what i tried but dont know how to point to the second function 'toArrayWithImages' . can someone explain me this ?
This is my controller ..
public function getAllBusinessAreas()
{
try {
$areas = Area::orderBy('id', 'desc')->get();
return BusinessAreaResource::collection($areas);
} catch (Exception $e) {
return sendErrorResponse('Error fetching Business Areas', true, 500);
}
}
what it does is by default it hits the toArray function i want to be specific which function to hit from controller. is it possible to do it ?
So basically what i am doing is adding an additional parameter to your request, you can use a flash session variable as well if it is not possible to attach extra params to your request, to filter weather it goes to the function which returns image or data without image.
public function toArray($request)
{
if($request->with_image)
{
this::toArrayWithImages($request->except('with_image'));
}
else
{
this::toArrayWithoutImages($request->except('with_image'));
}
}
public function toArrayWithoutImages($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'area_code' => $this->area_code
];
}
public function toArrayWithImages($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'area_code' => $this->area_code,
'image' => $this->image
];
}

Resources