Home » Code » Laravel 4 Eloquent Model Relationships

Laravel 4 Eloquent Model Relationships

Posted by on June 10th, 2013

Laravel 4 Eloquent Model Relationships
Last week I started looking more in-depth at Model relationships in Laravel 4, including what are the different types of relationship and how each of them work.

I also created the Twitter following model logic that will allow Cribbb users to follow each other.

This week I’m going to finish off creating the Model relationships and then look at writing tests.

Relations to be made

So if you can remember back from last week, I still need to make the Cliques model and memberships and I need to create the polymorphic relationship for comments.

It also occurred to me that Posts should be related to Cliques. The theory is, Users can create Posts in a Clique, or they can post a Status on their own page. I won’t over-complicate things by creating the Status model just yet.

Creating the Clique model and memberships

So a quick reminder of what a Clique is. A Clique is essentially a group on Cribbb. Users can become members of a Clique. A Clique will have many Posts. A Post will belong to a Clique and will be created by a User.

Create the Clique Model

So the first thing to do is to create the Clique model. To make the migration file, I will run the following command:

$ php artisan generate:migration create_cliques_table --fields="name:string"

This will create the following migration file:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;

class CreateCliquesTable extends Migration {

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

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

}

To create the table, run the migrate command from Terminal:

$ php artisan migrate

Next I’ll make a really quick Clique.php model file:

<?php
class Clique extends Eloquent {}

Create the Clique membership relationship

Next we need to create the pivot table to record the relationship between Users and Cliques. Remember, because this is a many-to-many relationship, we need a separate table to record the relationship.

To create the migration, run the following command from Terminal:

$ php artisan migrate:make create_clique_user_table --create --table=clique_user

Add the two integer columns for user_id and clique_id. Your migration file should look like this:

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateCliqueUserTable extends Migration {

  /**
   * Run the migrations.
   *
   * @return void
   */
  public function up()
  {
    Schema::create('clique_user', function(Blueprint $table)
    {
      $table->increments('id');
      $table->integer('clique_id');
      $table->integer('user_id');
      $table->timestamps();
    });
  }

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

}

Again, run the migrate command from Terminal:

$ php artisan migrate

Next open up the User.php and the Clique.php models and add the following methods:

In User.php:

/**
 * Clique relationship
 */
public function clique(){
  return $this->belongsToMany('Clique');
}

In Clique.php

/**
 * User relationship
 */
public function users(){
  return $this->belongsToMany('User');
}

And finally, because a Post must belong to a Clique, I need to add a column to the posts table for the clique_id.

Run the follow command to create the migration:

$ php artisan generate:migration add_clique_id_posts_table --fields="clique_id:integer"

Now add the following two methods to Post.php and Clique.php:

In Post.php

/**
 * Clique relationship
 */
public function clique()
{
  return $this->belongsTo('Clique');
}

And in Clique.php

/**
 * Post relationship
 */
public function posts()
{
  return $this->hasMany('Post');
}

Creating polymorphic comments

And finally I need to create a Comments model that will have polymorphic relationships with the Post model and (eventually) the Status Model.

So first, run the following migration to create the table:

$ php artisan generate:migration create_comments_table --fields="body:text, commentable_id:integer, commentable_type:string"

Which will create the following migration:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;

class CreateCommentsTable extends Migration {

  /**
   * Run the migrations.
   *
   * @return void
   */
  public function up()
  {
    Schema::create('comments', function(Blueprint $table) {
     $table->increments('id');
     $table->text('body');
     $table->integer('commentable_id');
     $table->string('commentable_type');
     $table->timestamps();
    });
  }

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

}

Once again, run the migrate command to run the migration:

$ php artisan migrate

Next create a Comment.php and copy the following class:

<?php

class Comment extends Eloquent {

  public function commentable()
  {
    return $this->morphTo();
  }

}

And finally add the follow method to the Post.php model:

/**
 * Comment relationship
 */
public function comments()
{
  return $this->morphMany('Comment', 'commentable');
}

Now when I create future Models that also have comments, I can just add the last method to the new Model so that it also has comments.

Writing Model relationship tests

