Code Bright: Eloquent Relationships

← Back to Index

This title was written for Laravel version 4. If you're looking for material on the latest version of Laravel, then please check out Code Smart.

It was a cold, wet night in Eloquent city. Raindrops trickled down the window of Zack's office like the tears of those left without hope, stumbling in the streets below. Eloquent city was once a peaceful place, and like everything pure, it was primed for corruption. The peace turned to gang wars, smuggling and other crime. The local law inforcement had been bought a long time ago, and would now turn a blind eye to the violence in the streets.

Zack Kitzmiller had once been part of the Eloquent city police force, but as the last honest man in this corrupt city he was forced to leave his work and attempt to fix the city his own way. He became a private investigator.

Zack sat at his old oaken desk near the window of his office on the 14th floor with a bottle of whiskey and a cheap cigar. The office was shabby, and like Zack, hadn't been cleaned in some time. It was cheap though, and money wasn't exactly stumbling through the door.

Knock knock.

Or was it? A tall beautiful blonde stepped through the doorway. Zack turned to face the angelic woman.

"Are you the investigator?" asked the mysterious blonde.

"The names Zack Kitzmiller doll, but you can call me anything you like." said Zack, with a smirk on his stubble covered face.

"Mr Kitzmiller, my name is Pivoté Tableux, and I am looking for the man who killed my husband. The blame for his murder belongs to Enrico Barnez."

"Enrico Barnez is the most notorious drug lord in the city, he keeps well hidden, and finding him won't be easy." grunted Zack. "It's gonna cost you, are you sure you have the coin to cover this?"

"Mister Kitzmiller, money will not be a problem. I has many funds." sneered Pivoté.

"That's all I needed to hear." said Zack with a cold smile.

Zack put on his fedora and long coat, and took his leave.


Zack strode through the main entrance of the docks warehouse. Parts from broken ships and unrecognizable vehicles were strewn across the floor.

"Are you Messy Stinkman?" shouted Zack to a shifty looking dockworker.

"Maybe I iz, maybe I aintz.. dependz who's askin."

"I'm looking for your Boss. Enrico Barnez. Where can I find him?" said Zack, sternly.

"Lookz, the boss has many dangerous friends. Howz I gonna stay safe if I tellz ya where he is? See this warehouse, it all belongs to him. Everythingz I own. Sorry pal, I just can't help yez."

Zack decided that he wasn't going to get any more information out of mister Stinkman without a little inspiration. He took hold of a nearby muffler, and proceeded to beat seven shades of hell out of Messy. Zack Kitzmiller is a total badass.

"Alrightz, I givez in. You want to know where Enrico is? Turn around." whimpered Messy.

Zack turned around slowly, and came chest to face with Enrico Barnez. You see, Enrico was a powerful man. A very powerful, very short man. His natural prey were packs of minstrels, and wild squirrels. Not to mention the inhabitants of Eloquent city.

"Stranger, why were you looking for me?"

Zack knew how dangerous the man that stood before him was. He decided not to answer, instead he pulled a gun from the pocket of his longcoat, and pointed it at Enrico's forehead.

He wasn't fast enough, Enrico also drew his gun. Zack and Enrico were caught in a standoff. Both men drew a long breathe, and exhaled a deep sigh. A gun was fired and a body fell to the ground.

Life isn't all cakes and pies.

Introduction to Relationships

In the previous chapters we have discovered how to represent the rows stored within our database table as objects. Class instances which represent a single row. This means that we have broken down our objects into their simplest form. Book, Fruit, BoyBand, whatever they happen to be.

Since these objects are now simple, if we wish to store data related to them we will need to create new objects and form a relationship between them. So what do we mean by 'relationship'? Well I don't mean the hugs and kisses and sweaty bedsheets type of relationship. This kind of relationship is best explained with an example.

Let's take our Book model from the previous chapter. If we think about books for a moment we soon come to the conclusion that they have to have been written by someone. I know that you would like to believe that I don't really exist, but the sad truth is that I do. Somewhere out there there's a crazy British guy who is fanatical about Laravel. There are other authors out there too, not just me.

So we know that a Book belongs to an author. There's our first relationship. The author has a 'link' to the book. In a way, the author identifies the book. It's not neccessarily a property of the book, like its title for example. No, an author has its own set of properties. A name, a birth place, a favourite pizza topping. It deserves to be its own model. An Author class.

