How to properly implement this polymorphic relationship in Laravel? - laravel

I am trying to build an inventory for users in Laravel 5.8, however the items have their own properties, therefore I needed to set up a polymorphic relationship. When attaching items to users, it tries to add the model User to the table on itemable_type and the user's ID to itemable_id aswell as add the User's ID to user_id, something I could workaround by passing the models I need, but when I try to retrieve them it tries to find item with itemable_type = 'App\Models\User', which makes me think something's completely wrong here. Can I have some orientation on how to solve it?
class User extends Model
{
public function inventory()
{
return $this->morhpToMany(InventoryItem::class, 'itemable', 'user_inventories', null, 'itemable_id')
->withPivot('amount', 'notes');
}
}
class InventoryItem extends Model
{
public $timestamps = false;
protected $table = 'character_inventories';
protected $fillable = [
'character_id', 'itemable_type', 'amount', 'parent_id', 'notes'
];
public function cloth()
{
return $this->mophedByMany(Cloth::class, 'itemable');
}
public function food()
{
return $this->morphedByMany(Food::class, 'itemable');
}
// Other similar relations
}
// The Inventory migration:
Schema::create('user_inventories', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('user_id');
$table->unsignedInteger('itemable_id');
$table->string('itemable_type');
$table->unsignedInteger('amount')->default(0);
$table->text('notes', 65535)->nullable();
$table->foreign('character_id')->references('id')->on('characters');
});
The expected result is the User model to have different items in his inventory, but the relation is trying to query by joinning to itself and filtering by user type instead of actual items.
The error:
Syntax error or access violation: 1066 Not unique table/alias: 'user_inventories' (SQL:
select `user_inventories`.*,
`user_inventories`.`itemable_id` as `pivot_itemable_id`,
`user_inventories`.`itemable_type` as `pivot_itemable_type`,
`user_inventories`.`amount` as `pivot_amount`,
`user_inventories`.`parent_id` as `pivot_parent_id`,
`user_inventories`.`notes` as `pivot_notes`
from `user_inventories`
inner join `user_inventories` on `user_inventories`.`id` = `user_inventories`.`itemable_id`
where `user_inventories`.`itemable_id` in (4)
and `user_inventories`.`itemable_type` = App\Models\User)

I highly suspect that you have to references the user table in the inventory relation. In general it is a million times easier just following the Laravel convention for naming.
public function inventory()
{
return $this->morhpToMany(InventoryItem::class, 'itemable', 'users', null, 'itemable_id')
->withPivot('amount', 'notes');
}

Related

Property [countries] does not exist on this collection instance

I am trying to create a drop down menu by with eloquent from where I can go to subcontinent with drop down and subcontinent to countries with sub-dropdown. The relationship is Subcontinent has many countries.
Models
Subcontinent
class Subcontinent extends Model
{
protected $guarded = [];
public function countries()
{
return $this->hasMany(Division::class, 'country_name', 'id');
}
}
Country
class Division extends Model
{
protected $table = 'divisions';
protected $fillable = [
'country_name', 'subcontinent_id'
];
public function subcontinent()
{
return $this->belongsTo(Subcontinent::class, 'country_name', 'id');
}
}
The table name of country is divisions and the model name is also Division.
Table
country/division
Schema::create('divisions', function (Blueprint $table) {
$table->id();
$table->string('country_name');
$table->bigInteger('subcontinent_id');
$table->timestamps();
});
Database formation
$subcontinents = Subcontinent::orderBy('id', 'DESC')->get();
But when I try to call dd($subcontinents->countries) it gives me property does not exist error.
"Property [countries] does not exist on this collection instance."
with $subcontinents = Subcontinent::find(1);
the dd still gives null value. How can I call subcontinents to countries!
you have misconception about second and third option in relationship method. for belongsTo relationship, the second argument is the foreign key of the child table and the third argument is the primary key or the reference key of the parent table.
your Division model relationship should be
public function subcontinent()
{
return $this->belongsTo(Subcontinent::class, 'subcontinent_id', 'id');
}
and for hasMany relationship the second argument is the foreign key in the child table. For SubContinent model the relationship would be
public function countries()
{
return $this->hasMany(Division::class, 'subcontinent_id', 'id');
}
and when you use $subcontinents = Subcontinent::orderBy('id', 'DESC')->get(); you get a collection, not an object. you have to loop over to get values and relationship data from this.
foreach($subcontinents as $subcontinent) {
$subcontinent->$subcontinent_name;
$subcontinent->countries;
}
and when you use $subcontinents = Subcontinent::find(1); you get an object. you can directly access its values. just update the relationship method. and you will get values by $subcontinents->countries then.

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();

Eloquent 'with()' returns null

