I've got a Product and a Category models:
class Product extends BaseModel
{
use Uuid;
protected $fillable = [
'barcode',
'name',
'sku',
'description',
'type',
'category_id',
'wholesale_price',
'retail_price',
'base_picture',
'current_stock_level',
'active',
];
public function category(): BelongsTo
{
return $this->belongsTo(Category::class, 'category_id');
}
class Category extends BaseModel
{
protected $fillable = [
'name',
'parent',
'description',
'image',
];
public function product(): HasMany
{
return $this->hasMany(Product::class, 'category_id');
}
In my controller, I'm retrieveing all products and wanted to return the category object the product belongs to in the response, so I'm doing:
class ProductsController extends Controller
{
public function index(): AnonymousResourceCollection
{
$products = Product::all();
return ProductsResource::collection($products->loadMissing('category'));
}
and my resource looks like:
class ProductsResource extends JsonResource
{
public function toArray($request) : array
{
return [
'id' => $this->id,
'type' => 'products',
'attributes' => [
'barcode' => $this->barcode,
'name' => $this->name,
'slug' => $this->slug,
'sku' => $this->sku,
'description' => $this->description,
'type' => $this->type,
// todo return category object?
'category' => new CategoriesResource($this->whenLoaded('category_id')),
'wholesale_price' => $this->wholesale_price,
'retail_price' => $this->retail_price,
'base_picture' => $this->base_picture,
'current_stock_level' => $this->current_stock_level,
'active' => $this->active,
]
];
}
}
but the response I'm getting is:
{
"data": [
{
"id": "a2102c4c-c14a-4d16-af28-e218bcc4fe39",
"type": "products",
"attributes": {
"barcode": "1010101010101",
"name": "phione",
"slug": "phione",
"sku": "w2e2r2",
"description": null,
"type": "services",
"wholesale_price": 54,
"retail_price": 34,
"base_picture": null,
"current_stock_level": 0,
"active": 1
}
}
]
}
I tried loading the relationship differently:
public function index(): AnonymousResourceCollection
{
$products = Product::with('category')->get();
return ProductsResource::collection($products);
}
but the result is the same.
It seems that the relationship is well established because if I run:
$product = Product::first();
dd($product->category);
I can see the category the product belongs to:
#attributes: array:8 [▼
"id" => 2
"name" => "Paper"
"slug" => "paper"
"parent" => 1
"description" => null
"image" => null
"created_at" => "2022-09-20 02:03:05"
"updated_at" => "2022-09-20 02:03:05"
]
what am I missing?
In controller load category relation with eager loading
$products = Product::with('category');
return ProductsResource::collection($products);
and in the ProductsResource file load, the relation category, not category_id
'category' => CategoriesResource::collection($this->whenLoaded('category')),
You don't need to use a collection here. Simply do
$products = Product::with('category')->get();
return response()->json($products);
Related
I am trying to build a resource collection with a second resource based on a hasManyThrough relationship. When i call the actual relationship on the main resource it does provide me the correct data. Whenever i pass the data to another resource and use the whenLoaded() method, it doesn't chain the data to the main collection.
The model:
public function fields()
{
return $this->hasManyThrough(Field::class, FieldValue::class, 'component_id', 'id', 'id', 'field_id');
}
The controller:
/**
* #return JsonResponse
*/
public function index()
{
$components = Component::with(['fields'])->get();
return ComponentValueResource::collection($components)->response();
}
ComponentValueResource class:
public function toArray($request)
{
return [
'fields' => FieldResource::collection($this->whenLoaded('fields')),
];
}
FieldResource class:
public function toArray($request)
{
$fields = $this->whenLoaded('fields');
return [
'fields' => new ComponentValueResource($fields),
];
}
Output of dd(FieldResource::collection($this->whenLoaded('fields')));
#original: array:9 [
"id" => 1
"base_id" => 1
"component_id" => null
"identifier" => "text input"
"type" => "text"
"position" => 1
"created_at" => "2021-08-22 19:44:23"
"updated_at" => "2021-08-22 19:44:23"
"laravel_through_key" => 6
]
Output from the resource:
{
"data": [
{
"id": 6,
"fields": [
[]
]
},
I have resource where i get product data trough third table but having hard time make relationships work on models so it return empty array.
Logic
Product has many barcodes
Barcodes can have (belongsTo) damage
In damage we get product trough barcode table (we store barcode_id)
I also included fillable part of each column so you can see columns in database.
Code
Product model
class Product extends Model
{
protected $fillable = [
'name', 'slug', 'stock', 'cover', 'description', 'sku', 'price', 'discount',
];
public function barcodes()
{
return $this->hasMany(Barcode::class);
}
}
Barcode model
class Barcode extends Model
{
protected $fillable = [
'product_id', 'sku', 'serial_number', 'price', 'discount',
];
public function product()
{
return $this->belongsTo(Product::class);
}
public function damages()
{
return $this->hasMany(DamageProduct::class);
}
}
DamageProduct model
class DamageProduct extends Model
{
protected $fillable = [
'outlet_id', 'user_id', 'barcode_id', 'description',
];
public function barcode()
{
return $this->belongsTo(Barcode::class);
}
public function user()
{
return $this->belongsTo(User::class, 'user_id', 'id');
}
}
DamageProductsResource resource
class DamageProductsResource extends JsonResource
{
public function toArray($request)
{
$arrayData = [
'id' => $this->id,
'outlet' => new OutletsResource($this->whenLoaded('outlet')),
'user' => new usersResource($this->whenLoaded('user')),
'barcode' => new BarcodeResource($this->whenLoaded('barcode')),
'description' => $this->description,
];
return $arrayData;
}
}
Result
Any idea?
Update
In case you need to see how BarcodeResource resource looks like here it is:
public function toArray($request)
{
$arrayNull = [
'id' => $this->id,
'product' => new ProductsResource($this->whenLoaded('product')),
'sku' => $this->sku,
'serial_number' => $this->serial_number ? (Int) $this->serial_number : null,
'price' => (Int) $this->price,
'discount' => $this->discount ? (Int) $this->discount : null,
];
}
I would say you simply forgot the return statement in your BarcodeResource
public function toArray($request)
{
$arrayNull = [
'id' => $this->id,
'product' => new ProductsResource($this->whenLoaded('product')),
'sku' => $this->sku,
'serial_number' => $this->serial_number ? (Int) $this->serial_number : null,
'price' => (Int) $this->price,
'discount' => $this->discount ? (Int) $this->discount : null,
];
return $arrayNull; // this is missing
}
TicketResource.php
public function toArray($request) {
return [
'id' => $this->id,
'user_id' => $this->user_id,
'title' => $this->title,
'body' => $this->body,
'status' => $this->status,
'created_at' => $this->created_at->toDateTimeString(),
];
}
CommentResource.php
public function toArray($request) {
return [
'id' => $this->id,
'body' => $this->body,
'user_id' => $this->user_id,
'created_at' => $this->created_at->toDateTimeString()
];
}
TicketController.php
public function index() {
return TicketResource::collection(Ticket::all());
}
public function show(Ticket $id) {
$ticket = $id;
return new TicketResource($ticket);
}
Model Ticket.php
public function comments() {
return $this->hasMany('App\Comment');
}
Model Comment.php
public function ticket() {
return $this->belongsTo('App\Ticket');
}
routes/api.php
Route::get('tickets', 'TicketController#index');
Route::get('tickets/{id}', 'TicketController#show');
I want when I request to tickets/{id} URL, I expect to receive this response:
{
"data": {
"id": 1,
"user_id": 2,
"title": "lorem",
"body": "epsum",
"status": "open",
"created_at": "2020-03-04 18:14:56",
"comments": [
{
"id": 1,
"body": "equi",
"user_id": 1,
"created_at": "2020-03-05 18:14:56",
}
]
}
}
On the contrary, when I visit tickets URL, I don't want the comments to be added on each ticket.
How can I implement that?
You need to add relation
This is my model class:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
public function children()
{
return $this->hasMany(Category::class, 'parent_id', 'id')->select('categories.id',
'categories.cid AS key',
'categories.name AS label',
'categories.type',
'categories.lvl');
}
}
in my controller:
$parents = Category::select(
'categories.id',
'categories.id AS key',
'categories.name AS label')->where('lvl', 1)->get();
foreach ($parents as $item) {
$item->children
}
return Response::json($parents, 200, array('Content-Type' => 'application/json;charset=utf8'), JSON_UNESCAPED_UNICODE);
Result:
[
{
"id":2,
"key":2,
"label":"parent label",
"children":[
{
"id":17,
"key":"92697f63-5c50-11ea-80df-5cf9ddf839d3",
"label":"child label",
"type":"Category",
"lvl":2,
}
]
}
]
To initialize my app I have the following route:
/initialize
This returns Taxonomies, Enumerables and a couple of other taxonomy like collections. This saves multiple HTTP requests.
Although with Dingo / Fractal, I cannot see how I can respond with multiple collections?
e.g.
return [
'taxonomies' => $this->response->collection($taxonomies, new TaxonomyTransformer);
'enumerables' => $this->response->collection($enumerables, new EnumerableTransformer);
'otherStuff' => $this->response->collection($otherStuff, new OtherStuffTransformer);
];
return response()->json([
'data' => [
'taxonomies' => $this->fractal->collection($taxonomies, new TaxonomyTransformer);
'enumerables' => $this->fractal->collection($enumerables, new EnumerableTransformer);
'otherStuff' => $this->fractal->collection($otherStuff, new OtherStuffTransformer);
]
], 200);
This should return the JSON in the format you are looking for.
I have the same issue ,and I found the solution from How to use Transformer in one to many relationship. #1054.
Here is the collection I want to return with the transfomer of dingo in my controller.
$user = User::where('email','=',$input['email'])->with('departments')->with('roles')->get();
DepartmentTransformer
class DepartmentTransformer extends TransformerAbstract
{
public function transform($department)
{
return [
'id' => $department['id'],
'name' => $department['name'],
'level' => $department['level'],
'parent_id' => $department['parent_id']
];
}
}
RolesTransformer
class RolesTransformer extends TransformerAbstract
{
public function transform($role)
{
return [
'name' => $role['name'],
'slug' => $role['slug'],
'description' => $role['description'],
'level' => $role['level']
];
}
}
UserTransformer
class UserTransformer extends TransformerAbstract
{
protected $defaultIncludes = ['departments','roles'];
public function transform($user)
{
return [
'id' => $user['id'],
'name' => $user['name'],
'email' => $user['email'],
'phone' => $user['phone'],
];
}
public function includeDepartments(User $user)
{
$dept = $user->departments;
return $this->collection($dept, new DepartmentTransformer());
}
public function includeRoles(User $user)
{
$rl = $user->roles;
return $this->collection($rl, new RolesTransformer());
}
}
In my controller
$user = User::where('email','=',$input['email'])->with('departments')->with('roles')->get();
return $this->response->collection($user, new UserTransformer());
And I got the result
"data": {
{
"id": 43,
"name": "test7",
"email": "test7#foxmail.com",
"phone": "186********",
"departments": {
"data": {
{
"id": 1,
"name": "业务一部",
"level": 1,
"parent_id": 0
}
}
},
"roles": {
"data": {
{
"name": "agent",
"slug": "agent",
"description": "业务员",
"level": 1
}
}
}
}
}
Please take note of the usage of $defaultIncludes and includeXXX() methonds in the UserTransformer.You can get more detail info from Fractal Doc.
I'm using fractal in Laravel 5.2. I'm using a transformer on a collection like this:
public function allFromCompany()
{
$users = UserModel::all();
return $this->response->collection($users, new UserTransformer);
}
UserTransformer
class UserTransformer extends Fractal\TransformerAbstract
{
public function transform(UserModel $user)
{
return [
'user' => [
'id' => $user->id,
'role' =>
[
'role_id' => $user->role_id,
'name' => $user->role->name
],
'company' =>
[
'company_id' => $user->company_id,
'company' => $user->company->name,
],
'active' => $user->active,
'name' => $user->name,
'lastname' => $user->lastname,
'address' => $user->address,
'zip' => $user->zip,
'email' => $user->email
]
];
}
}
But when I do it like that I receive an error:
{
"status_code": 500,
"debug": {
"line": 10,
"file": "/home/vagrant/Code/forum/app/Src/v1/User/UserTransformer.php",
"class": "Symfony\\Component\\Debug\\Exception\\FatalThrowableError",
"trace": [
"#0 /home/vagrant/Code/forum/vendor/league/fractal/src/Scope.php(338): Src\\v1\\User\\UserTransformer->transform(Object(Src\\v1\\User\\User))",
When I try this with one item:
return $this->response->item($user, new UserTransformer);
It works.
It's pretty old question, and I ran into it :) but if anyone has similar problem, maybe you've forgot to "use Helpers" something like
class UserController extends Controller
{
use Helpers;
public function index()
{
$users = User::all();
return $this->collection($users, new UsersTransformer);
}