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

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

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 8: One To Many Polymorphic: Relate same child to different parents

I used the one-to-many polymorphic relationship like described in the laravel documentation, to be able to relate different parent models to one child model. i have assumed, that i would be able to assign different parent models to the same child model. but this does not work. as soon as i create a new relation with another parent model to the same child, the old relation is replaced.
Example:
A, B and C are parent models, each with one data-record (id=1).
X is the child model with one data-record (id=1)
I can't do something like that with the common methods:
A(id=1) <-> X(id=1)
B(id=1) <-> X(id=1)
C(id=1) <-> X(id=1)
Since the last creation of a relation always overwrites the previous one. In this example one relation would remain (C(id=1) <-> X(id=1))
I am able to do that with a many-to-many polymorphic implementation - but this is actually not what i want, since i do not want the parent models to be able to have more than one relation to the child model. (altough i could rule that out by creating a composite key within the *able table on the corresponding fields)
This is the actual code, that should assign one image to multiple parent models (but only the last save inside the loop remains - if i add a break at the end of the loop, the first one is saved):
public function store(StoreImageRequest $request)
{
$validated = $request->validated();
$image = $validated['image'];
$name = isset($validated['clientName']) ? $image->getClientOriginalName() : $validated['name'];
$fileFormat = FileFormat::where('mimetype','=',$image->getClientMimeType())->first();
$path = $image->store('images');
$imageModel = Image::make(['name' => $name, 'path' => $path])->fileFormat()->associate($fileFormat);
$imageModel->save();
$relatedModels = Image::getRelatedModels();
foreach($relatedModels as $fqcn => $cn) {
if(isset($validated['model'.$cn])) {
$id = $validated['model'.$cn];
$models[$fqcn] = call_user_func([$fqcn, 'find'], [$id])->first();
$models[$fqcn]->images()->save($imageModel);
}
}
}
Well, it's a bit more clear what you're trying to do now.
The thing is, even though you want to enforce that a maximum of one of each parent is attached to the child, you're still actually trying to create a ManyToMany PolyMorphic relationship. Parents can have mutiple images and images can have multiple parents (one of each).
Without knowing the data structure, it could be viable, if the parents all have similar structures to consolidate them into one table, and the look into the relation HasOne ofMany to enforce that an image only have one of each parent.
If you insist on a Polymorphic relationship, I would do the following
// child
Schema::create('images', function (Blueprint $table) {
$table->id();
$table->timestamps();
});
// parents
Schema::create('parent_ones', function (Blueprint $table) {
$table->id();
$table->timestamps();
});
Schema::create('parent_twos', function (Blueprint $table) {
$table->id();
$table->timestamps();
});
Schema::create('parent_trees', function (Blueprint $table) {
$table->id();
$table->timestamps();
});
// morph table
Schema::create('parentables', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('image_id');
$table->foreign('image_id')->references('id')->on('images');
$table->string('parentable_type');
$table->unsignedBigInteger('parentable_id');
$table->timestamps();
$table->unique(['image_id', 'parentable_type', 'parentable_id'], 'uniq_parents');
});
The unique constraint on the table enforces that an image can only have one of each parent type.
Model relations
// Child
public function parent_one(): MorphToMany
{
return $this->morphedByMany(ParentOne::class, 'parentable');
}
public function parent_two(): MorphToMany
{
return $this->morphedByMany(ParentTwo::class, 'parentable');
}
public function parent_tree(): MorphToMany
{
return $this->morphedByMany(ParentTree::class, 'parentable');
}
// parents
public function images(): MorphToMany
{
return $this->morphToMany(Image::class, 'parentable')->withTimestamps();
}
Then it's up to you code to handle that it does not try to attach a parent to an image if it already has a parent of that type.
This could either be done in your controller or if the parent isn't replaceable, you could add it as validation in you Request.
A solution could be installing this package (untested by me).
MorphToOne
Old answer
It sounds to me that you have flipped the One To Many relation in the wrong direction.
Your child model should implement morphTo and your parent models should implement morphMany
a
id - integer
title - string
body - text
b
id - integer
title - string
url - string
c
id - integer
title - string
url - string
x
id - integer
body - text
commentable_id - integer
commentable_type - string
Child Model
public class X extends Model
{
public function commentable(): MorphTo
{
return $this->morphTo();
}
}
Parent Models
public class A extends Model
{
public function comments(): MorphMany
{
return $this->morphMany(X::class, 'commentable');
}
}
Adding to the relation:
$a = A::firstOrFail();
$x = X::firstOrFail();
// Attaches
$a->comments()->attach($x);
// Sync, removes the existing attached comments
$a->comments()->sync([$x]);
// Sync, but do not remove existing
$a->comments()->sync([$x], false);
$a->comments()->syncWithoutDetaching([$x]);