I have a simple relationship between two models: User and Prescription. A user has many prescriptions. In the PrescriptionsController, when I try to get the user that the prescriptions belongs to, it returns null when using with().
PrescriptionsController
public function index()
{
$user_id = Auth::id();
$prescriptions = Prescription::with('user')->where('prescription_for', $user_id)->get();
return response()->json($prescriptions);
}
The result from that Eloquent query:
[{"id":1,"prescription_for":1,"prescription_by":1,"prescription_content":"Paracetamol 120mg - O cutie. Mod administrare: 1 Dimineata | 0 Pranz | 0 Seara","created_at":"2020-10-13T17:33:35.000000Z","updated_at":null,"user":null}]
You can see that the last parameter is null.
In my User model, I have set up the relationship using:
public function prescriptions()
{
return $this->hasMany(Prescription::class);
}
And the Prescription model:
protected $table = 'prescriptions';
public $primaryKey = 'id';
public $timestamps = true;
public function user()
{
return $this->belongsTo(User::class);
}
I am using VueJs so I cannot just do $prescription->user->name as you can in Blade files, that's why I need to eager load the data.
The way I set up the Prescriptions table:
$table->id();
$table->unsignedBigInteger('prescription_for');
$table->foreign('prescription_for')->references('id')->on('users');
$table->unsignedBigInteger('prescription_by');
$table->foreign('prescription_by')->references('id')->on('users');
$table->string('prescription_content');
$table->timestamps();
Any ideas to why this happens? Thanks!
On your prescriptions table, your primary key is prescription_for. If the parent model does not use id as its primary key, or you wish to find the associated model using a different column, you may pass a third argument to the belongsTo() method specifying the parent table's custom key :
public function user()
{
return $this->belongsTo(User::class, 'id', 'prescription_for');
}

Laravel 5.6 eager load nested childrelations

I'm having an issue with getting child relations from a Card object to a User object. The relations are as following:
'User' -> hasOne 'Company'
'Company' -> hasMany 'Card'
So the other way around is:
'Card' -> belongsTo 'Company'
'Company' -> belongsTo 'User'
My Card model has got this:
public function company()
{
return $this->belongsTo('App\Company');
}
My Company model has got this:
public function user()
{
return $this->belongsTo('App\User');
}
public function cards()
{
return $this->hasMany('App\Card');
}
My User model has got this:
public function company()
{
return $this->hasOne('App\Company');
}
What I want to do is: in the User model I want to eager load all cards from that user. So my code looks like this:
$cards = Card::with('company.user')->get();
But it's constantly returning me all the card records in the database, not the ones that are from the logged in user itself. There definitely
is a userID, cause when I dump $this->id in the User model, I'm getting the ID '1'. In the database I've configured all foreign keys so that won't be the problem I assume.
Table 'cards' has a foreign key to 'company_id', and table 'companies' has a foreign key to 'user_id', they are all set by the migration script, which looks like this:
Schema::create('cards', function (Blueprint $table) {
$table->increments('id');
$table->integer('amount');
$table->string('status');
$table->unsignedInteger('company_id');
$table->timestamp('expires_at')->nullable();
$table->boolean('is_debit_allowed')->default(1);
$table->string('cancelled_by')->nullable();
$table->timestamp('cancelled_at')->nullable();
$table->timestamps();
$table->foreign('company_id')->references('id')->on('companies');
});
What am I doing wrong guys?
In the User model, you can add it to the $with array:
// this will eager load the company and cards for every user query, so beware!
protected $with = ['company.cards'];
Or create a new function cards:
public function cards()
{
return $this->company->cards;
}
$cards = $user->cards();
// use the auth helper to get logged in user's cards
$cards = auth()->user()->cards();
This should work for accessing via Card:
$cards = Card::whereHas('company.user', function ($query) {
$query->whereKey(auth()->id());
})->get();

In Laravel, how to set up a relationship for a "likes" table? Also, how to include that information in a query?

I have three tables: users, ideas, and ideas_likes. The ideas_likes table looks like:
ideas_likes
------------
id primary key
user_id foreign key
idea_id foreign key
liked boolean
There's already a one-to-many relationship set up between users and ideas. It looks something like this:
class User extends Ardent implements UserInterface, RemindableInterface {
use UserTrait, RemindableTrait;
protected $table = 'users';
public function ideas()
{
return $this->hasMany('Idea');
}
}
Similarly, the idea model looks like this:
class Idea extends Ardent {
protected $table = 'ideas';
public function user()
{
return $this->belongsTo('User');
}
}
My first question is: How do I create the IdeaLike model? And once that is finished, using Eloquent, how do I retrieve all liked ideas for a user?
===========================================================================
Lucasgeiter's updated solution worked great.
First, there's no need to create an IdeaLike model. You just need a many-to-many relationship
User
public function ideas(){
return $this->belongsToMany('Idea', 'ideas_likes')->withPivot('liked');
}
Idea
public function users(){
return $this->belongsToMany('User', 'ideas_likes')->withPivot('liked');
}
(By adding withPivot I tell Laravel that I want to have the liked column from the pivot table included in the result)
Usage
$user = User::find(1);
$likedIdeas = $user->ideas()->where('liked', true)->get();
By the way: You don't need to specify the $table if it is the plural of the model name.
Update
It looks like you actually really do need both, a one-to-many and a many-to-many relation.
So your final relations would look like this:
User
public function ideas(){
return $this->hasMany('Idea');
}
public function liked(){
return $this->belongsToMany('Idea', 'ideas_likes')->withPivot('liked');
}
Idea
public function likes(){
return $this->belongsToMany('User', 'ideas_likes')->withPivot('liked');
}
public function user(){
return $this->belongsTo('User');
}
(I just chose names for the relations that made kind of sense to me. You can obviously change them)
Usage
Liked ideas by a certain user: (id = 1)
$ideas = Idea::where('user_id', 1)->whereHas('likes', function($q){
$q->where('liked', true);
})->get();

Resources