Returning all manyToMany Polymorphic relations - laravel

I have a case where I have the following models:
Presentation, Slideshow, Video (not yet implemented)
These are all joined via a polymorphic many to many relationship, and everything looks fine. However I find the need to query for all "presentables" (both Slideshows and Videos) for a particular presentation, as I want to display them all in one place in a list.
How can I define this relationship, or am I doomed to create a function in my model to handle all of this?
Below is the models and DB schema:
Presentation Model
class Presentation extends \Eloquent {
protected $fillable = [
'name'
];
public function slideshows()
{
return $this->morphedByMany('Slideshow', 'presentable'); //, 'presentables', 'presentable_id', 'presentable_type');
}
public function presentables()
{
//code in here to return all presentable objects
}
}
Slideshow Model
<?php
class Slideshow extends \Eloquent {
protected $fillable = [];
public function presentation()
{
return $this->morphToMany('Presentation', 'presentable', 'presentables', 'presentable_id', 'presentable_type');
}
}
presentables table
Schema::create('presentables', function(Blueprint $table)
{
$table->increments('id');
$table->integer('presentation_id');
$table->integer('order');
$table->integer('presentable_id')->unsigned()->index();
$table->string('presentable_type')->index();
$table->timestamps();
});

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.

Laravel model relationship method

I have 3 tables
User (id,name,mail,mobile)
Contest (id,contest_name,contest_description)
Contest_user (id,user_id,contest_id)
I want to write a has many contest user method in the contest model also I need the user details from the method.
Kindly tell me how can I get the desired result.
Model Screenshot
<?php
namespace App\Model\Contest;
use App\Model\Project\ContestUsers;
use Illuminate\Database\Eloquent\Model;
class Contest extends Model
{
protected $table = 'contest';
protected $fillable = ['name','description'];
public $dates = [
'created_at',
'updated_at',
'deleted_at',
];
//I want to get user details from this method
public function project_contest_users()
{
return $this->hasMany(ContestUsers::class, 'contest_id', 'id')->whereNull('deleted_at');
}
}
contest_user is a pivot table for users and contests and should not have a model class.
The nature of the relation between User and Contest is a many to many, so you need to use belongsToMany() relation in both sides
Contest.php
public function users()
{
return $this->belongsToMany(User::class);
}
User.php
public function contests()
{
return $this->belongsToMany(Contest::class);
}
From what you have done, based on my experience, it's already a good thing that you normalize the many to many relationship. But what N69S said is also correct, that pivot table should not have a Model class, so you need to make your own convention to not use the Model class to create/update/delete (read is fine by me) and if you still want to stick to this method, what you need to do now is just defining the relationships for each of the Models.
User.php:
public function contest_user()
{
return $this->hasMany(ContestUser::class);
}
ContestUser.php:
public function user()
{
return $this->belongsTo(User::class);
}
public function contest()
{
return $this->belongsTo(Contest::class);
}
Contest.php:
public function contest_user()
{
return $this->hasMany(ContestUser::class);
}

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

Get relationship with eloquent

I am using laravel 6.16.0 and I have two migrations:
person
Schema::create('persons', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('full_name')->nullable($value = true);
$table->date('date_of_birth')->nullable($value = true);
$table->string('phone')->nullable($value = true);
$table->string('office')->nullable($value = true);
$table->timestamps();
});
senator_attributes
Schema::create('senator_attributes', function (Blueprint $table) {
$table->bigIncrements('id');
$table->integer('persons_id')->default('999999')->nullable($value = true);
$table->string('party')->nullable($value = true);
// ...
$table->timestamps();
});
My models look like the following:
class Person extends Model
{
protected $table = 'persons';
protected $guarded = ['id'];
}
class SenatorAttributes extends Model
{
protected $table = 'senator_attributes';
protected $guarded = ['id'];
public function person()
{
return $this->belongsTo(Person::class, 'persons_id');
}
}
I was wondering, how can I query all Persons and get for each person their attributes?
I appreciate your replies!
For pretty straight forward solutions you can directly do:
First, you need to add the SenatorAttributes model inside the Person model. You can do so like the following:
class Person extends Model
{
protected $table = 'persons';
protected $guarded = ['id'];
public function senator_attributes()
{
return $this->hasMany(SenatorAttributes::class, 'persons_id'); // or you can use hasOne if person model is supposed to only have one single senator attribute.
}
}
Now to load senator attributes for each person.
Solution 1, with Eager(early/desperate) Loading:
$persons = Person::with('senator_attributes')->get();
Solution 2, on-demand loading:
$persons = Person::all();
foreach($persons as $person) {
$senator_attribures = $person->senator_attributes;
// write your logic here...
}
Solution 1 is fine as long as you don't load all of the rows at the runtime. If you are planning to load hundreds/thousands of models, consider chunking the solution and use eager loading with it.
Learn more about Laravel Eloquent Relationships here.
Now, read the following if you are interested in best practices.
I don't want to sound nosey, but I personally follow the following ground rules while working on any project.
Table name should always be in plural and model name to be in singular form. For example, persons table will have Person model.
Foreign keys should always be in singular form of referenced table + referenced column of the referenced table. For example, if you wish to add a person reference to senator_attributes table, you should add person_id column to senator_attributes table.
Fact, Laravel supports this naming convention, above all, you save yourself time and maintain the consistency and practice across the teams and projects.

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

Resources