Laravel - How to setup a morphOne relationship - laravel

I'm trying to implement a morphable table for categories, right now I've the following.
// Snippet Table
- id
- title
- body
// Post Table
- id
- title
- body
// Category Table
- id
- name
I want to be able to morph the posts and snippets to have only one category, something like this:
// Categorizable Table
- category_id
- categorizable_id
- categorizable_type
Do I have to have another model for this categorizable table? Or there is a way to set it without another model?
So far I have this
class Snippet extends Model
{
public function category()
{
return $this->morphOne(Category::class, 'categorizable');
}
}
class Post extends Model
{
public function category()
{
return $this->morphOne(Category::class, 'categorizable');
}
}
class Category extends Model
{
public function categorizable()
{
return $this->morphTo();
}
}
And I have 4 tables, snippets, posts, categories, categorizables, the last one the migration looks like this.
Schema::create('categorizables', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('category_id');
$table->morphs('categorizable');
$table->timestamps();
});
The thing is, what is the correct way to save this relationship, I'm testing it on tinker and bothattach($category_id) and sync($category_id) aren't saving the relationship they return the error BadMethodCallException with message 'Call to undefined method Illuminate\Database\Query\Builder::categorizable()', What I'm missing?

Sorry I thought you wanted many-to-many relationship. morphOne relationship API is the same as morphMany from the docs
Post extends Model
{
public function category() {
return $this->morphOne(Category::class, 'categorizable');
}
}
Category extends Model
{
public function categorizable() {
return $this->morphTo();
}
}
EDIT
When using morphOne you don't need a pivot table categorizables table must be deleted and change your categories table to include the morph fields
// Category Table
- id
- name
- categorizable_id
- categorizable_type

Related

Define additional relationship on many-to-many polymorphic relationship

I'm creating an taggable table like so:
Schema::create('taggable', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('tag_id');
$table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
$table->unsignedBigInteger('taggable_id');
$table->string('taggable_type');
$table->unsignedBigInteger('company_id');
$table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade');
$table->unsignedBigInteger('created_by')->nullable();
$table->foreign('created_by')->references('id')->on('users')->onDelete('set null');
$table->timestamps();
});
As you can see, next to connecting tags to a Post, Video etc (as per the Laravel docs example), I'd also like to ensure that the row that's added is connected to a Company and User model so I can keep track who it belongs to and who created it, but even more so access properties from those models in controllers and views.
I know that in my Post model I can do:
public function tags()
{
return $this->morphedByMany(\App\Models\Tag::class, 'taggable')->withPivot('created_by', 'company_id', 'created_at');
}
The problem is that this will retrieve just the value for created_by and company_id and not the Eloquent model. Is this possible?
So what I'd like to do is access properties of those relationships in controllers and views like so:
$post = Post::findOrFail(1);
foreach($post->tags as $tag) {
$tag->created_by->name // accessing 'name' property on the `User` model
}
foreach($post->tags as $tag) {
$tag->company->address // accessing `address` property on the `Company` model
}
You must do like below:
first you must define relationship between tags and users
class Tags extends Model
{
public function taggable(): MorphTo
{
return $this->morphTo();
}
public function createdBy(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by');
}
}
then for achieve that you want you must:
$post = Post::first();
$users = $post->tags()->with('createdBy')->get();

Laravel Eloquent Relationship by Models