So how do we link these two together? With relational databases we can use 'foreign key's to identify relationships. These are normally integer columns.

Let's build two example tables.

books

+---------+------------------------+
| id (PK) | name                   |
+---------+------------------------+
| 1       | Code Sexy              |
| 2       | Code Dutch             |
| 3       | Code Bright            |
+----------------------------------+

Here's our Book table with three books held within. You will notice that each table has a unique integer primary key as required by the Eloquent ORM. This can be used to identify each individual row. Now let's take a look at the second table.

authors

+---------+------------------------+
| id (PK) | name                   |
+---------+------------------------+
| 1       | Dayle Rees             |
| 2       | Matthew Machuga        |
| 3       | Shawn McCool           |
+----------------------------------+

Here we have another table containing the names of three fantastically handsome developers. We will call this the Author table. Notice how each row once again has an integer primary key that can be used as a unique identifier?

Right, let's form a relationship between the two tables. This is a relationship between a Book and an Author, we will ignore co-authorship for now and assume that a book will only have a single author. Let's add a new column to the Book table.

books

+---------+------------------------+-----------------+
| id (PK) | name                   | author_id (FK)  |
+---------+------------------------+-----------------+
| 1       | Code Sexy              | 2               |
| 2       | Code Dutch             | 3               |
| 3       | Code Bright            | 1               |
+----------------------------------+-----------------+

We have added our first foreign key. This is the author_id field. Once again it's an integer field, but it isn't used to identify rows on this table, instead it's used to identify related rows on another table. A foreign key field is normally used to identify a primary key on an adjacent table, however in some circumstances it can be used to identify other fields.

Do you see how the addition of a foreign key has created a relationship between the author and a book? Our author_id foreign key references the primary key within the Author table. Take a closer look at row three of the book table. Code Bright has an author_id of 1. Now look at row 1 of the Author table and we will see Dayle Rees. This means that Code Bright was written by Dayle Rees. It's as simple as that.

Why didn't we place the foreign key on the Author table?

Well, an Author could have many books, couldn't they? For example, take a look at this relationship.

authors

+---------+------------------------+
| id (PK) | name                   |
+---------+------------------------+
| 1       | Dayle Rees             |
| 2       | Matthew Machuga        |
| 3       | Shawn McCool           |
+----------------------------------+

books

+---------+------------------------+-----------------+
| id (PK) | name                   | author_id (FK)  |
+---------+------------------------+-----------------+
| 1       | Code Sexy              | 2               |
| 2       | Code Dutch             | 3               |
| 3       | Code Bright            | 1               |
| 4       | Code Happy             | 1               |
+----------------------------------+-----------------+

Notice how Dayle Rees, I mean, me, notice how I... oh dear I've ruined this sentence. Notice how there are two books belonging to myself. Both 'Code Happy' and 'Code Bright' have an author_id value of 1. This means that they were both written by Dayle Rees.

Now let's try to express the above example with the foreign key on the Author table instead.

authors

+---------+------------------------+---------------+-----------+
| id (PK) | name                   | book_one (FK) | book_two  |
+---------+------------------------+---------------+-----------+
| 1       | Dayle Rees             | 3             | 4         |
| 2       | Matthew Machuga        | 1             | null      |
| 3       | Shawn McCool           | 2             | null      |
+----------------------------------+---------------+-----------+

books

+---------+------------------------+-----------------+
| id (PK) | name                   | author_id (FK)  |
+---------+------------------------+-----------------+
| 1       | Code Sexy              | 2               |
| 2       | Code Dutch             | 3               |
| 3       | Code Bright            | 1               |
| 4       | Code Happy             | 1               |
+----------------------------------+-----------------+

As you can see, we have had to add a number of foreign keys to the Author table. Not only that, but because some authors will not have two books, many of the columns will have null values contained within. What happens if we want our authors to have three or four books? We can't keep adding columns, our tables will start to look messy!

Very well, we will keep the foreign key on the Book table.

So why don't we learn the names of these relationship types? It will be useful when we become to implement these relationship types with Eloquent. Here we go.

We have the author_id field on the Book, which identified its single author. This means that it belongs to an Author. That's our first relationship name.

