Codeigniter Doctrine In this episode of the series, we will continue building our Message Board project. First we will have a quick overview of Doctrine Model Relationships. Then we will create Models for our Message Board, such as: Forums, Threads, Posts etc… with proper relationships. Finally we will learn how to add data into such Models.

"CodeIgniter and Doctrine from Scratch" Series:

download_code

Doctrine Model Relationships (a quick overview)

One of the key features of Doctrine is the handling of Relationships between Models. At this point in our project, we will start utilizing it.

On a typical Message Board:

  • There are Forums
  • Forums have Threads
  • Threads have Posts
  • Users have Posts

ci_doctrine_day6_1

We will learn how to define these relationships in our Models. Doctrine supports the following types of relationships:

  • One to Many (or Many to One)
  • One to One
  • Many to Many
  • Tree Structure
  • Nest Relations

Today we will be talking about only the first two.

Foreign Keys

To setup relationships between models (and tables in the database), we need to have certain fields in our tables. These are used as Foreign Keys.

For example, if each Post belongs to a User, we need a user_id field in the Post table. We can specify this in our model:

class Post extends Doctrine_Record {

	public function setTableDefinition() {
		$this->hasColumn('content', 'string', 65535);
		$this->hasColumn('user_id', 'integer');
	}

}

But to specify the type of relationship, we are going to have to do more than that.

One to Many Relationships

Just like the name suggests, this creates a relationship between one instance of a Model and multiple instances of another Model.

For example: one User has many Posts.

In Doctrine, this kind of relationship is setup using the hasOne() and hasMany() functions.

Post Example:

class Post extends Doctrine_Record {

	public function setTableDefinition() {
		$this->hasColumn('content', 'string', 65535);
		$this->hasColumn('user_id', 'integer');
	}

	public function setUp() {
		$this->hasOne('User', array(
			'local' => 'user_id',
			'foreign' => 'id'
		));
	}

}

hasOne() says that the Post is associated with one User. The first parameter is the Model name and the second parameter is an array that sets the foreign keys.

It links the two Models by the user_id field in the local Model (i.e. Post), with the id field in the foreign Model (i.e. User).

Now we can access the User object for a post like this: $post->User. We’ll see more of that later.

User Example:

class User extends Doctrine_Record {

	public function setTableDefinition() {
		$this->hasColumn('username', 'string', 255, array('unique' => 'true'));
		$this->hasColumn('password', 'string', 255);
	}

	public function setUp() {
		$this->hasMany('Post as Posts', array(
			'local' => 'id',
			'foreign' => 'user_id'
		));
	}
}

On this side of the relationship, there are no additional fields for the foreign key. But we still define it by the hasMany() call.

The first parameter looks a bit different this time. When we say Post as Posts, we link to the Model named Post, but we name the property Posts.

This way we are going to access it like $user->Posts, instead of $user->Post. This is just a matter of preference, because it sounds better.

Note: As a rule of thumb, the Model that has the hasOne() call is the one with the foreign key field. (Post.user_id in this case).

One to One Relationships

This type of relationship is used when each Model is linked to only one instance of the other Model.

For example, if you have separate Models/Tables for User and Email, and if you want each User to have only one Email, you might use this.

The syntax is exactly the same as the One to Many Relationships, but this time both Models call the hasOne() function, instead of hasMany().

for more info

This is all we needed to know to continue our project for now. But if you want to find out about all types of relationships in Doctrine, you can read the whole documentation section on this subject here.

Let’s Create Some Models

Let’s use what we just learned to create our new Models.

Note: I would like to build this project similar to the CodeIgniter Message Board. They also seem to have Categories, that contain multiple Forums, so we are going to create a Model for that too.

Category Model

  • Create: system/application/models/category.php
<?php
class Category extends Doctrine_Record {

	public function setTableDefinition() {
		$this->hasColumn('title', 'string', 255);
	}

	public function setUp() {
		$this->hasMany('Forum as Forums', array(
			'local' => 'id',
			'foreign' => 'category_id'
		));
	}
}

Looks simple. We have a Category Model that has a title, and can have many Forums. Also we are expecting that the Forum Model to have a field named category_id.

Forum Model

  • Create: system/application/models/forum.php
<?php
class Forum extends Doctrine_Record {

	public function setTableDefinition() {
		$this->hasColumn('title', 'string', 255);
		$this->hasColumn('description', 'string', 255);
		$this->hasColumn('category_id', 'integer', 4);
	}

	public function setUp() {
		$this->hasOne('Category', array(
			'local' => 'category_id',
			'foreign' => 'id'
		));
		$this->hasMany('Thread as Threads', array(
			'local' => 'id',
			'foreign' => 'forum_id'
		));
	}

}

As expected, we have a category_id field for the foreign key.

Also we have 2 relationships now. First one is with Category Model, as Forums have (or belong to) a single Category. The second one is with Thread Model. Each Forum can have many Threads.

Thread Model

  • Create: system/application/models/thread.php