I have a table called "categories" and there is a 3 table needed the id of categories
the 3 tables needed a categories id is "activies", "stories", "news" i really want to know how to fix this issue the stories and news table is running well im using php tinker to see if the relationship is running and the table activities it give me a null value, inside the activities table i`d input same category_id in the table of activities it gives always a null value given in php tinker
Models
Category.php
public function stories()
{
return $this->hasMany(Story::class);
}
public function news()
{
return $this->hasMany(Newy::class);
}
public function activites()
{
return $this->hasMany(Activity::class);
}
Activity.php
public function category()
{
return $this->belongsToMany(Category::class, 'category_id');
}
If you are using hasMany in your Category Model, the opposite relationship in your Activity model should be belongsTo() and not belongsToMany()
public function category()
{
return $this->belongsTo(Category::class);
}
You can read more in the documentation here

Same Foreing Key Twice That Refers to One Model

I need to make an advice platform for products. User can make advices under product section. So product has many advices. Also advices belongsTo product. But on product_advices table i have product_id and product_advice_id these both refers to id on products table.
So here is the problem. I can take advices from product_advices table which refers to product_id. But how can i take the other one as product.
product->advices to show advice and user message
each advices as advice and advice->product->name to show adviced product name
I couldn't make a relationship with eloquent between them.
//Product Model
public function advices()
{
return $this->hasMany('App\ProductAdvice', 'product_id');
}
//ProductAdvice Model
protected $table = 'product_advices';
public function product() {
return $this->belongsTo('App\Product', 'product_id');
}
//Product Advice Table
Schema::create('product_advices', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->text('body')->nullable();
$table->integer('product_id')->unsigned();
$table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
$table->integer('product_advice_id')->unsigned();
$table->foreign('product_advice_id')->references('id')->on('products')->onDelete('cascade');
$table->timestamps();
});
For example :
Iphone has many advices by users.
Iphone->advices brings the advices from product_advices that Iphone belongs to product_id column.
When User recommends Samsung Galaxy S10 to Iphone. Samsung refers to product_advice_id column on product_advices table. But how to show Samsung Galaxy S10 as a product.
$advice->product->name returns Iphone instead of Samsung Galaxy S10.
EDIT 2
After your answer, I understood what you want.
Just update your ProductAdvice model to have another relationship, like:
class ProductAdvice extends Model
{
public function product()
{
return $this->belonsTo('App\Product', 'product_id');
}
public function related()
{
return $this->belonsTo('App\Product', 'product_advice_id');
}
}
Then you can do:
$product = Apple
$product->advices->message = 'blablabla'
$product->advices->related = 'ProductX'
If you need to to the inverse order, $advice->product->related add the same relationship on your Product model.
EDIT 1
Sorry, you edited the post after my answer...
Could you explain the 'product_advice_id' necessity? You already have the product_id.
Unless you want a pivot table, like: Product -> ProductAdvice -> Advice, which is not needed in your case, since you can just put the advice information in the Advice table and link it to the product (belongsTo), removing the necessity of the ProductAdvice pivot.
I think your structure should be like this:
Product model with all the product data
ProductAdvice model with product_id and the advice information (message, rating, etc)
Then, your Product hasMany Advices and your Advice belongsTo Product:
class Product extends Model
{
public function advices()
{
return $this->hasMany('App\ProductAdvice', 'product_id');
}
}
class ProductAdvice extends Model
{
public function product()
{
return $this->belonsTo('App\Product', 'product_id');
}
}
Finally, you can query the advices of a specific Product to get the advice information:
$product->advices->message
$product->advices->rating
Or query the product name if you have an advice:
$advice->product->name
So, If understand well, you want and advice from product A to return the name of product B.
You can make this by creating multiple method related to the same model.
Your models will look similar at this:
class Product extends Model
{
public function advices()
{
return $this->hasMany('App\ProductAdvice', 'product_id');
}
public function relations()
{
return $this->hasMany('App\ProductAdvice', 'related_id');
}
}
class ProductAdvice extends Model
{
public function product()
{
return $this->belonsTo('App\Product', 'product_id');
}
public function relation()
{
return $this->belonsTo('App\Product', 'related_id');
}
}
Then you will have to create another column in your talbe name :related_id (it could be something else, just made it match in your model. You can also change relations() and relation() method to whatever name you want.)
After this it's how you store your data. You have to make your code associate the good product model into your product_id and into related_id. This way. You can have $advice->product->name === 'iPhone' && $advice->relation->name === 'Samsung S10'
Found a solution like this for now.
//ProductAdvice Model
protected $table = 'product_advices';
public function user()
{
return $this->belongsTo('App\User');
}
public function product() {
return $this->belongsTo('App\Product', 'product_id');
}
public function advicedProduct()
{
return Product::where('id', $this->product_advice_id)->first();
}
//Product Model
public function advices()
{
return $this->hasMany('App\ProductAdvice', 'product_id');
}
How i show it on view
#foreach($product->advices as $advice)
<li>{{ $advice->body }} - {{ $advice->advicedProduct()->name }}</li>
#endforeach

Use pivot table for extra attributes?

I am following the many to many relationships Laravel tutorial here on Laracasts - https://laracasts.com/series/laravel-5-fundamentals/episodes/21
My challenge to myself is, I have created the pivot table article_tag) to keep track of the many to many relations. Articles can have many tags, and tags can have many articles. So I can runsyncetc to associatetagXtagYtagZtoarticle1`. However I also want to be able to optionally set one of the associated tags as "isGreenTag". Can I do this within the pivot table that is keeping track of the many-to-many relations? Could I add a column "is_green_tag"?
Here is my Article class relationship:
class Article extends Model {
public function tags() {
return $this->belongsToMany('App\Tag')->withTimestamps();
}
}
Here is my Tag class relationship:
class Tag extends Model {
public function articles() {
return $this->belongsToMany('App\Articles');
}
}
Here is my migration for the pivot table:
public function up() {
Schema.create('article_tag', function(Blueprint $table) {
$table->integer('article_id')->unsigned()->index();
$table->foreign('article_id')->references('id')->on('articles')->onDelete('cascade');
$table->integer('tag_id')->unsigned()->index();
$table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
$table->timestamps();
});
}
Can I add to the pivot table migration $table->boolean('is_green_tag')->nullable()?
Yes you can, you can give it a default of 0 instead of making it nullable:
$table->boolean('is_green_tag')->default(0);
And then you can modify the relationship on Article class:
public function tags() {
return $this->belongsToMany('App\Tag')->withPivot(['is_green_tag'])->withTimestamps();
}
Once you have an Article object, you can access that property:
foreach ($article->tags as $tag) {
if ($tag->pivot->is_green_tag) {
// some logic here
}
}
To save is_green_tag for a $tagId:
$article->tags()->attach($tagId, ['is_green_tag' => 1]);
Laravel docs:
https://laravel.com/docs/5.7/eloquent-relationships#many-to-many
https://laravel.com/docs/5.7/eloquent-relationships#updating-many-to-many-relationships

