When dealing with belongsToMany relation, you use a pivot table to record the relation.
For many pivot tables, the relations are just created and then deleted. They won't have their own property, so you never update them.
I know I can do this to auto-set both updated_at and created_at.
class Foo extends Model
{
public function bars() {
$this->belongsToMany(Bar::class)->withTimestamps();
}
}
But how to use only created_at? I have no idea.
You could take another approach:
Skip the withTimestamps() method (to avoid adding both created_at and updated_at columns).
Add a custom pivot column: created_at.
So your code:
class Foo extends Model
{
public function bars() {
$this->belongsToMany(Bar::class)->withPivot('created_at');
} // ^^^^^^^^^^^^^^^^^^^^^^^^
}
Now, with this approach you will need to set the value of created_at manually when creating records:
$foo = Foo::find(1);
$foo->bars()->attach($someId, ['created_at' => now()->format('d-m-Y H:i:s')]);
// or whatever you use as date ^^^^^^^^^^^^^
Also, this column won't be casted as a Date by default -as opposed to what Laravel do with timestamp columns- so this:
$foo->bars()->first()->pivot->created_at
won't be an instance of Carbon. If you want it though, you could create a custom Pivot model, then specify the column to cast and update your relationship to use the custom Pivot model:
Pivot model FooBar.php
class FooBar extends Pivot // <--
{
protected $casts = [
'created_at' => 'datetime:d-m-Y H:i:s',
];
}
Then in your Foo.php class:
class Foo extends Model
{
public function bars() {
$this->belongsToMany(Bar::class)->using(FooBar::class)->withPivot('created_at');
// ^^^^^^^^^^^^^^^^^^^^
}
}
Related
How to get all the relational records of timeslots when intermediate(hasOneThrough) has soft deletes.
1. Attendance Model Attendance.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Attendance extends Model
{
protected $fillable = [
'tutor_id',
'center_timeslot_id',
// other attributes are ommited
];
public function timeslot()
{
return $this->hasOneThrough(
Timeslot::class,
CenterTimeslot::class,
'id',
'id', // primary key on timeslots table...
'center_timeslot_id', //local key of this table to primary of centerTimeslots table
'timeslot_id' //local key on centertimeslots table of target table timeslot
)
->withTrashed();//this does not work
}
}
2. CenterTimeslot Model CenterTimeslot.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class CenterTimeslot extends Model
{
use SoftDeletes;
protected $fillable = [
'center_id', 'timeslot_id', 'capacity'
// other attributes are ommited
];
public function timeslot()
{
return $this->belongsTo(Timeslot::class);
}
}
3. Finally Timeslot Model Timeslot.php
<?php
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
class Timeslot extends Model
{
protected $fillable = [
'name',
// other attributes are ommited
];
}
So if any record is soft deleted that is in center_timeslot_table we can not retrieve the model though applying withTrashed() as it only works on direct model on in the through table. So the soft deletes gets still applied as defined in CenterTimeslot Model
So there is no way to get the records.
But I can trick the query builder to have the method like this which does retrieve the records
Attendance.php
public function timeslot()
{
return $this->hasOneThrough(
Timeslot::class,
CenterTimeslot::class,
'id',
'id', // primary key on timeslots table...
'center_timeslot_id', //local key of this table to primary of centerTimeslots table
'timeslot_id' //local key on centertimeslots table of target table timeslot
)
->withTrashed()
->orWhere(function ($query) {
$query->whereNotNull('center_timeslots.deleted_at');
});
Sot it returns the record but I am not satisfied with the hack. So if someone can give some light or a better workaround will be good.
I face the same issue when my project was in the middle of development and my conclusion after searching on this is:
The documentation says at first coming note that:
And you make your pivot table a model therefore SoftDeletes are working.And withTrashed() is working for Timeslot::class but not for pivot table (CenterTimeslot) because laravel was not expecting a model here.
According to documentation, your Central or pivot table line should be
use Illuminate\Database\Eloquent\Relations\Pivot;
class CenterTimeslot extends Pivot
instead of
class CenterTimeslot extends Model
in this way, there will be the issue of SoftDeletes
In my case, I solve this by creating to classes one is extending from Pivot (which I am using for relation calls) and the other from Model (using where SoftDeletes or as a model is required) but I am not sure that this is the proper way or not but is working fine. I Will appreciate it if someone adds a proper way to deal with it but for the current time, it is working as required.
Note: Make the pivot class name different from the model and if the table name is different then you can define $table separately in pivot class.
This is my tables structure:
Attribute.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Attribute extends Model
{
protected $guarded = [];
public function products()
{
return $this->belongsToMany('App\Product');
}
}
Product.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
protected $guarded = [];
public function attributes()
{
return $this->belongsToMany('App\Attribute');
}
}
I want to get the value column for each row.
What code should I write in my controller to access this value?
Laravel version: 6.9.0
Thanks
You can solve this problem by adding the following method of your end of the relationship
withPivot(['value']);
public function attributes()
{
return $this->belongsToMany('App\Attribute')->withPivot(['value']);
}
And also
public function products()
{
return $this->belongsToMany('App\Product')->withPivot(['value']);
}
When we implements Many To Many relationship,it default create a intermediate table
In your case that table is attribute_product table, we might reference this table as Pivot
table.
This tables value was retrieve by those model by pivot attribute name as follows:
$product = App\Product::find(1);
foreach ($product->attributes as $attribute) {
echo $attribute->pivot->product_id;
}
To add Extra column in (Pivot table)
By default, only the model keys [$attribute_id,$product_id] will be present on the attribute_product table. If your pivot table contains extra attributes, you must specify them when defining the relationship:
return $this->belongsToMany('App\Attribute')->withPivot('column1', 'column2','value');
To change pivot Attribute Name to your given name
you may wish to rename your intermediate table accessor to values instead of pivot.
return $this->belongsToMany('App\Attribute')
->as('values')
Then you will retrieve by $attribute->values->product_id instead of $attribute->pivot->product_id
I have Post model with relation:
public function prices() {
return $this->morphedByMany('App\Price', 'postable');
}
I have a separate table postables with columns:
- postable_id
- postable_type
- custom (type: json)
Why when I want do: $post->pivot->custom I get null, why? When I do dd($post) column custom not found in collection.
You have to specify the custom attributes when you define the relation, like this:
public function prices() {
return $this->morphedByMany('App\Price', 'postable')->withPivot('custom');
}
If you want to cast the custom attribute, you would have to create a model for the pivot table, like this:
use Illuminate\Database\Eloquent\Relations\Pivot;
class Postable extends Pivot
{
protected $casts = [
'custom' => 'array',
];
}
Reference this model in your relation definition:
return $this->morphedByMany('App\Price', 'postable')
->using('App\Postable')
->withPivot('custom');
Now you can retrieve the value like this:
foreach ($post->prices as $price) {
echo $price->pivot->custom;
}
So I have two Laravel/Eloquent models, and I want to add one more field to one of them.
Model 'Car' gets data from table 'cars' and has fields 'id', 'model', 'color' and 'price'. Model 'Person' has fields 'id', 'name' and 'car_id', which is foreign key from 'cars' table. I want model 'Person' to have a field named 'car', which would contain car model from 'Car' model, depending on existing car_id. I've tried something like:
use App\Models\Car;
class Person extends Model {
protected $car = array(Car::find($this->car_id));
}
But that was unsuccessful (errors like 'syntax error, unexpected '(', expecting ')''). What could be the solution? Thanks!
You need to define One-To-Many relationship first. Then get car model for the person:
$carModel = Person::find($personId)->car->model;
Take a look at Eloquent Relationships. What you are trying to do is to create a relationship between Car and Person models. It is up to you if a person can own one or multiple cars. I am suggesting you to let a person have multiple cars.
So, the Person model should know that it has multiple cars:
class Person extends Model
{
public function cars()
{
return $this->hasMany(App\Car::class);
}
}
A car belongs to a person, so the model should know that:
class Car extends Model
{
public function person()
{
return $this->belongsTo(App\Person::class);
}
}
Of course, when creating the tables you should add the field person_id to the CARS table.
Well, what I needed was:
protected $appends = ['car'];
public function getTypeAttribute($car)
{
return Car::find($this->car_id)->model;
}
It was all about serialization and 'protected $appends', thank you all :)
That's not how its done.
The person can have a car (or many). Let's suppose that every person have one car in your database, your car table should have a nullable person_id column, and add this to your User model
public function car() {
return $this->hasOne('App\Role');
}
Now you can get the person and the his car information's like this
User::where('id',$id)->with('car')->get();
I hope you get the point here
I have a phone_models, phone_problems, and a phone_model_phone_problem pivot table. The pivot table has an extra column 'price'.
PhoneModel:
class PhoneModel extends \Eloquent
{
public function problems()
{
return $this->belongsToMany('RL\Phones\Entities\PhoneProblem')->withPivot('price');
}
}
PhoneProblem:
class PhoneProblem extends \Eloquent
{
public function models()
{
return $this->belongsToMany('PhoneModel')->withPivot('price');
}
}
What I'm trying to do is get the price of a specific phone with a specific problem.
This is how I have it now but I feel like Laravel has a built in Eloquent feature I can't find to do this in a much simpler way:
$model = $this->phoneService->getModelFromSlug($model_slug);
$problem = $this->phoneService->getProblemFromSlug($problem_slug);
all this does is select the specific model and problem from their slug.
then what I do is with those credentials I get the price like so:
$row = DB::table('phone_model_phone_problem')
->where('phone_model_id', '=', $model->id)
->where('phone_problem', '=', $problem->id)
->first();
so now I can get the price like so $row->price but I feel like there needs to be a much easier and more 'Laravel' way to do this.
When using Many to Many relationships with Eloquent, the resulting model automatically gets a pivot attribute assigned. Through that attribute you're able to access pivot table columns.
Although by default there are only the keys in the pivot object. To get your columns in there too, you need to specify them when defining the relationship:
return $this->belongsToMany('Role')->withPivot('foo', 'bar');
Official Docs
If you need more help the task of configuring the relationships with Eloquent, let me know.
Edit
To query the price do this
$model->problems()->where('phone_problem', $problem->id)->first()->pivot->price
To get data from pivot table:
$price = $model->problems()->findOrFail($problem->id, ['phone_problem'])->pivot->price;
Or if you have many records with different price:
$price = $model->problems()->where('phone_problem', $problem->id)->firstOrFail()->pivot->price;
In addition.
To update data in the pivot you can go NEW WAY:
$model->problems()->sync([$problemId => [ 'price' => $newPrice] ], false);
Where the 2nd param is set to false meaning that you don't detach all the other related models.
Or, go old way
$model->problems()->updateExistingPivot($problemId, ['price' => $newPrice]);
And remind you:
To delete:
$model->problems()->detach($problemId);
To create new:
$model->problems()->attach($problemId, ['price' => 22]);
It has been tested and proved working in Laravel 5.1 Read more.
Laravel 5.8~
If you want to make a custom pivot model, you can do this:
Account.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Account extends Model
{
public function users()
{
return $this->belongsToMany(User::class)
->using(AccountUserPivot::class)
->withPivot(
'status',
'status_updated_at',
'status_updated_by',
'role'
);
}
}
AccountUserPivot.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Relations\Pivot;
class AccountUserPivot extends Pivot
{
protected $appends = [
'status_updated_by_nice',
];
public function getStatusUpdatedByNiceAttribute()
{
$user = User::find($this->status_updated_by);
if (!$user) return 'n/a';
return $user->name;
}
}
In the above example, Account is your normal model, and you have $account->users which has the account_user join table with standard columns account_id and user_id.
If you make a custom pivot model, you can add attributes and mutators onto the relationship's columns. In the above example, once you make the AccountUserPivot model, you instruct your Account model to use it via ->using(AccountUserPivot::class).
Then you can access everything shown in the other answers here, but you can also access the example attribute via $account->user[0]->pivot->status_updated_by_nice (assuming that status_updated_by is a foreign key to an ID in the users table).
For more docs, see https://laravel.com/docs/5.8/eloquent-relationships (and I recommend press CTRL+F and search for "pivot")