Define additional relationship on many-to-many polymorphic relationship - laravel

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

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.

Can't access pivot model's id attribute inside of the static::saved() method in Laravel

I can't access pivot model's id attribute. I have one pivot model PivotModel and two models that are connected through this pivot model
ModelA class:
public function modelB()
{
return $this->belongsToMany(ModelB::class, 'model_a_model_b', 'model_a_id', 'model_b_id')
->using(PivotModel::class)
->withPivot('id', 'prop_1', 'prop_2');
}
ModelB class:
public function modelA()
{
return $this->belongsToMany(ModelA::class, 'model_a_model_b', 'model_b_id', 'model_a_id')
->using(PivotModel::class)
->withPivot('id', 'prop_1', 'prop_2');
}
PivotModel:
use Illuminate\Database\Eloquent\Relations\Pivot;
class PivotModel extends Pivot
{
public $incrementing = true;
public static function boot() {
parent::boot();
static::saved(function ($model) {
dump($model->id);
dump($model->toArray());
});
}
}
Pivot table migration file
Schema::create('model_a_model_b', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('model_a_id');
$table->unsignedInteger('model_b_id');
$table->string('prop_1');
$table->string('prop_2');
$table->unique(['model_a_id', 'model_b_id'], 'model_a_id_model_b_id');
$table->foreign('model_a_id')
->references('id')->on('model_a')
->onDelete('cascade')
;
$table->foreign('model_b_id')
->references('id')->on('model_b')
->onDelete('cascade')
;
$table->timestamps();
});
I assume this should work.
This is from the official documentation for Laravel 5.8
Custom Pivot Models And Incrementing IDs
If you have defined a many-to-many relationship that uses a custom pivot model, and that pivot model has an auto-incrementing primary key, you should ensure your custom pivot model class defines an incrementing property that is set to true.
/**
* Indicates if the IDs are auto-incrementing.
*
* #var bool
*/
public $incrementing = true;
I can only access the prop_1 and prop_2 properties but not the id property.
The id is null
dump($model->id);
and the toArray() only shows other props but not the id
dump($model->toArray());
I found a temporary solution. If you know how to do it better please suggest.
As mentionied, the id property is accessible in the created() method
so you can easily get it using $model->id.
static::created(function ($model) {
dump($model->id);
});
The problem is in the updated() method where the $model instance is filled with properties other than id.
See the method updateExistingPivotUsingCustomClass inside of Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithPivotTable
static::updated(function($model) {
// If the model is of type Pivot we have to store it into a different variable or overwrite it. If we need dirty props we first need to store them to separate variable before overwriting the $model variable
$dirtyProps = $model->getDirty();
if($model instanceof Pivot) {
$model = get_class($model)
::where($model->foreignKey, $model->{$model->foreignKey})
->where($model->relatedKey, $model->{$model->relatedKey})
->firstOrFail();
// Use the model
$model->id ...
}
});

Updating a Laravel pivot table when the pivot has a unique field

I have two models, User and Service, with a ManyToMany relationship. The pivot table has two additional fields, id_code and alias. There may be many relationships between User and Service, but each one is uniquely identified by the field id_code. I need to retrieve a specific record in the pivot table by the id_code, and update the alias for that record only.
My models:
User:
class User extends Authenticatable
{
//All attributes & other functions here...
public function linked_services(){
return $this->belongsToMany(Service::class)
->withPivot('alias', 'id_code')
->withTimestamps();
}
}
Service:
class Service extends Model
{
//All attributes & other functions here...
public function linked_users(){
return $this->belongsToMany(User::class)
->withPivot('alias', 'id_code')
->withTimestamps();
}
}
Service-User migration:
Schema::create('service_user', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('user_id')->nullable();
$table->unsignedBigInteger('service_id');
$table->string('alias', 50)->nullable();
$table->string('id_code', 50);
$table->foreign('user_id')->references('id')->on('users');
$table->foreign('service_id')->references('id')->on('services');
$table->timestamps();
});
In the update function, I get the specific $id_code from a view, but I don't know how to update the "alias" for that specific "id_code".
What I already tried and is not working:
public function update(Request $request, String $id_code){
foreach(Auth::user()->linked_services as $service){
if($service->pivot->id_code === $id_code){
$service->pivot->alias = $request->alias;
$service->pivot->save();
}
}
return redirect()->route('services_user.index');
}
This function updates ALL the existing pivot records for the service_user, I need to update only the specific record for an given "id_code".
I solved it!!! I tell you how:
Auth::user()->linked_services()
->wherePivot('id_code', $id_code)
->update(['alias' => $request->alias]);
I learned that $user->linked_services() (unlike $user->linked_services) returns a relationship object (in this case, belongsToMany), which contains all the relationship data (intermediate table, pivots, FKs, parent / related objects, etc), and the desired pivot can be accessed on that object with ->wherePivot().

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

Resources