Book belongs_to Author

Relationships also have inverse variations. If a Book belongsto an Author, then this means that an Author hasmany Books. Now we have learned the name of another relationship.

Author has_many Book

If the Author instead only had a single book, but the books table contained the identifying primary key, we would use the has_one relationship type instead.

Now don't forget those three relationship types, but let's go ahead and move on to a fourth. Now we need another example. Hmm... How about a 'favourites' system? Where Users can vote on Book. Let's try to express this using the relationships we have already discovered.

User has_many Book

Book has_many User

The has many is the relationship that created the favourite. We don't need an entire entity to express a favourite, since it will have no attributes. When both the relationship and its inverse relationship are hasmany then we will need to implement a new type of relationship. First of all, instead of saying hasmany we will say belongstomany. This way we won't confuse it with the other relationship. This new type of relationship forms a manytomany relationship, and requires an additional table.

Let's view the table structure.

users

+---------+------------------------+
| id (PK) | name                   |
+---------+------------------------+
| 1       | Dayle Rees             |
| 2       | Matthew Machuga        |
| 3       | Shawn McCool           |
+----------------------------------+

books

+---------+------------------------+
| id (PK) | name                   |
+---------+------------------------+
| 1       | Code Sexy              |
| 2       | Code Dutch             |
| 3       | Code Bright            |
| 4       | Code Happy             |
+----------------------------------+

book_user

+-------------------------+
| id  | user_id | book_id |
+-----+---------+---------+
| 1   | 1       | 2       |
| 2   | 1       | 3       |
| 3   | 2       | 2       |
| 4   | 3       | 2       |
+-----+---------+---------+

Wait a minute, what's that third table?

Well spotted! That would be our join table, or pivot table, or lookup table, or intermediatory table, or doing the hibbity-bibbity table. It has a lot of names. The Laravel documentation tends to refer to them as pivot tables, so I'm going to stick with that. Whenever you need a manytomany relationship you will find a need for a pivot table. It's the database table that links the two entities together by using two foreign keys to define the rows from the other tables.

Looking at the first two rows of the pivot table, we can see that the user 'Dayle Rees' has favourited both 'Code Dutch' and 'Code Bright'. We can also see that the users Matthew Machuga and Shawn McCool have both favourited Code Dutch.

There is an additional type of relationship known as a polymorphic relationship. Due to it's complex nature, and it's long name, we will cover it within a later chapter on advanced Eloquent tactics. We won't need it yet.

That's enough learning. It's time for different learning! Practical learning. Now that we have discovered the variety of relationships available, let's learn how to implement them with Eloquent.

Implementing Relationships

Right, let's set the stage. First we are going to need to construct some tables. Normally I'd create a new migration for each table, but to simplify the examples I will put them all in one.

We are going to build a system using Artists, Albums and Listeners. Listeners are simply users that like to listen to a variety of albums. Let's think about the relationships.

  • An Artist has_many Albums.
  • An Album belongs_to an Artist.
  • A Listener belongstomany Albums.
  • An Album belongstomany Listeners.

We have a simple relationship between an Artist and many Albums, and a many to many relationship between Listeners and Albums. So let's think about our table structure. If an Album belongs_to an Artist then the identifying foreign key for the relationship will exist on the Album table. The many to many relationship will require a pivot table.

We know that Eloquent required a plural form of the model for its table name (unless we specify otherwise), but how do we name the pivot table? The default format for the pivot table is the singular form of the two related models separated by an _ underscore. The order of the instances should be alphabetical. This means that our pivot table is called album_listener.

All foreign key columns follow a similar naming convention. The singular form of the related model appended with _id. Our pivot table will contain both album_id and listener_id. The foreign keys will be unsigned, and we will use the references() and on() schema builder methods to reference other tables.

Let's look at the constructed migration, then we will examine anything that's new in more detail.

<?php

use Illuminate\Database\Migrations\Migration;

// app/datebase/migrations/2013_08_26_130751_create_tables.php

class CreateTables extends Migration {

    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('artists', function($table)
        {
            $table->increments('id');
            $table->string('name', 64);
            $table->timestamps();
        });

        Schema::create('albums', function($table)
        {
            $table->increments('id');
            $table->string('name', 64);
            $table->integer('artist_id')->unsigned();
            $table->foreign('artist_id')->references('id')->on('artists');
            $table->timestamps();
        });