Now to write some tests! Ok, ok so writing the tests once you have already wrote the majority of the code isn’t strictly speaking good Test Driven Development (What is Test Driven Development?) practice, but you get the point. Personally, I think you should be wise to follow best practices but not to blindly follow every rule by the book.

So now I’m going to step through all of the changes that I’ve made and write appropriate tests to ensure everything is working correctly.

A Post must belong to a clique

As I mentioned above, I recently decided that a Post should belong to a Clique. Up until this point a Post only belonged to a User. Because I haven’t added this requirement, all of my Post tests pass correctly.

In order to make a Post require a Clique Id, I simply have to add a new validation rule:

'clique_id' => 'required|numeric'

Now if you run phpunit you should find that two of the Post tests have failed. This is because they now depend on having a clique_id in order to save.

To fix this, all we have to do is to ensure that a clique_id is always set when creating a new Post.

First I added a factory array to Clique.php:

/**
 * Factory
 */
public static $factory = array(
  'name' => 'string'
);

Next I added a new key / value pair to the Post factory:

'clique_id' => 'factory|Clique'

And now I can create a new Clique in my tests and assign it to the Post like this:

// Create a new Clique
$clique = FactoryMuff::create('Clique');

// Set the clique_id
$post->clique_id = $clique->id;

Users can follow each other

Next I will write tests to ensure that the follower model is working correctly.

This is what I wrote for testing follower and following relationships. It should be pretty self explanatory, I’m just creating four users and then making different combinations of relationships:

  /**
   * Test a user can follower other users
   */
  public function testUserCanFollowerUsers()
  {
    // Create users
    $philip = FactoryMuff::create('User');
    $jack = FactoryMuff::create('User');
    $ev = FactoryMuff::create('User');
    $biz = FactoryMuff::create('User');

    // First set
    $philip->follow()->save($jack);

    // First tests
    $this->assertCount(1, $philip->follow);
    $this->assertCount(0, $philip->followers);

    // Second set
    $jack->follow()->save($ev);
    $jack->follow()->save($biz);

    // Second tests
    $this->assertCount(2, $jack->follow);
    $this->assertCount(1, $jack->followers);

    // Third set
    $ev->follow()->save($jack);
    $ev->follow()->save($philip);
    $ev->follow()->save($biz);

    // Third tests
    $this->assertCount(3, $ev->follow);
    $this->assertCount(1, $ev->followers);

    // Fourth set
    $biz->follow()->save($jack);
    $biz->follow()->save($ev);

    // Fourth tests
    $this->assertCount(2, $biz->follow);
    $this->assertCount(2, $biz->followers);
  }

The eagle eyed amongst you will notice that at the minute you could actually follow yourself. This is a bit of a weird situation, but it can be easily addressed by writing our own methods to create the following relationships. I’ll address this in the future with a more specific article on that problem.

Creating a Clique

Next I will write some basic tests for the Clique model. This is really basic stuff at the minute, but in the future I will add a lot more of the required functionality.

For now though I will just set the name of the Clique to be required, and I will write a test to ensure that it works correctly.

First set the $fillable and the $rules array property in the model:

/**
 * Properties that can be mass assigned
 *
 * @var array
 */
protected $fillable = array('name');

/**
 * Ardent validation rules
 */
public static $rules = array(
  'name' => 'required',
);

Also ensure that the model extends Ardent rather than Eloquent.

Next the test to ensure that the name is required is simply:

public function testNameIsRequired()
{
	// Create a new Clique
  $clique = new Clique;

  // Post should not save
  $this->assertFalse($clique->save());

  // Save the errors
  $errors = $clique->errors()->all();

  // There should be 1 error
  $this->assertCount(1, $errors);

  // The error should be set
  $this->assertEquals($errors[0], "The name field is required.");
}

Becoming a member of a Clique

Next I will test becoming a member of a Clique. This is pretty straight forward as you should recognise that it’s basically the same process as following another user:

public function testCliqueUserRelationship()
{
  // Create a new Clique
  $clique = FactoryMuff::create('Clique');

  // Create two Users
  $user1 = FactoryMuff::create('User');
  $user2 = FactoryMuff::create('User');

  // Save Users to the Clique
  $clique->users()->save($user1);
  $clique->users()->save($user2);

  // Count number of Users
  $this->assertCount(2, $clique->users);
}

