I have created a custom push() method which saves all model's relations in cascading manner and collects full snapshot of entity and children details for audit log.
Everything works fine with belongsTo and hasMany relations. I just do as follows:
$ae->target()->associate(new AuditableEntryTarget(["name" => "single target"]));
$ae->children->add(new AuditableEntryChild(["name" => "one of children"]));
$ae->children->add(new AuditableEntryChild(["name" => "two of children"]));
// add grandchildren to the first child
$ae->children[0]->children->add(new AuditableEntrySubChild(["name" => "one of subchildren for first child"]));
// add target subchild
$ae->target->subtarget()->associate(new AuditableEntryTargetChild(["name" => "single target child"]));
// my custom method which saves and collects to audit log all the children, no matter if they are attached through hasMany or belongsTo
$ae->push();
But the problem is with hasOne relation. It does not offer any way to attach the related model to my root model without saving it first. HasOne relation has only save() method in Laravel:
/**
* Attach a model instance to the parent model.
*
* #param \Illuminate\Database\Eloquent\Model $model
* #return \Illuminate\Database\Eloquent\Model
*/
public function save(Model $model)
{
$model->setAttribute($this->getPlainForeignKey(), $this->getParentKey());
return $model->save() ? $model : false;
}
which, as you see, not only associates, but also saves the model. To be able to detect all changes for fields, my method requires that the relations are attached to the model but not saved yet because else I won't be able to intercept the save process and attach the data to my snapshot. BelongsTo has associate and hasMany has add for attaching models without saving them yet, but I cannot find anything similar for hasOne.
Is there any way to attach a new model instance to hasOne relation without causing it to be saved immediately (something like associate for belongsTo or add for hasMany)?
With help of Laracasts guys, the workaround seems to be
$model->setRelation('child', $childInstance);
Still, it's strange that Laravel does not initialize hasOne relationship the same way as it inits hasMany.
Related
I'm facing an issue with model extension. Here's an example of my problem:
I have a User model and an Admin model that extends my User model. I use a github repo called Bouncer for permissions. When I save my roles for an Admin model, it saves as /App/Admin and for Users, it saves as /App/User for the model reference.
So when I call my roles for a Admin or for a User, no problem. But my issue is when I want to query all my users with their roles. I obviously get all my users AND my admin, but the Admins can't get their roles because the are "/App/Admin" in the database.
How can I get all the roles of my "extended" models when I call the parent?
You will create a relationship in your model using belongs to and use a method called "with ()".
First step:
In your model, create belongs to ex:
public function post()
{
return $this->belongsTo('App\Post', 'foreign_key', 'other_key');
}
Second step:
You will use it in your controller,example:
$users = User::with('podcasts')->get();
Eloquent: Relationships
https://laravel.com/docs/7.x/eloquent-relationships#updating-belongs-to-relationships
Another example
Get Specific Columns Using “With()” Function in Laravel Eloquent
In my application, a model Device has a many-to-many relationship with model Task.
A combination of Device and Task could be assigned to a various number of model User.
example: Device A has a Task Check something and this should be done by User Frank and Steven.
From my point of view, this should be a "standard problem", but so far I could not find a proper solution.
Right now, I use following workaround:
a) added an unique ID id to the device_task pivot table
b) query id from the pivot table
c) create a new table device_task_user which contains user_id and device_task_id
b) use query builder to get/add users
But I am really not happy with this approche.
Would it be possible, that the pivot table also extends Model and then have a one-to-many relationship with User?
Or would you suggest to add a json colum to the pivot table and store the users there?
Any idea would be very welcome!
Would it be possible, that the pivot table also extends Model
Yes, it's possible. From the docs:
If you would like to define a custom model to represent the intermediate table of your relationship, you may call the using method when defining the relationship. All custom models used to represent intermediate tables of relationships must extend the Illuminate\Database\Eloquent\Relations\Pivot class
You also can create a new hasMany() and belongsTo() relationships between Task and Device models and use them as well as existing belongsToMany relationship. And you'll need to define a new relationship between pivot model and User model to be able to get data by device, task or user.
Modify many-to-many relationship to hold an extra field user_id
class Device extends Model
{
public function tasks()
{
return $this->belongsToMany(
Task::class,
'device_task',
'device_id',
'task_id'
)->withPivot('user_id');
}
}
And when updating do like this in controller
$device->tasks()->attach([$taskId]=>['user_id']=>$userId);
And of-course you need DeviceTask model and also a has-many relationship between User model and DeviceTask model to get user's task
I'm just getting going with Laravel, and have used Eloquent to define my Campaign table. I have a Campaign model which is currently empty.
I'm not sure how to add attributes to this model to represent the fields in the db - or even if I should. The Laravel documentation seems thin on models and searches keep leading me to accessors and mutators.
If I have a database field called platform_type in my campaigns table, how do I link the PlatformType model attribute to this field?
To clarify:
This is not a question about relationships - there is only one entity in my solution thus far.
platform_type is a field in my campaigns table because it is an attribute of a campaign - I'm asking how to represent this in my model.
The model has an internal array which stores the attributes of a given row (it's called $attributes and replicated by $original if you look for them in the source code). The reason it's replicated is so when you call save() it will only do a save if you actually changed them from the originals.
You can access said attributes via $modelInstance->getAttribute("platform_type") or $modelInstance->platform_type which will call the magic __get method that in turn calls the getAttribute
So in your case you can have:
$campaign = Campaign::find($id);
echo $campaign->platform_type;
The ORM will automatically create the relevant SQL query and fill the model instance with the attributes of the row it finds.
You need to define relationships. In the PlatformType model:
public function campaigns()
{
return $this->hasMany(Campaign::class, 'platform_type');
}
And in the Campaign model:
public function platformType()
{
return $this->belongsTo(PlatformType::class, 'platform_type');
}
You also need to rename the campaign table to campaigns. Or you should add this to the model to be able to use a custom name:
protected $table = 'campaign';
At this point, these tables will be connected and relationships will work. However, it is recommended to add foreign key constraints.
I am designing shop solution in Laravel 5.2.
Now, I am stuck with a problem. I am creating self relation (one product can have many related products), like this
public function related()
{
return $this->belongsToMany(self::class, 'related', 'product_id', 'related_id');
}
Now, let's presume I got array of related products ids from form. How can I mass assign them? Seems I cannot do this without creating model for Related. Or can I?
The pivot table for many to many relation doesn't need model.
So you have to create pivot table "related" and make on it "product" function to set the relation.
and this link discuss that point: https://laravel.com/docs/5.2/eloquent-relationships#many-to-many.
You can use sync function like $product->related()->sync(/* product ids array */); to manage the relation.
I'm trying to get my head around using polymorphic relationships for a many-to-many relationship between suppliers and products:
products
id
name
suppliers
id
name
product_supplier
id
product_id // belongsToMany easily takes care of this id
supplier_id // and this id
price // this can be fetched using withPivot('price')
deliverymethod_id // I'm having difficulties "joining" this one.
I'm confident in using belongsToMany(), I can easily do something like this:
public function products()
{
return $this
->belongsToMany('Supplier')
->withPivot('price');
}
But the catch here is joining to that third column in the relationship table:
deliverymethods
id
name
I am unsure how to do this. I've been told that Polymorphic Relationships are what I'm after however I'm unsure how to implement them for my situation.
http://laravel.com/docs/4.2/eloquent#many-to-many-polymorphic-relations
According to the documentation, I would have to rename my table columns to include *able_id and *able_type. This is really confusing.
I was expecting laravel to having something like belongsToMany('Supplier')->withAlso('Deliverymethod')
I'm afraid that method does not exist (yet?).
What I fall back to is manually filling in the 3rd relation:
public function products()
{
return $this
->belongsToMany('Supplier')
->withPivot('price', 'delivermethod_id');
}
Now I can access ->pivot->deliverymethod_id on every Product that I get via Supplier.
You could even add a function in your Product model that fills this in automatically:
Class Product ... {
protected $appends = array('deliverymethod');
public function getDeliverymethodAttribute()
{
return Deliverymethod::find($this->pivot->delivermethod_id);
}
Now every time you request a product via it's relation to the supplier, it automatically includes a deliverymethod attribute with the object in it.
(To have it not throw an error when you get a Product directly, just remove the $appends variable from the Product model and call the getDeliverymethodAttribute() method manually whenever you need it.)
Short explanation about polymorphic relations:
Polymorphic relations are for relations, where two models are related to a third model at the same time. So for example both a User and a Product can have a Picture of them. Now, it doesn't make sense to have two models for the pictures (UserPicture and ProductPicture), since they both have the same characteristics. This would be a perfect reason to use a polymorphic relation, where the Picture can both belong to a User or a Product.
However, in your case the Deliverymethod applies directly to the relation between Supplier and Product. So this is not where polymorphic relations would work, but it has instead to be done the way you did it.