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
}
Related
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);
I'm using a Laravel Json Resource in my controller, as follows
public function index(Request $request)
{
$itemsWithTranslations = MenuItem::where(['menu_id' => $request->id, 'parent_id' => null])
->with(['children', 'translations'])
->orderBy('sort_order', 'asc')
->get();
return MenuItemResource::collection($itemsWithTranslations);
}
Now I would like to generate a collection, inside this collection with the children for the item that's being shown.
The following code works fine. Notice how I commented out the children reference
class MenuItemResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'text' => $this->title,
// 'children' => MenuItemResource::collection($this->whenLoaded('children')),
'data' => [
'id' => [
'value' => $this->id,
'type' => 'hidden'
],
'title' => [
'value' => $this->title,
'type' => 'text',
'label' => 'Title'
],
'resource_link' => [
'value' => $this->resource_link,
'type' => 'text',
'label' => 'Resource Link'
],
'translations' => MenuItemTranslationResource::collection($this->whenLoaded('translations'))->keyBy(function ($translation) {
return $translation['locale'];
})
]
];
}
}
When I uncomment the children, I get the following error
"Call to undefined method Illuminate\Http\Resources\Json\AnonymousResourceCollection::keyBy()"
Is it wrong, to include a Resource inside a resource? Or how should I go about this?
Model
class MenuItem extends Model
{
protected $table = 'menu_items';
protected $fillable = ['menu_id', 'parent_id', 'title', 'order', 'resource_link', 'html_class', 'is_blank'];
public function translations()
{
return $this->hasMany(MenuItemTranslation::class, 'menu_item_id');
}
public function children()
{
return $this->hasMany(MenuItem::class, 'parent_id');
}
}
Extra Information
When I return the following data, it does return empty as a collection for the children.
MenuItemResource::collection($this->children);
This returns
While if I return the children without a collection, it returns them (for 1 item, which is correct)
return $this->children;
returns
you should use ChildrenResource::collection
'children' => ChildrenResource::collection($this->whenLoaded('children'))
hope this works.
create a ChildrenResource class if not exists.
I have many-to-many relation with three tables: Category, Product, ProductCategory.
Relation in Category:
public function getProductCategories()
{
return $this->hasMany(ProductCategory::className(), ['category_id' => 'id']);
}
Relation in Product:
public function getProductCategories()
{
return $this->hasMany(ProductCategory::ClassName(), ['product_id' => 'id']);
}
And in ProductCategory
public function getProduct()
{
return $this->hasOne(Product::className(), ['id' => 'product_id']);
}
public function getCategory()
{
return $this->hasOne(Category::className(), ['id' => 'category_id']);
}
In my Category Controller I used this code to show the products I need according to their category (one-to-many):
$cats = Category::findOne(['slug1'=>$slug1]);
$dataProvider = new ActiveDataProvider([
'query' => $query = Product::find()->where(['category_id' => $cats->id]),
'sort'=>array(
'defaultOrder'=>['id' => SORT_ASC],
),
'pagination' => [
'pageSize' => 9,
],
]);
So the question is how to make my ActiveDataProvider to get in query the many-to-many relation?
You can create two more relations like this
In category:
public function getProducts()
{
return $this->hasMany(Product::className(), ['id' => 'product_id'])->via("productCategories");
}
And in product:
public function getCategories()
{
return $this->hasMany(Category::ClassName(), ['id' => 'category_id'])->via("productCategories");
}
Then you can use it like this
$cats = Category::findOne(['slug1'=>$slug1]);
$dataProvider = new ActiveDataProvider([
'query' => $query = $cats->getProducts(),
'sort'=>array(
'defaultOrder'=>['id' => SORT_ASC],
),
'pagination' => [
'pageSize' => 9,
],
]);
I am having a yii2 error : common\models\Book has no relation named "favorite".
When I try to add:
public function search($params) {
$query = Book::find();
$query->joinWith(['author', 'profile', 'favorite']);
In the book model I do have the public function:
public function getFavoritedIcon() {
if (isset($this->favorite)) {
return '<i class="glyphicon glyphicon-asterisk books-form"></i>';
} else {
return '';
}
}
And also this extra function to get the icon
public function getFavoritedIcon() {
if (isset($this->favorite)) {
return $icon;
} else {
return '';
}
}
And this works fine in the grid where I want to add sorting and filter:
[
'label' => 'Favorites',
'attribute' => 'favorite',
'value' => 'favoritedIcon',
'hAlign' => 'center',
'vAlign' => 'middle',
'format' => 'raw',
'width' => '50px',
],
I do some different things from another models I am using:
in the grid i get the value as an icon from the book model but i used this before.
the other thing is that the Favorite model has not the same name that the table but it work fine in the grid
abstract class Favorite extends \yii\db\ActiveRecord
{
public static function tableName()
{
return 'user_favorite';
}
/**
* #inheritdoc
*/
public function rules()
{
return [
[['user_id', 'book_id'], 'required'],
[['user_id', 'book_id'], 'integer'],
[['selectedTime'], 'safe']
];
}
Any clues what I am doing wrong ?
======================================================
UPDATE after Pedro del Sol answer
There was some errors in the code but the main one was answered by Pedro, I do had a favorite function in the Book model but not favorites with multiple output.
So now it is working like that:
In the Book model
public function getFavorite() {
$userID = Yii::$app->user->identity->id;
return Favorite::find()->where(['user_id' => $userID, 'book_id' => $this->id])->one();
}
public function getFavorites() {
$userID = Yii::$app->user->identity->id;
return $this->hasMany(Favorite::className(), ['book_id' => 'id'], ['book_id' => $this->id]);
}
public function getFavoritedIcon() {
if (isset($this->favorite)) {
return '<i class="glyphicon glyphicon-asterisk books-form"></i>';
} else {
return '';
}
}
In the BookSearch model:
public function search($params) {
$query = Book::find();
$query->joinWith(['favorites']);
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
$dataProvider->setSort([
'attributes' => [
'title',
'author_id',
'rights_owner_id',
'user_favorite.user_id',
]
]);
and the grid view :
[
'label' => 'Favorites',
'attribute' => 'user_favorite.user_id',
'value' => 'favoritedIcon',
'hAlign' => 'center',
'vAlign' => 'middle',
'format' => 'raw',
'width' => '50px',
],
Having a method to getFavoritedIcon() is not the same as declaring a relation to getFavorite()
I assume that in your Book model class you have the methods getAuthor() and getProfile() which will return queries linking a Book with an Author and a Profile. You'll need something similar with Favorite(s) but I suspect the multiplicities will be different.
I think to declare your relation you'll need something like
/**
* #return \yii\db\ActiveQuery
*/
public function getFavorites()
{
return $this->hasMany(Favorite::className(), ['book_id' => 'ID']);
}
if the relation between Books and Favorites is one to many (most likely) or
/**
* #return \yii\db\ActiveQuery
*/
public function getFavorite()
{
return $this->hasOne(Favorite::className(), ['book_id' => 'ID']);
}
if the relation is one to one.
You can then use joinWith() with either 'favorite' or 'favorites' depending on the multiplicities of your relation.
I created a table in relation to two other tables, however I do not know how to relate them when creating new projects, how could I do that?
I'll create the categories in a separated page in my admin, and when the user create's a new project he will be able to select an array of categories coming from the table.
My question is, how can I store the relation when POST the data? I've never done this before.
Project model
class Project extends Model
{
protected $table = 'projects';
protected $fillable = [
'name',
'slug',
'header',
'desc',
'about',
'url',
'status'
];
public function customer()
{
return $this->belongsTo(Customer::class);
}
public function category()
{
return $this->belongsToMany(Category::class);
}
public function categories()
{
return $this->hasMany(Category::class);
}
}
Category model
class Category extends Model
{
protected $table = 'categories';
protected $fillable = [
'name',
'status'
];
public function subCategory()
{
return $this->hasMany(SubCategory::class);
}
public function projects()
{
return $this->belongsToMany(Project::class);
}
}
My actual Post create
public function postCreate(ProjectCreateRequest $request, Customer $customer)
{
//Array
$categories = $request->categories;
$customer->projects()->create([
'name' => $request->name,
'header' => $request->header,
'desc' => $request->desc,
'about' => $request->about,
'url' => $request->url,
]);
//How do I store the relation?
return redirect('admin/clientes/editar/' . $customer->id);
}
use attach or sync for many to many relationships
reference : https://laravel.com/docs/5.1/eloquent-relationships#inserting-related-models
public function postCreate(ProjectCreateRequest $request, Customer $customer)
{
//Array
$categories = $request->categories;
$projects = $customer->projects()->create([
'name' => $request->name,
'header' => $request->header,
'desc' => $request->desc,
'about' => $request->about,
'url' => $request->url,
]);
//suppose here the $categories is an array of ids or a single integer variable id of the category that are related
$projects->categories()->attach($categories);
return redirect('admin/clientes/editar/' . $customer->id);
}
First: Make sure you have the following table in the database in order for the many to many relationship to work
table name : category_project
columns: category_id, project_id
Second: class Project needs only the following function for categories relation to work:
public function categories()
{
return $this->belongsToMany(Category::class);
}
Third: class Category needs only the following function for projects relation to work:
public function projects()
{
return $this->belongsToMany(Project::class);
}
Fourth: postCreate function should be as follow:
public function postCreate(ProjectCreateRequest $request, Customer $customer)
{
//should be an array of categories ids
$categories = $request->categories;
$project = $customer->projects()->create([
'name' => $request->name,
'header' => $request->header,
'desc' => $request->desc,
'about' => $request->about,
'url' => $request->url,
]);
//How do I store the relation? you can use attach or sync
$project->categories()->sync($categories);
return redirect('admin/clientes/editar/' . $customer->id);
}