Adding a comment to a post

Finally, adding a comment to a post is very similar to all of the other Model relationship tests so far:

/**
 *  Test adding new comment
 */
public function testAddingNewComment()
{
  // Create a new Post
  $post = FactoryMuff::create('Post');

  // Create a new Comment
  $comment = new Comment(array('body' => 'A new comment.'));

  // Save the Comment to the Post
  $post->comments()->save($comment);

  // This Post should have one comment
  $this->assertCount(1, $post->comments);
}

Conclusion

So hopefully as you can see, creating Model relationships is relatively straight forward when you have a firm understanding of each of the different types of relationships and when they should be applied. Understanding these very simple relationship types will enable you to build very powerful applications.

As you can probably tell, much of this functionality and the associated tests are still very basic. I don’t think it is a good use of time to really focus on building every eventuality at this stage, as there is more value in just fleshing stuff out.

Update

After thinking about these relationships some more, I decided that it was too complicated. I really want to keep this as simple as it can possibly be. I decided to completely get rid of the concept of “Cliques”. Having this as an additional object in the application was just too messy.

Don’t be afraid to cut stuff out that doesn’t feel right. It’s always better to remove stuff that isn’t right rather than have an overcomplicated product.

This is a series of posts on building an entire Open Source application called Cribbb. All of the tutorials will be free to web, and all of the code is available on GitHub.

So far I’ve covered:

  1. Getting started with Laravel 4
  2. Laravel 4 Migrations
  3. Setting up your first Laravel 4 Model
  4. Getting started with testing Laravel 4 Models
  5. Laravel 4 Fixture Replacement with FactoryMuff
  6. Creating the Twitter following model in Laravel 4

The next post in this series is Setting up Vagrant with Laravel 4

Philip Brown

Hey, I'm Philip Brown, a designer and developer from Durham, England. I create websites and web based applications from the ground up. In 2011 I founded a company called Yellow Flag. If you want to find out more about me, you can follow me on Twitter or Google Plus.

Join the Culttt

