Although I’ve been using Laravel heavily for over two years, I still find things there that I didn’t really know about.
Today I took some time to really understand all the different ways of saving and associating related models. I had misunderstood the docs at first, but after some searching and playing with test code I’ve come to a better understanding.
One-To-Many and Belongs-To Relationships
To illustrate I’ll use an example
Parent and a
Child class. The docs used
Roles which threw me off, so I hope that my naming convention is clearer.
class Parent extends Model
public function children()
class Child extends Model
public function parent()
I always use plurals for
hasMany() relationships as a reminder to myself how it goes.
The first example is saving a
Child object to an existing
// Find an existing Parent record
$parent = App\Parent::find(2);
// Creates a new Child instance
$child = new App\Child(['name' => 'mei']);
// effectively sets $child->parent_id to 2 then saves the instance to the DB
If called repeatedly, the above code will continue to save
Child records with the same
So far so good. We now want to do the reverse of the above.
Let’s say we want a new parent for our child (programmatic adoption?):
// a new Parent object
$parent = App\Parent::find(31);
// updates $child->parent_id to 31
Our Child had parent_id of 2. Above,
associate set the parent_id property of
$child to 31, then
$child->save() persists the change to the database.
Of course, it’s possible to for an object to have more than one parent relation (for example, a Comment can belong to a Post and an Author). As the association is only confirmed when
save() is called, we can simply call
associate with another parent object.
$parent = App\Parent::find(31);
// a new parent!
$anotherParent = new App\AnotherParent(['name' => 'steve']);
// associate with our parent, as above
// set the second relationship
Assuming that foreign keys are set, calling
$child->save() before setting the second relationship would throw an error.
Now that the relationship is set, it’s also possible to sever it.
// sever the parent-child relationship
In this example $child->parent_id is set to
save() method must be called to persist the change to the database.
Again, if foreign keys have been set an error would be thrown here. This code would only work if the foreign key (parent_id) is nullable.
Let’s move on from
To illustrate the Many-To_-Many relationship we’ll use
Tag. These two are connected with the
class Post extends Model
public function tags()
class Tag extends Model
public function posts()
Post can have zero or more tags, and a
Tag can have zero or more posts (in theory anyway, an unused tag is mostly pointless).
Such a relationship allows us to get all tags attached to a
Post, and conversely get all posts attached to a
Unlike one to many relations, both records must be persisted before the following to work. The pivot table must have both ids.
In this example, a tags are added to a post.
$post = App\Post::find(3);
$tag = App\Tag::find(47);
The first argument of
attach() is an id, but an Eloquent model can also be passed:
What happens above is that the pivot table gets a new record, with post_id set to 3 and tag_id to 47.
If your pivot table has extra fields, these can be set by passing an array as the second argument. The parent (in this case
Post) model’s timestamp can be touched by passing in
true as a third argument:
$post->tags()->attach($tag, ['expires' => date('Y-m-d H:i:s')], true);
To sever the relationship,
detach() is used.
A single id or an array can be passed to
detach(), or called without any arguments which detaches all of the parent’s relations.
// detaches $tag from $post
// detaches tags with the following ids from $post
$post->tags()->detach([41, 52, 54]);
The first and second methods deletes rows corresponding to the $post with the given tag ids. The last method deletes all rows from the pivot table with that $post’s id.
sync() method is provided to make life just a bit easier for us.
Let’s assume we’ve edited the post, and have remove one tag and attached another.
$post = App\Post::find(4);
// $post has tags with ids: 32, 45, 47
// tag 32 is no longer required, and we need to add tag with id 86
// so instead of this:
// we do this
$post->tags()->sync([45, 47, 86]);
Here the new tags
[45, 47, 86] will be persisted, and tag 32 will be deleted from the pivot table. This also removes any logic for finding out which tags already exist for the post which are not meant to be removed.
Having spent time playing with this I have a much better appreciation of how it works.
Again, Laravel and Eloquent provide an elegant interface to the database, and lets us write readable code.
I’ve picked up a few points here myself, and I’ll be taking a look back at a few of my own projects to see where I can improve them.