add relationship to added values in pivot table laravel

I'm new to laravel relationships, but i'm familiar with SQL. I'm trying to add a pivot table with other values than just the two main ids.
I have some users, and some teams. Each user can play multiple instruments, and can be in multiple teams, but they can only play one instrument per team.
My pivot migration:
Schema::create('team_members', function (Blueprint $table) {
$table->integer('user_id')->unsigned();
$table->integer('team_id')->unsigned();
$table->integer('instrument_id')->unsigned();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('team_id')->references('id')->on('teams')->onDelete('cascade');
$table->foreign('instrument_id')->references('id')->on('user_instruments')->onDelete('cascade');
});
I can already see this:
$team = Team::first();
$team->members->first()->details->instrument_id; // "2"
How can I define the relationship between instrument_id on user_instruments, so I can do $team->members->first()->details->instrument
UPDATE:
I found this one: https://medium.com/#DarkGhostHunter/laravel-has-many-through-pivot-elegantly-958dd096db
Not sure if it will work...
You should create an intermediate model TeamMember, in which you will define a relationship to Instrument. To achieve that, modify your many-to-many relationship on both Team and User, adding the option "using", like this:
// Team.php
public function members() {
return $this->belongsToMany(App\User::class, 'team_members', 'user_id')
->using(App\TeamMember:class);
}
Then you need to create a model for your pivot table called TeamMember, which will extend the class Pivot and define the relationship with Instrument.
class TeamMember extends Pivot {
public function instrument() {
return $this->belongsTo(App\Instrument::class);
}
}
Then you should be able to get the instrument like this (assuming you set a custom name for your pivot table as "details"): $team->members->first()->details->instrument.

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 hasManyThrough with ManyToMany pivot

I am making a game and I have users which have facilities and for this I use ManyToMany
user_facilities
-user_id
-facility_id
But each relation must have a facility level, so I've added facility_levels table and each of this levels must be connected to the ManyToMany relation. So user_facilities now looks like this
user_facilities
-user_id
-facility_id
-level_id
level_id is the connections between the facility which the user owns and which level it is.
My question is how do I connect this in the models?
The User model now has this
public function facilities()
{
return $this->belongsToMany('App\Facility', 'user_facilities');
}
And Facility
public function users()
{
return $this->belongsToMany('App\User', 'user_facilities');
}
So how do I get the level of the facility which the user owns?
In blade I hope there is a way I can use something like
{{ $user->facility->level->property }}
level is part of the user_facilities table not of facility
Therefore, you should be able to access the level_id from the many to many relationship of user and facility
One thing you can do is to access the immediate table (also called pivot table).
First, edit your relationship to include the extra attributes.
public function facilities()
{
return $this->belongsToMany('App\Facility', 'user_facilities')
->withPivot('level_id');
}
public function users()
{ // if you omit this EDIT/UPDATE, you cannot do this:
// $facility->users()->first()->pivot->level_id;
return $this->belongsToMany('App\User', 'user_facilities')
->withPivot('level_id');
}
Take note that when accessing a many to many relationship, Laravel will immediately assign a pivot attribute onto the result which contains details about the pivot table of the two models
Now try accessing the extra column:
$facility = $user->facilities->first();
$level_id = $facility->pivot->level_id;
// now you can use $level_id for finding the level.
$level = Level::find($level_id);
Now, since you can do that, you can also create a model for the many to many relationship of user and facility that will have that property of level_id
Let's create a new model called UserFacility that will extend Pivot.
This will be your Pivot model for many to many relationship of user and facilities.
use Illuminate\Database\Eloquent\Relations\Pivot;
class UserFacility extends Pivot
{
}
Then update your users and facilities relationships as follows.
public function facilities()
{
return $this->belongsToMany('App\Facility', 'user_facilities')
->using('App\UserFacility');
}
public function users()
{
return $this->belongsToMany('App\User', 'user_facilities')
->using('App\UserFacility');
}
Notice that using method.
$userfac = $users->facilities->pivot; // <-- pivot will now be an instance of App\UserFacility
echo $userfac->level_id;
Lastly,
If you don't want the pivot attribute name, you can change it using the as method, chain it after the belongsToMany method, like this:
public function users()
{
return $this->belongsToMany('App\User', 'user_facilities')
->as('UFac')
->using('App\UserFacility');
}
$userfac = $users->facilities->UFac; // <-- you can now access the pivot table using the property `UFac`
echo $userfac->level_id;
It may also be possible that your pivot table has a relationship with a level since it has a level_id. Don't worry, it's possible, just add this function in your UserFacility model.
public function level()
{
return $this->belongsTo('App\Level');
}
Now you can do this!
$user->facilities->first()->UFac->level; // <-- this will be an instance of App\Level
source: https://laravel.com/docs/5.5/eloquent-relationships#many-to-many

Resources