        Schema::create('listeners', function($table)
        {
            $table->increments('id');
            $table->string('name', 64);
            $table->timestamps();
        });

        Schema::create('album_listener', function($table)
        {
            $table->integer('album_id')->unsigned();
            $table->foreign('album_id')->references('id')->on('albums');
            $table->integer('listener_id')->unsigned();
            $table->foreign('listener_id')->references('id')->on('listeners');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('artists');
        Schema::drop('albums');
        Schema::drop('listeners');
        Schema::drop('album_listener');
    }

}

The schema builder entries for the Album, Artist, and Listener are typical of Eloquent model schema definitions, however we have yet to construct a pivot table. We simply create a album_listener table and add two integer fields to act as foreign keys. We don't need timestamps or a primary key since this table simply acts as a 'join' between the two model instances.

Time to create our Eloquent models. Let's start with the Artist.

<?php

class Artist extends Eloquent
{
    // Artist __has_many__ Album
    public function albums()
    {
        return $this->hasMany('Album');
    }
}

Here's something new! Inside our Eloquent model definition we have a relationships method. Let's examine this a little more closely.

public function albums()

The name of the public method doesn't require a strict format. It will serve as a nickname that we can use to refer to the relationship. This method could have just as easily been called relatedAlbums().

return $this->hasMany('Album');

Within the relationship method we return the result of the $this->hasMany() method. This is ony of the many relationship methods that are inherited from the Eloquent base class. The first parameter to the relationship method is the full name of the model to be related. If we decide to namespace our Album model later, we will need to insert the full namespaced class as a parameter.

If our foreign key on the Album table is named differently to the default naming scheme of artist_id then we can specify the alternative name of the column as an optional second parameter to this method. For example:

return $this->hasMany('Album', 'the_related_artist');

So what exactly is being returned? Well don't worry about that for now! Let's finish creating our model definitions first. Next we have the Album model.

<?php

class Album extends Eloquent
{
    // Album __belongs_to__ Artist
    public function artist()
    {
        return $this->belongsTo('Artist');
    }

