My many-to-many relations are saving data only once for each items and I can not find the mistake I possibly did when attaching multiple attributes to multiple items.
Here are my models:
Item
<?php
...
class Item extends Model
{
protected $fillable = [
'external_id',
'url',
'created_at',
'updated_at'
];
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function attributes()
{
return $this->belongsToMany(AttributeValue::class);
}
}
Attribute
<?php
...
class Attribute extends Model
{
protected $fillable = [
'name'
];
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function values()
{
return $this->belongsToMany(AttributeValue::class);
}
}
AttributeValue
<?php
...
class AttributeValue extends Model
{
protected $fillable = [
'attribute_id',
'name',
];
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function items()
{
return $this->belongsToMany(Item::class);
}
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function attribute()
{
return $this->belongsTo(Attribute::class);
}
}
As you can see, I try to separate attribute and its values for better access to each values like $item->attributes() or $attribute->values().
My migrations look like this:
Item
<?php
...
class CreateItemsTable extends Migration
{
public function up()
{
Schema::create('items', function ( Blueprint $table ) {
$table->increments('id');
$table->string('external_id');
$table->string('url');
$table->timestamps();
}
}
attributes
<?php
...
class CreateAttributesTable extends Migration
{
public function up()
{
Schema::create('attributes', function ( Blueprint $table ) {
$table->increments('id');
$table->string('name');
$table->timestamps();
}
}
attribute values
<?php
...
class CreateAttributeValuesTable extends Migration
{
public function up()
{
Schema::create('attribute_values', function ( Blueprint $table ) {
$table->increments('id');
$table->text('name');
$table->integer('attribute_id')->unsigned();
$table->foreign('attribute_id')->references('id')->on('attributes')->onDelete('cascade');
$table->timestamps();
}
}
...and last but not least, the pivot table created with Jeffrey Way's cool Laravel Extended Generators
attribute value item pivot
<?php
...
class CreateAttributeValueItemPivotTable extends Migration
{
public function up()
{
Schema::create('attribute_value_item', function ( Blueprint $table ) {
$table->integer('attribute_value_id')->unsigned()->index();
$table->foreign('attribute_value_id')->references('id')->on('attribute_values')->onDelete('cascade');
$table->integer('item_id')->unsigned()->index();
$table->foreign('item_id')->references('id')->on('items')->onDelete('cascade');
$table->primary([ 'attribute_value_id', 'item_id' ]);
});
}
}
I know, this is a lot of code but I think this is neccesary for helping me with this issue.
Imagine, I am getting objects for different items with attributes like brand, size, height etc.
When I try to attach them to my pivot table like
$items = $this->getExternalItemsObject();
...
foreach($items as $item) {
$attributes = $item->Attributes->AttributesValueList;
foreach ( $attributes as $attribute )
{
$itemAttribute = Attribute::firstOrCreate([ 'name' => $attribute->Name ]);
$itemAttributeValue = AttributeValue::firstOrCreate(
[ 'name' => $attribute->Value[0] ],
[ 'attribute_id' => $itemAttribute->id ]
);
$itemAttributeValue->items()->attach($item->id);
}
Now, the when looping through the first item, everything works fine. All attribute ids are stored in the pivot table with the related item id. But for the second, third, nth item which has sometimes the same attributes like brand (let's say Nike or something like this) the relations are skipped and not beeing saved.
This is really curios and honestly drives my crazy a little bit.
Alright, as it seems I did not follow the right conventions for naming pivot tables.
So adding the second argument belongsToMany()method solved it.
This is what it has to look like:
...
return $this->belongsToMany(AttributeValue::class, 'attribute_value_item');
...
...and the same for the related many-to-many models too.
Additionally, I had to use ->syncWithoutDetaching([$item->id]) instead of ->attach($item->id).
Related
I have a problem with Laravel model relationships. I need to let users create new trucks. However, I need to store manufacturer's field as an id, not a title. So I decided to make two tables (manufacturers and trucks) that have one to many relationship (manufacturers have multiple trucks while one truck has one manufacturer).
Here's the migrations files.
Manufacturers table:
public function up()
{
Schema::create('manufacturers', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('manufacturer');
$table->timestamps();
});
}
Trucks table:
public function up()
{
Schema::create('trucks', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('make_id');
$table->unsignedInteger('year');
$table->string('owner');
$table->unsignedInteger('owner_number')->nullable();
$table->text('comments')->nullable();
$table->foreign('make_id')->references('id')->on('manufacturers');
$table->timestamps();
});
}
Manufacturer.php model:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Manufacturer extends Model
{
/**
* #var string
*/
protected $table = 'manufacturers';
/**
* #var array
*/
protected $fillable = [
'manufacturer',
];
public function trucks(){
return $this->hasMany(Truck::class);
}
}
Truck.php model:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Truck extends Model
{
/**
* #var string
*/
protected $table = 'trucks';
/**
* #var array
*/
protected $fillable = [
'make_id', 'year', 'owner', 'owner_number', 'comments',
];
public function manufacturer(){
return $this->belongsTo(Manufacturer::class);
}
}
Controller file:
public function index()
{
$trucks = Truck::all();
return view('trucks.index')->with('trucks', $trucks);
}
index.blade.php
#foreach($trucks as $truck)
<tbody>
<tr>
<td>{{$truck->make_id}}</td> //I need this one to show manufacturers title
<td>{{$truck->year}}</td>
<td>{{$truck->owner}}</td>
<td>{{$truck->owner_number}}</td>
<td>{{$truck->comments}}</td>
</tr>
</tbody>
#endforeach
This view now shows the id. What I need to do to show manufacturers title(manufacturers.manufacturer) instead of the id? Thank you all in advance!
Your foreign key for manufacturer in trucks table is not manufacturer_id. In this case you need to declare it in your models:
return $this->belongsTo(Manufacturer::class, 'make_id' )
And
return $this->hasMany(Truck::class, 'make_id' )
I want to print which user is an author of a subcategory but when I do dd(). I get a NULL value.
User model:
<?php
namespace App;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
{
use Notifiable;
use HasRoles;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password'
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
/**
* The attributes that should be mutated to dates.
*
* #var array
*/
protected $dates = [
'created_at',
'updated_at'
];
}
Category model:
class Category extends Model
{
public function subcategory()
{
return $this->hasMany(Subcategory::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
}
Subcategory Model:
class Subcategory extends Model
{
public function category()
{
return $this->belongsTo(Category::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
}
Function where I want to print an author of subcategory.
public function show(Category $category)
{
$subcategories = $category->subcategory->user->name;
dd($subcategory);
return view('subcategories', compact('subcategories '));
}
DD output: NULL also when I do dd($category) In "relations" i can see my "subcategory" but there is not "user" relation anywhere. Please help :/
Migrations:
public function up()
{
Schema::create('categories', function (Blueprint $table) {
$table->increments('id');
$table->text('description');
$table->timestamps();
});
}
public function up()
{
Schema::create('subcategories', function (Blueprint $table) {
$table->increments('id');
$table->string('description')->nullable();
$table->foreign('category_id')->references('id')->on('categories')
->onUpdate('cascade')->onDelete('cascade');
$table->integer('user_id')->unsigned();
$table->foreign('user_id')->references('id')->on('users')
->onUpdate('cascade')->onDelete('cascade');
$table->timestamps();
});
}
Requests:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ItemRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
//
];
}
}
View:
#foreach($subcategories as $subcategory)
<li>{{$subcategory->user->name}}</li>
#endforeach
Your Category model defines the relation as subcategories, but you are calling $category->subcategory. Instead, call the relation as you have it defined:
$subs = $category->subcategories;
Also, since the relation is one to many, you will need to loop through each subcategory in order to retrieve the user. Example:
$user_names = array();
foreach ($subs as $s) {
$user_names[] = $s->user->name
}
dd($user_names);
Or, get the nth subcategory, etc.:
dd($category->subcategories->first()->user->name);
//Or
dd($category->subcategories->last()->user->name);
//Etc.
Edit:
Change your show method to this:
public function show(Category $category)
{
$subcategories = $category->subcategory;
dd($subcategories);
return view('subcategories', compact('subcategories'));
}
If You are still unable to see the user relation in each subcategory, try it with this:
$subcategories = $category->subcategory()->with('user')->get();
subcategory does not have a user() relationship, only category has.so the user's name would be:
$category->user->name
I'm learning php with laravel and trying to implement categories and subcategories for multiple on my project.
For Example: I have Books, Mobiles in my project
Books has its own categories and subcategories. Same goes for Mobile.
I have added another table with relation
Schema::create('category_product', function (Blueprint $table) {
$table->increments('id');
$table->integer('product_id')->unsigned();
$table->integer('category_id')->unsigned();
$table->unique(array('product_id', 'category_id'));
// foreign key constraints are optional (but pretty useful, especially with cascade delete
$table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
$table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade');
});
Category Database Schema
Schema::create('categories', function (Blueprint $table) {
$table->increments('id');
$table->integer('parent_id')->nullable()->index();
$table->string('title')->unique();
$table->string('slug')->unique();
$table->string('description')->nullable();
$table->string('keywords')->nullable();
$table->timestamps();
});
category.php (Model)
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Cviebrock\EloquentSluggable\Sluggable;
class Category extends Model
{
use Sluggable;
/**
* Return the sluggable configuration array for this model.
*
* #return array
*/
public function sluggable()
{
return [
'slug' => [
'source' => 'title'
]
];
}
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'categories';
/**
* Attributes that should be mass-assignable.
*
* #var array
*/
protected $fillable = [
'parent_id', 'title', 'description', 'slug'
];
public function parent()
{
return $this->belongsTo('Category', 'parent_id');
}
public function children()
{
return $this->hasMany('Category', 'parent_id');
}
public function categoryProduct(){
return $this->belongsToMany('CategoryProduct');
}
public function product(){
return $this->belongsToMany('Product');
}
}
Am i doing the right way as i didn't find a proper tutorial for this kind of approach. Do i need to create a CategoryProduct.php model and reference
public function categories(){
return $this->belongsToMany('Category');
}
public function products(){
return $this->belongsToMany('Product');
}
No, you don't need to create a model for a pivot table. Eloquent has many methods to make working with pivot tables a breeze. In your case you don't need a model.
But sometimes, when you're working with pivot table additional columns a lot it's a good idea to create a model for a pivot table.
I'm new to Laravel and am having a bit of a hard time cracking how relationships work. I'm building a simple e-commerce application, where each user has some orders, and order has one or many sub-orders, and each sub-order is linked to only one item (please don't comment on my scheme yet; for now I just need to figure out Eloquent and will be doing refactoring later :) ).
Following are my models:
class Order extends Model
{
//timestamp
protected $created_at;
public function sub_orders() {
return $this->hasMany('App\SubOrder');
}
public function user() {
return $this->belongsTo('App\User');
}
}
class SubOrder extends Model
{
protected $fillable = array('delivery_date', 'quantity', 'total_price', 'delivery_status');
public function item() {
return $this->hasOne('App\Item');
}
public function order() {
return $this->belongsTo('App\Order');
}
}
class Item extends Model
{
//note - slug is kind of categorization and is common to many items
protected $fillable = array('sku', 'name', 'slug', 'unit_price');
}
And here are the migrations:
class CreateOrdersTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('orders', function (Blueprint $table) {
$table->increments('id');
$table->timestamp('created_at');
//foreign keys
$table->unsignedInteger('user_id')->after('id');
$table->foreign('user_id')->references('id')->on('users') ->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('orders');
}
}
class CreateSubOrdersTable extends Migration
{
public function up()
{
Schema::create('sub_orders', function (Blueprint $table) {
$table->increments('id');
$table->date('delivery_date');
$table->decimal('quantity', 5, 2);
$table->decimal('total_price', 7, 2);
$table->enum('delivery_status', ['pending_from_farmer', 'ready_for_customer', 'out_for_delivery', 'delivered']);
//foreign keys
$table->unsignedInteger('order_id')->after('id');
$table->foreign('order_id')->references('id')->on('orders') ->onDelete('cascade');
$table->unsignedInteger('item_id')->after('order_id');
$table->foreign('item_id')->references('id')->on('items') ->onDelete('cascade');
});
}
public function down()
{
Schema::dropIfExists('sub_orders');
}
}
class CreateItemsTable extends Migration
{
public function up()
{
Schema::create('items', function (Blueprint $table) {
$table->increments('id');
$table->string('sku')->unique();
$table->string('name');
$table->string('slug');
$table->decimal('unit_price', 5, 2);
});
}
public function down()
{
Schema::dropIfExists('items');
}
}
The problematic expression is why I write App\Order::all()[0]->sub_orders[0]->item in my web.php and get the following error:
SQLSTATE[42703]: Undefined column: 7 ERROR: column items.sub_order_id does not exist
LINE 1: select * from "items" where "items"."sub_order_id" = $1 and ...
^ (SQL: select * from "items" where "items"."sub_order_id" = 1 and "items"."sub_order_id" is not null limit 1)
I don't understand why it's looking for sub_order_id in the items table. And what's the right way to go about doing it?
Overall: define the 1-to-1 relationship using hasOne or belongsTo will affect the target table where Laravel find the foreign key. hasOne assume there is a my_model_id in target table.And belongsTo assume there is a target_model_id in my table.
class SubOrder extends Model
{
public function item() {
return $this->hasOne('App\Item', 'id', 'item_id');
}
}
or
class SubOrder extends Model
{
public function item() {
return $this-> belongsTo('App\Item');
}
}
According to Laravel Doc
class User extends Model
{
/**
* Get the phone record associated with the user.
*/
public function phone()
{
return $this->hasOne('App\Phone');
}
}
Eloquent determines the foreign key of the relationship based on the model name. In the above case, the Phone model is automatically assumed to have a user_id foreign key. If you wish to override this convention, you may pass a second argument to the hasOne method:
$this->hasOne('App\Phone', 'foreign_key', 'local_key');
Or Defining The Inverse Of The Relationship
class Phone extends Model
{
/**
* Get the user that owns the phone.
*/
public function user()
{
return $this->belongsTo('App\User');
}
}
In the example above, Eloquent will try to match the user_id from the Phone model to an id on the User model.
Your SubOrder item has relationship of type OneToOne (hasOne is bidirectional) with an Item.
So Eloquent expects to have sub_order_id in the items table.
So the solution is to define the inverse of this relationship (belongsTo) in the Item model
I have been stuck for most of the day getting an empty array any time I eager loaded product images while requesting products in my controller in Laravel.
public function ProductImages() {
return $this->hasMany('App\ProductImage', 'product_id'); // this matches the Eloquent model
}
I changed my code to make the FK 'test' and suddenly it has started returning the appropriate data I want back. I put the FK back to product_id but again am back to an empty array. Below are My product Model ProductImages Model and the migrations for both with the call Im making in the controlelr
Product Model
class Product extends Model
{
protected $fillable = array('name', 'url_name', 'sku', 'description', 'short_description', 'enabled', 'track_inventory', 'stock_level', 'allow_backorder', 'updated_user_id' );
//protected $hidden = array('id');
// LINK THIS MODEL TO OUR DATABASE TABLE ---------------------------------
// Database table is not called my_products
protected $table = 'products';
// each product HAS many product images
public function ProductImages() {
return $this->hasMany('App\ProductImage', 'productId'); // this matches the Eloquent model
}
}
Product Images Model
class ProductImage extends Model
{
protected $fillable = array('name', 'description', 'path', 'sort_order', 'product_id');
// DEFINE RELATIONSHIPS --------------------------------------------------
// each attribute HAS one product
public function Product() {
return $this->belongsTo('App\Product', 'id'); // this matches the Eloquent model
}
}
Product Migration
class CreateProductsTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('products', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('url');
$table->string('sku');
$table->string('description');
$table->string('short_description');
$table->integer('enabled');
$table->integer('track_inventory');
$table->integer('stock_level');
$table->integer('allow_backorder');
$table->dateTime('updated_user_id');
$table->timestamps();
});
}
}
Product Images Migration
class CreateProductImagesTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('product_images', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('description');
$table->string('path');
$table->integer('sort_order');
$table->integer('product_id')->unsigned();
$table->foreign('product_id')->references('id')->on('products');
$table->timestamps();
});
}
}
Product Controller Snippet
public function index()
{
//
$Products = Product::with('ProductImages','productTypes')->get();
//dd($Products);
return response()->json( $Products, 200);
}
If you could help me understand why this strange behavior is happening i would be very grateful.