<?php
class Thread extends Doctrine_Record {

	public function setTableDefinition() {
		$this->hasColumn('title', 'string', 255);
		$this->hasColumn('forum_id', 'integer', 4);
	}

	public function setUp() {
		$this->hasOne('Forum', array(
			'local' => 'forum_id',
			'foreign' => 'id'
		));
		$this->hasMany('Post as Posts', array(
			'local' => 'id',
			'foreign' => 'thread_id'
		));
	}

}

Post Model

  • Create: system/application/models/post.php
<?php
class Post extends Doctrine_Record {

	public function setTableDefinition() {
		$this->hasColumn('content', 'string', 65535);
		$this->hasColumn('thread_id', 'integer', 4);
		$this->hasColumn('user_id', 'integer', 4);
	}

	public function setUp() {
		$this->actAs('Timestampable');
		$this->hasOne('Thread', array(
			'local' => 'thread_id',
			'foreign' => 'id'
		));
		$this->hasOne('User', array(
			'local' => 'user_id',
			'foreign' => 'id'
		));
	}

}

Notice this time we have 2 foreign keys. Because a Post belongs to a Thread, and also belongs to a User.

User Model

We already have this Model, we are just making changes.

  • Edit: system/application/models/user.php
<?php
class User extends Doctrine_Record {

// ...

	public function setUp() {
		$this->setTableName('user');
		$this->actAs('Timestampable');
		$this->hasMutator('password', '_encrypt_password');

		$this->hasMany('Post as Posts', array(
			'local' => 'id',
			'foreign' => 'user_id'
		));
	}
//...
}

Create the Tables

Remember our Controller that creates Tables from Models? It’s time to use that again.

ci_doctrine_day6_2

Looks great!

Now let’s see how we can add some data using these Models.

Creating Records

We are going to write a quick test controller to demonstrate how we can add data.

  • Create: system/application/controllers/test.php
<?php
class Test extends Controller {

	function index() {

		$category = new Category();
		$category->title = "The CodeIgniter Lounge";
		$category->save();

		$forum = new Forum();
		$forum->title = "Introduce Yourself!";
		$forum->description = "Use this forum to introduce yourself to the CodeIgniter community, or to announce your new CI powered site.";

		$forum->Category = $category;

		$forum->save();
	}

}

Do you see how intuitive it is?

$forum->Category represents the relationship that the Forum object has with the Category Model. We can directly assign a Category object to it to link them.

Let’s expand this so we have a bit more data.

  • Edit: system/application/controllers/test.php
<?php
class Test extends Controller {

	function index() {

		$category = new Category();
		$category->title = "The CodeIgniter Lounge";

		$forum = new Forum();
		$forum->title = "Introduce Yourself!";
		$forum->description = "Use this forum to introduce yourself to the CodeIgniter community, or to announce your new CI powered site.";
		// add category to the forum
		$forum->Category = $category;

		$forum2 = new Forum();
		$forum2->title = "The Lounge";
		$forum2->description = "CodeIgniter's social forum where you can discuss anything not related to development. No topics off limits... but be civil.";

		// you can also add the other way around
		// add forum to the category
		$category->Forums []= $forum2;

		// a different syntax (array style)

		$category2 = new Category();
		$category2['title'] = "CodeIgniter Development Forums";

		$forum3 = new Forum();
		$forum3['title'] = "CodeIgniter Discussion";
		$forum3['description'] = "This forum is for general topics related to CodeIgniter";

		$forum4 = new Forum();
		$forum4['title'] = "Code and Application Development";
		$forum4['description'] = "Use the forum to discuss anything related to programming and code development.";

		$category2['Forums'] []= $forum3;
		$category2['Forums'] []= $forum4;

		// flush() saves all unsaved objects
		$conn = Doctrine_Manager::connection();
		$conn->flush();

		echo "Success!";

	}

}

We just created 2 categories, and 2 forums in each of them, in a few different ways.

  • Line 21: We add a Forum to the Category, instead of the other way around. But this time, since $category->Forums contains multiple Forums, we push to it like you would to an array, instead of direct assignment like on line 13.
  • Lines 25-37: Here you can see how we can access all elements of the Doctrine Model objects like array elements. Some people prefer this syntax.
  • Lines 40-41: Instead of calling save() on every single object, we can just use flush() to save everything.

Let’s test this.

You should see the success message:

Success!

Now let’s look at the category table:

ci_doctrine_day6_3

You can see our new 2 categories, with id’s 1 and 2. Let’s see if the forums got the correct associations.

Look at the forum table:

ci_doctrine_day6_4

Everything looks good! The Forums each have proper category_id’s assigned.

Stay Tuned

Today we learned about one of main features of Doctrine. We were able to create foreign key relationships without using any queries whatsoever! That is one of the nice things about using an ORM like Doctrine. I hope you are able to see how much time you can save by developing like this.

In the next episode we will continue on our project, with a little more CodeIgniter stuff.

See you next time!

"CodeIgniter and Doctrine from Scratch" Series: