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:
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

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.
- Go to: http://localhost/ci_doctrine/doctrine_tools/create_tables
- Press the button, and it should create the tables.
- Look at phpmyadmin at: http://localhost/phpmyadmin.

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:

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:

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!

#1 by shin on November 13th, 2009
| Quote
Another excellent post. I’d like to see more article like this.
I’d like to request a simple project management with CI and doctorine for near future. Project management also has different users and projects and I think it will suit with CI/Doc combination.
#2 by BB on November 13th, 2009
| Quote
these are really seriously cool tutorials.
could you pls fix the css so I can print ‘em out correctly?
many thx.
#3 by Drew Spencer on November 13th, 2009
| Quote
Loving these tutorials – just devoured them all on a morning!
Can’t wait for the next installment. Keep up the great work.
#4 by BradM on November 13th, 2009
| Quote
@BB have you ever tried Readability?
http://lab.arc90.com/experiments/readability/
Its great for printing and reading posts online. Gets rid of the clutter. No installation, works great.
#5 by BB on November 15th, 2009
| Quote
thanks for the tip BradM.
you saved the day
#6 by Burak on November 15th, 2009
| Quote
I just tested it, it seems like it deletes the code sections, which is not good.
I’m using a free wordpress theme, it didn’t come with a print CSS. I’ll look into it when I get chance, to see if there is another solution.
#7 by Mike on February 10th, 2010
| Quote
it seems to print ok in IE
By the way, great work. This is probably the best tutorial on doctrine available, with or without CI.
#8 by zack on November 13th, 2009
| Quote
Can’t get enough. You should write a book.
#9 by Emre on November 14th, 2009
| Quote
One more great tutorial by you, Burak! Hats off to you..
I think there is a typo at the begining of the tutorial. It is where you are talking about one-to-many relationship. At the User model code:
9 $this->hasMany(’Post as Posts’, array(
10 ‘local’ => ‘id’,
11 ‘foreign’ => ‘post_id’
12 ));
foreign value seems ‘post_id’, but it should be ‘user_id’ I think.
#10 by Burak on November 14th, 2009
| Quote
Nice catch, thank you. It’s corrected now.
#11 by Kerran on November 14th, 2009
| Quote
Bring on day 7
#12 by Steve Larch on November 15th, 2009
| Quote
Great tutorials, Burak. I just started working with PHP frameworks this weekend. I was beginning to think I’d picked the wrong horse (CodeIgniter) until I came across your site. Thanks for the great information!
#13 by Emre on November 15th, 2009
| Quote
I wonder if we will be able to make nested posts for this example project. I mean hiearchical posts that one post may have children posts which are reply for parent post?
As an example: your wordpress’ hierarchical comment structure..
It would be very useful to learn Doctrine’s nested set structure for handling trees in database.
#14 by Burak on November 15th, 2009
| Quote
Yea, Doctrine supports those kinds of structures. There are a few different ways of handling it. I’m trying to think of something that will use it in this project. It might be either nested posts or nested forums.
#15 by Emre on November 15th, 2009
| Quote
Thank you for the explanation. We are really waiting for it.
#16 by chonlatee on November 15th, 2009
| Quote
sorry my English , Thank you for your article it’s useful and i hope you will post again. i am waiting
#17 by Drew Spencer on November 16th, 2009
| Quote
Burak, when is part 7 coming out?
Also, I was wondering if you might do something with jQuery as well? These 3 combined would be really useful.
Thanks again.
#18 by Albert on November 16th, 2009
| Quote
Very carefully and thoroughly developed tutorial. Excellent introduction to Doctrine and Code Igniter.
Hope you’ll consider contacting their project group and offering it for their tutorial library.
Thank you for taking the time to produce these.
#19 by Carlos on November 16th, 2009
| Quote
God, you really know how to explain hard to understand things.
For me your tutorials are top quality learning material.
Please keep up with your hard work.
Thankyou!
#20 by yfi on November 19th, 2009
| Quote
Wow, these are excellent. They read like a novel and you have a real gift for clarity.
Now, I myself am wondering how to create a search function in CI. It’s such a common feature in web applications but I haven’t seen many tutorials that would tell the story the way you do.
After reading episode 1, I browsed the doctrine website and noticed the searchable and taggable extensions. Is there any way you could cover those?
Thank You.
#21 by Burak on November 19th, 2009
| Quote
Searchable might be useful in this project, but I’m not sure how Taggable would apply to a Message Board.
#22 by ABACA on November 30th, 2009
| Quote
That’s great. Just the way I need it in my project. Can’t wait to see the next tuts. Thanks.
#23 by Kamil Grzegorczyk on December 13th, 2009
| Quote
Where CRUD methods should be called?
When I was using AR – I was calling model functions from controller to delete a record for example.
Now I dont know how to do it using doctrine. For example deleting from controller works – but i don;t think that this is a good idea – what should i do to call function model->delete from controller – old way doesn;t work…
#24 by Burak on December 13th, 2009
| Quote
I am not sure if I understand the question correctly. But to delete a User with a given id for example, you can do this:
Doctrine::getTable(’User’)->find($user_id)->delete();
If you already have the user object, you can directly call delete():
$user->delete();
#25 by Kamil Grzegorczyk on December 13th, 2009
| Quote
yes I was doing like that and this method worked for me – but I’m calling it from inside a controller – is it a good way? I always thought that this kind of operations (operating on database) should be done in model.
So this is the diffrence between Active record and ORM approach?
#26 by Kamil Grzegorczyk on December 13th, 2009
| Quote
OK, Now I see that I got lost – but now after little stop I think that this is the same as old approach (using AR directives) but everything is managed for me by Doctrine methods – so it is actually done in model…
Thanks
#27 by Burak on December 13th, 2009
| Quote
Yeah, delete() is already implemented in the Doctrine class, so it is already abstracted.
But if you need to do complex DQL queries, you can put that in a function inside the Model. There is an example of that in the Day 9 article that I just posted.
#28 by Naim on December 15th, 2009
| Quote
COULD SOMEONE EXPLAIN THIS ERROR PLEASE?
Fatal error: Uncaught exception ‘Doctrine_Export_Exception’ with message ‘SQLSTATE[HY000]: General error: 1005 Can’t create table ’safarista-cms.#sql-160c_4d’ (errno: 150). Failing Query: “ALTER TABLE post ADD CONSTRAINT post_thread_id_thread_id FOREIGN KEY (thread_id) REFERENCES thread(id)”. Failing Query: ALTER TABLE post ADD CONSTRAINT post_thread_id_thread_id FOREIGN KEY (thread_id) REFERENCES thread(id)’ in C:\wamp\www\safarista-cms\application\plugins\doctrine\lib\Doctrine\Export.php:1219 Stack trace: #0 C:\wamp\www\safarista-cms\application\plugins\doctrine\lib\Doctrine\Export.php(1100): Doctrine_Export->exportClasses(Array) #1 C:\wamp\www\safarista-cms\application\plugins\doctrine\lib\Doctrine\Core.php(889): Doctrine_Export->exportSchema(NULL) #2 C:\wamp\www\safarista-cms\application\controllers\doctrine_tools.php(11): Doctrine_Core::createTablesFromModels() #3 C:\wamp\www\safarista-cms\system\codeigniter\CodeIgniter.php(236): Doctrine_Tools->create_tables() #4 C:\wamp\www\safarista-cms\index.php(115): require_onc in C:\wamp\www\safarista-cms\application\plugins\doctrine\lib\Doctrine\Export.php on line 1219
#29 by Burak on December 15th, 2009
| Quote
That happens when you try to create a foreign key between two columns that are not the same exact structure.
For example if one column is INT and the other is BIGINT.. Or if one column is UNSIGNED and the other one isn’t etc..
Seems like your error has to do with post.thread_id and thread.id
#30 by Marcello Romani on January 5th, 2010
| Quote
Hi, the problem is that we are setting up foreign key constraints against automatically created ‘id’ fields.
Remember from the first tutorial that the ‘id’ column is automatically created by Doctrine (in doctrine_pi there is a setting for this).
If you DESCRIBE the Forum table, for example, you’ll see that the ‘id’ column is UNSIGNED. By contrast, when you define the column ‘forum_id’ in the Post model, you probably just wrote ‘integer’. So the end result is a foreign key constraint where one field is ‘integer’ and the other one is ‘integer unsigned’. Thus the error.
Solution: add the ‘unsigned’ option to the foreign key, e.g.:
$this->hasColumn(’forum_id’, ‘integer’, 4, array(’unsigned’ => true));
HTH
#31 by Marcello Romani on January 5th, 2010
| Quote
This is the relevant line in system/application/plugins/doctrine_pi.php:
Doctrine_Manager::getInstance()->setAttribute(Doctrine::ATTR_DEFAULT_IDENTIFIER_OPTIONS, array(’name’ => ‘id’, ‘type’ => ‘integer’, ‘length’ => 4));
#32 by Burak on January 9th, 2010
| Quote
In doctrine_pi.php I put this line too:
Doctrine_Manager::getInstance()->setAttribute(
Doctrine::ATTR_DEFAULT_COLUMN_OPTIONS,
array(’notnull’ => true, ‘unsigned’ => true));
That applies to all columns. So it should already be defaulting to unsigned.
#33 by Naim on December 15th, 2009
| Quote
I would like to know how to implement the EMAIL and GROUPS functionality having enjoyed the way CI-D is relating things. am totally hew to Doctrine.
Burak is probably one guy who learned English as a fifth language. Native English speakers don’t explain things like this. Simple, easy to understand and straight to the point. No big ambiguous Words. Or your teacher was a good one, OR you just have a talent for teaching. Do you have a donate button?
#34 by Burak on December 15th, 2009
| Quote
I learned it as a third language actually, so you’re pretty close
By GROUPS, do you mean GROUP BY in queries? That is done with DQL. You can see some examples of that in the further articles.
I’m not sure what you mean by EMAIL functionality. If you are talking about input validation, you can take a look at this: http://www.doctrine-project.org/documentation/manual/1_2/en/data-validation#examples:email
#35 by Gareth Price on December 23rd, 2009
| Quote
Hello, Quick question; how would I go about getting a text column, for say, topic text? I can’t see a suitable option in doctrine..
#36 by Burak on December 23rd, 2009
| Quote
Just use a ’string’ type column with a high length value, for example 10000. It should turn into a TEXT column.
#37 by Gareth Price on December 23rd, 2009
| Quote
Wicked, thank you!
#38 by esryl on January 9th, 2010
| Quote
@27, @28, @29
I am using these tutorials to craft my own little CMS, and ran across the same problem as @27.
The culprit was, autogenerated IDs are created as INT(10), while the key associations in Many to Many are created as BIGINT(20). When I manually changed these to INT(10), the script reran without problem.
Any ideas how to automatically get Doctrine to use matching Data Types?
#39 by Burak on January 10th, 2010
| Quote
In the plugin file (doctrine_pi.php), I set the id fields to be 4byte INT by default. You can change that to BIGINT (i.e. 8 byte integer) if you want to use that.
I don’t think you can tell Doctrine to automatically match the column types on the foreign keys.
#40 by Umut on January 11th, 2010
| Quote
Hi, you have a really nice series going on!
I just like to know how you was able to change the default(?) coallition latin1_swedish_ci to utf8_general_ci. It seems when I create tables with the inbuilt doctrine function I get those swedish cells.
#41 by Umut on January 11th, 2010
| Quote
Selamlar!, you have a really nice series going on!
I just like to know how you was able to change the default(?) coallition latin1_swedish_ci to utf8_general_ci. It seems when I create tables with the inbuilt doctrine function I get those swedish cells.
#42 by Burak on January 11th, 2010
| Quote
When I created the ci_doctrine database, I set it to utf8 default. That’s why the tables defaulted to utf8 as well.
You can explicitly set it like this if you want to (in setTableDefinition()):
$this->option(’collate’, ‘utf8_unicode_ci’);
$this->option(’charset’, ‘utf8′);
#43 by Umut on January 12th, 2010
| Quote
Thanks!, figured it out eventually! Thou I tried everything. Only to notice that i hadn’t changed the collation option when I created the database
#44 by krzysko on February 15th, 2010
| Quote
Hi.
I try made a relationship many to many and I get this:
Doctrine_Export_Exception: SQLSTATE[42703]: Undefined column: 7 BŁĄD: kolumna “id” określona w kluczu obcym nie istnieje. Failing Query: “ALTER TABLE poku_order_customer ADD CONSTRAINT poku_order_customer_id_poku_order_id FOREIGN KEY (id) REFERENCES poku_order(id) NOT DEFERRABLE INITIALLY IMMEDIATE”. Failing Query: ALTER TABLE poku_order_customer ADD CONSTRAINT poku_order_customer_id_poku_order_id FOREIGN KEY (id) REFERENCES poku_order(id) NOT DEFERRABLE INITIALLY IMMEDIATE in /var/www/postkurier_prog/ci_ver_0.1/system/application/plugins/doctrine/lib/Doctrine/Export.php on line 1219
If is necessary I can put my source code models. Any ideas?:)
#45 by krzysko on February 15th, 2010
| Quote
Sorry for that above the problem is solved.