Laravel | Using Eloquent hasManyThrough

I have a table called invoiceDetails that has item_id as foreign key from another table called items which has category_id as foreign key from table called categories.
I want to do that following using eloquent:
$result = InvoiceDetail::groupBy('item_id')
->selectRaw('sum(qty) as qty, item_id')->with('item', 'category')->get();
but I am getting error:
Call to undefined relationship [category] on model [App\InvoiceDetail].
Here's my relation inside Category model:
public function invoiceDetail() {
return $this->hasManyThrough('App\InvoiceDetail', 'App\Item', 'category_id', 'item_id');
}
Any suggestions?
Not sure you would even need a hasManyThrough relation here, unless you want to fetch all InvoiceDatail objects belonging to all items which in turn belong to the Category. That part is not clear from your question.
But in your example you are fetching items with their category from distinct item_id.
The reason this is not working is because you are trying to fetch the category relation from the InvoiceDetail object, which does not exist.
->with('item', 'category')
You want to load the Category based on the item relation, not based on the InvoiceDetail, try the dot notation (given that you did define the other relations)
->with('item.category')
Relations should be like this:
class InvoiceDetail extends Model
{
public function item()
{
return $this->belongsTo(\App\Item::class);
}
}
class Item extends Model
{
public function invoiceDetails()
{
return $this->hasMany(\App\InvoiceDetail::class);
}
public function category()
{
return $this->belongsTo(\App\Category::class);
}
}
class Category extends Model
{
public function items()
{
return $this->hasMany(\App\Item::class);
}
public function invoiceDetails()
{
return $this->hasManyThrough(\App\InvoiceDetail::class, \App\Item::class, 'category_id', 'item_id');
}
}
You would want to use the hasManyThrough if, for example, you have a Category and you want to load all the InvoiceDetails directly.
dd($category->invoiceDetails);

Resources