I have the following Eloquent Models with relationships:
class Lead extends Model
{
public function contacts()
{
return $this->belongsToMany('App\Contact')
->withPivot('is_primary');
}
}
class Contact extends Model
{
public function leads()
{
return $this->belongsToMany('App\Lead')
->withPivot('is_primary');
}
}
The pivot table contains an additional param (is_primary
) that marks a relationship as the primary. Currently, I see returns like this when I query for a contact:
{
"id": 565,
"leads": [
{
"id": 349,
"pivot": {
"contact_id": "565",
"lead_id": "349",
"is_primary": "0"
}
}
]
}
Is there a way to cast the is_primary
in that to a boolean? I've tried adding it to the $casts
array of both models but that did not change anything.
Since this is an attribute on the pivot table, using the
$casts
attribute won't work on either theLead
orContact
model.One thing you can try, however, is to use a custom
Pivot
model with the$casts
attribute defined. Documentation on custom pivot models is here. Basically, you create a newPivot
model with your customizations, and then update theLead
and theContact
models to use this customPivot
model instead of the base one.First, create your custom
Pivot
model which extends the basePivot
model:Now, override the
newPivot()
method on theLead
and theContact
models:Good news! Tylor already fixed this bug:
https://github.com/laravel/framework/issues/10533
In Laravel 5.1 or higher you can use dot notation for pivot casts:
The answer provided by @patricus above is absolutely correct, however, if like me you're looking to also benefit from casting out from JSON-encoded strings inside a pivot table then read on.
The Problem
I believe that there's a bug in Laravel at this stage. The problem is that when you instantiate a pivot model, it uses the native Illuminate-Model
setAttributes
method to "copy" the values of the pivot record table over to the pivot model.This is fine for most attributes, but gets sticky when it sees the
$casts
array contains a JSON-style cast - it actually double-encodes the data.A Solution
The way I overcame this is as follows:
1. Set up your own Pivot base class from which to extend your pivot subclasses (more on this in a bit)
2. In your new Pivot base class, redefine the
setAttribute
method, commenting out the lines that handle JSON-castable attributesThis highlights the removal of the
isJsonCastable
method call, which will returntrue
for any attributes you have casted asjson
,array
,object
orcollection
in your whizzy pivot subclasses.3. Create your pivot subclasses using some sort of useful naming convention (I do
{PivotTable}Pivot
e.g. FeatureProductPivot)4. In your base model class, change/create your
newPivot
method override to something a little more usefulMine looks like this:
Then just make sure you Models extend from your base model and you create your pivot-table "models" to suit your naming convention and voilà you will have working JSON casts on pivot table columns on the way out of the DB!
NB: This hasn't been thoroughly tested and may have problems saving back to the DB.
I had to add some extra checks to have the save and load functions working properly in Laravel 5.
In Laravel 5.4.14 this issue has been resolved. You are able to define a custom pivot model and tell your relationships to use this custom model when they are defined. See the documentation, under the heading Defining Custom Intermediate Table Models.
To do this you need to create a class to represent your pivot table and have it extend the
Illuminate\Database\Eloquent\Relations\Pivot
class. On this class you may define your$casts
property.You can then use the
using
method on theBelongsToMany
relationship to tell Laravel that you want your pivot to use the specified custom pivot model.Now, whenever you access your pivot by using
->pivot
, you should find that it is an instance of your custom pivot class and the$casts
property should be honoured.Update 1st June 2017
The issue raised in the comments by @cdwyer regarding updating the pivot table using the usual
sync
/attach
/save
methods is expected to be fixed in Laravel 5.5 which is due to be released next month (July 2017).See Taylor's comment at the bottom of this bug report and his commit, fixing the issue here.