    // Album __belongs_to_many__ Listeners
    public function listeners()
    {
        return $this->belongsToMany('Listener');
    }
}

The Album table has two relationship methods. Once again, the names of our public methods aren't important. Let's look at the content of the first method.

return $this->belongsTo('Artist');

Since the foreign key exists in this table, we use the $this->belongsTo() method to state that the Album model is related to an Artist. The first parameter once again is the related model name, and once again we can provide an optional second parameter to use an alternative column name.

The second method forms one side of our many to many relationship. Let's take a closer look.

return $this->belongsToMany('Listener');

The $this->belongsToMany() method informs Eloquent that it should look at a pivot table for related models. The first parameter is, once again, the name of the related model including namespace if present. This time we have a different set of optional parameters. Let's construct another example.

return $this->belongsToMany('Listener', 'my_pivot_table', 'first', 'second');

The second, optional parameter is the name of the pivot table to use for related objects. The third and fourth parameters are used to identify alternate naming schemes for the two foreign keys that are used to related our objects within the pivot table.

We have one model left. Let's take a look at the Listener.

<?php

class Listener extends ELoquent
{
    // Listener __belongs_to_many__ Album
    public function albums()
    {
        return $this->belongsToMany('Album');
    }
}

The listener forms the inverse side... well is it really inverse? The listener forms the other side of the many to many relationship. Once again we can use the $this->belongsToMany() method to construct the relationship.

Right then! Our models have been created. Let's create some model instances.

Relating and Querying

First let's create an Artist and a Album, and an association between the two. I'll be using my / routed closures as I believe that they are a nice simply way to demo code.

<?php

// app/routes.php

Route::get('/', function()
{
    $artist = new Artist;
    $artist->name = 'Eve 6';
    $artist->save();

    $album = new Album;
    $album->name = 'Horrorscope';
    $album->artist()->associate($artist);
    $album->save();

    return View::make('hello');
});

Wait, what's this new line?

$album->artist()->associate($artist);

Let's break the method chain down. Here's the first method.

$album->artist();

Do you remember that method? That's the relationship method that we created on the Album object. So what exactly does it return? The method returns an instance of the Eloquent query builder, just like the the one that we used in a previous chapter.

However, this query builder instance will have some constraints placed on it for us. The current set of results represented by the query builder will be the related Artists (in this case, a collection of one) to our Album.

It's the same as the following.

Artist::where('album_id', '=', __OUR__CURRENT__ALBUM__);

That's handy right? We could chain on more queries if we wanted to. For example:

$album->artist()->where('genre', '=', 'rock')->take(2);

Be sure to remember that the query builder required a trigger method at the end of the join to return a result collection. Like this:

$album->artist()->where('genre', '=', 'rock')->take(2)->get();

This also means that we can retrieve a full collection of related artists by performing the following.

$album->artist()->get();

Or, a single instance using the first() trigger method.

$album->artist()->first();

Oh the flexibility! Right, let's take a look at our earlier example.

$album->artist()->associate($artist);

I haven't seen associate before!

Don't panic! Just because it's new doesn't mean it's evil. Only spiders are evil. Also Kevin Bacon.

The associate method is a helper method available on relationships to create the relation between two objects. It will update the foreign key for the relationship accordingly.

This means that by passing our $artist instance to the associate() method we will have updated the artist_id column of our Album table row with the ID of the Artist. If we wanted to, we could also handle the foreign key directly.

Here's an example.

<?php

// app/routes.php

Route::get('/', function()
{
    $artist = new Artist;
    $artist->name = 'Eve 6';
    $artist->save();

    $album = new Album;
    $album->name = 'Horrorscope';
    $album->artist_id = $artist->id;
    $album->save();

    return View::make('hello');
});

Both code snippets would have the same result, and a relationship will have been created between the two objects.

Don't forget to save() the model that you intend to relate, before passing it to the associate() method. The reason for this is that the model instance will require a primary key to create the relation, and the model will only receive a primary key value when it has been saved.

Relating objects that form many to many relationships is handled in a slightly different way. Let's take a closer look by introducing the Listener model.

<?php

// app/routes.php

Route::get('/', function()
{
    $artist = new Artist;
    $artist->name = 'Eve 6';
    $artist->save();

    $album = new Album;
    $album->name = 'Horrorscope';
    $album->artist()->associate($artist);
    $album->save();

    $listener = new Listener;
    $listener->name = 'Naruto Uzumaki';
    $listener->save();
    $listener->albums()->save($album);

    return View::make('hello');
});

Let's focus on this bit:

$listener = new Listener;
$listener->name = 'Naruto Uzumaki';
$listener->save();
$listener->albums()->save($album);

After we have populated our new Listener model instance, we must save() it. This is because we need it to have a primary key before we can create an entry in our pivot table.

This time, instead of using the associate() method on our relationship, we instead use the save() method, passing the object to relate as the first parameter. The effect is the same, only this time the pivot table will be updated to define the relationship.

If you would like to associate a model by its primary key directly, you can use the attach() method which accepts the primary key as the first parameter. For example.

$album->artist()->attach(2);

To remove a relationship between two objects you can use the detach() method on the relationship. Just pass either a primary key, or an object as the first parameter.

For example:

<?php

// app/routes.php

Route::get('/', function()
{
    $album = Album::find(5);
    $listener = Listener::find(2);
    $album->listeners()->detach($listener);

    return View::make('hello');
});

We can remove all associations for an object that has a many to many relationship by calling the detach() method with no parameter on the relationship method.

Here's an example.

$album->listeners()->detach();

Now the album will have no related listeners.

Eloquent is a beautiful and vast topic full of many amazing features, but I don't want to burden you with all of them right now. It will only cause you to forget some of the basics that we have just learned.

Right now we have all the knowledge we need to start building a basic application. We will be building a system to manage the many Playstation 3 games that I posses. I'd hate to forget which ones I own!

If you're still hungering for more Eloquent then don't panic. We will return to this wonderfully elegant ORM solution in a later chapter.

My books are available online for free to encourage learning. However, if you'd like for me to keep writing, then please consider buying a digital copy over at Leanpub.com.

It's available in PDF, ePub, and Kindle format, and contains a bunch of extras that you won't find on the site. I have a full-time job, and I write my books in my spare time. Please consider buying a copy so that I can continue to write new books from the comfort of my sofa!