Become an insider and join the Culttt

  • Tolu

    Hi Philip,

    Nice job you’re doing here man! Hope you know you have great following? I look forward to Monday mornings on your blog, and I’m so sure I ain’t alone! I would be selfish to wish it were twice a week instead… Keep it up!

    Kudos!!!

    • http://culttt.com/ Philip Brown

      Thanks Tolu :D that’s very kind of you to say so! I’m glad my posts are helping you out :)

  • R. S. Harding

    I was getting a PHP Fatal error when trying the PHPUnit test to check that the clique_id requirement failed: https://github.com/laravelbook/ardent/issues/36

    The fix requires updating the FixtureMuff from Zizaco GitHub

    https://github.com/Zizaco/factory-muff/commit/9f5a8a9ead73c5307187538736cd1a30de1b60b2

    All I had to do is run ‘composer update’ and it was fixed!

    • http://culttt.com/ Philip Brown

      Good thinking! That’s usually the first thing I try if I’m getting a weird error.

  • danny

    how to make use morphOne()?

    • http://culttt.com/ Philip Brown

      Do you mean morphoTo()? I would have a read through the documentation http://laravel.com/docs/eloquent#polymorphic-relations

      • Trip

        morphOne() is a thing. Check the API.

        • http://culttt.com/ Philip Brown

          Ah I see, yeah it’s just for what type of relationship you want to use, “One” or “Many”.

  • Tipo

    Great tutorials here!

    But I have problem to understand your models.

    In the chapter:”Create the Clique membership relationship”
    you want to add two methods to the models User.php and Clique.php.
    Then just a half page later you want to add again two methods but with the same name to the same models (User.php and Clique.php).
    That will overwrite it, doesn’t it?
    Or do you mean two different files?

    • http://culttt.com/ Philip Brown

      Thank you :)

  • Rafael Adel

    I’m wondering after your update about removing the clique section. What part should i follow exactly in this very tutorial ?

    • http://culttt.com/ Philip Brown

      Hi Rafael, you just don’t need to create any of the Clique stuff. However, try and make your own application using these methods. You will understand these tutorials much more if you use the theory to build your own application rather than just copying my code step-by-step :)

      • Rafael Adel

        Ok, I’ve just created the Comment model for the tutorial.
        Is this gonna be sufficient to carry on to the future tutorials ?

        • http://culttt.com/ Philip Brown

          Yeah, that’s right! :)

  • Pingback: Creating forms in Laravel 4 | Culttt

  • markalanevans

    Cool. How about an example with a belongsToMany relationship. Update? Insert? Should we create a model for this join table?

    • http://culttt.com/ Philip Brown

      Thank you :)

      The User / Clique relationship is belongs to many. Normally you don’t need a model for a pivot table, however I’m sure there are exceptions when you would.

      • http://anthonyvipond.com/ Anthony Vipond

        Good job here, Philip. Highly enjoyable reading your stuff. No you wouldn’t need a model (in file form) for that as its built into Laravel. From the documentation:

        $user = User::find(1);

        foreach ($user->roles as $role)
        {
        echo $role->pivot->created_at;
        }

        Notice that each Role model we retrieve is automatically assigned a pivot attribute. This attribute contains a model representing the intermediate table, and may be used as any other Eloquent model.

        By default, only the keys will be present on the pivot object. If your pivot table contains extra attributes, you must specify them when defining the relationship:

        return $this->belongsToMany(‘Role’)->withPivot(‘foo’, ‘bar’);

        Now the foo and bar attributes will be accessible on our pivot object for theRole model.

        • http://culttt.com/ Philip Brown

          Yeah spot on Anthony :)

  • Pavel Rogala

    One question. Obviously the comment belongs to the post but would the comment also need to belong to the User model to retrieve data such as the commenter’s profile pic and name?

    • http://culttt.com/ Philip Brown

      Yeah, absolutely! I haven’t implemented that in this article, but you would definitely want to associate a comment with it’s user.

  • Luis Rolando Barzola

    Nice job Phillip.

    I was this error:

    1) FollowerTest::testUserCanFollowerUsers

    IlluminateDatabaseQueryException: SQLSTATE[23000]: Integrity constraint violation: 19 user_follows.created_at may not be NULL (SQL: insert into “user_follows” (“follow_id”, “user_id”) values (2, 1))

    I fixed it adding withTimestamps to follow:

    public function follow(){
    return $this->belongsToMany(‘User’, ‘user_follows’, ‘user_id’, ‘follow_id’)->withTimestamps();
    }

    • http://culttt.com/ Philip Brown

      Glad you got it sorted Luis :) Yeah Laravel requires you to specify if you want timestamps on pivot tables.

  • Kiash

    Nice bro! We want more Laravel tutorials. Please write for us…..

    • http://culttt.com/ Philip Brown

      Thanks :) I only write for Culttt though, sorry :)

  • Paul

    Hey Phillip, Just wondering if you think the Polymorphic relations is more efficient than using pivot tables? The database will definitely be cleaner with less tables, but concerned about the fact it goes against best practice for not being able to assign foreign keys.

    • http://culttt.com/ Philip Brown

      Hmm, I think it’s probably best to choose the type of relationship that best suits your needs rather than reducing the number of tables. Your code doesn’t really care how the data is persisted, but changing a database structure at some point in the future will be a right nightmare! :(

  • Johan Nyberg

    Hi Philip, thanks again for your great tutorial!

    Just so you know, there’s a to missing in the artisan call above:

    php artisan generate:migration add_clique_id_to_posts_table –fields=”clique_id:integer”

    • http://culttt.com/ Philip Brown

      Thanks Johan :)

  • Michał ‘Mix’ Roszka

    Great job with the Laravel tutorials, Philip! I have one suggestion regarding the polymorphic comments.

    Now when I create future Models that also have comments, I can just add
    the last method to the new Model so that it also has comments.

    We could have follow the DRY principle here and have all commentable models extending an abstract class Commentable implementing the method comments.

    • http://culttt.com/ Philip Brown

      Thank you :)

      Yeah, that sounds like a great idea, especially if there were to be many shared methods.

  • Pingback: Eloquent tricks for better Repositories | Culttt

  • Pingback: Multi-Tenancy in Laravel 4 | Culttt

Supported by