100% found this document useful (1 vote)
879 views206 pages

CodeIgniter and Doctrine From Scratch

This document provides instructions for setting up CodeIgniter and Doctrine ORM from scratch. It explains why Doctrine is useful for mapping database tables to PHP classes. It then guides the user through installing WAMP, CodeIgniter, and Doctrine, and configuring the database connection. It demonstrates creating a basic User model and controller to add some sample users to the database using Doctrine. The goal is to get the basic CodeIgniter and Doctrine setup working and introduce some key concepts.

Uploaded by

Satish Kumar
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
100% found this document useful (1 vote)
879 views206 pages

CodeIgniter and Doctrine From Scratch

This document provides instructions for setting up CodeIgniter and Doctrine ORM from scratch. It explains why Doctrine is useful for mapping database tables to PHP classes. It then guides the user through installing WAMP, CodeIgniter, and Doctrine, and configuring the database connection. It demonstrates creating a basic User model and controller to add some sample users to the database using Doctrine. The goal is to get the basic CodeIgniter and Doctrine setup working and introduce some key concepts.

Uploaded by

Satish Kumar
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 206

CodeIgniter and Doctrine from scratch

Day 1 – Install and Setup


Why add Doctrine to CodeIgniter?
Before we get started, first let me explain the reason I am doing this. Doctrine is an Object
Relational Mapper for PHP. It’s OK if you don’t know this term. It basically means that you can
map your database tables to classes in your web application. And instances of these classes (i.e.
objects) represent records in the database.
This makes it very easy to create, read, update and delete records from the database, while
handling them almost like regular objects, so you don’t even have to write any queries. It will
also handle relationships between your tables. There are several other benefits that I will not get
into until later in these tutorials. See the Doctrine Introduction, if you would like more info right
now.
Here is an illustration I put together, that might give you a visual picture.
First Step: Setup your development environment
If you already have a web server with PHP and MySQL setup, you can skip some of this.
• Download and install WAMP (for Mac: MAMP)
Warning Skype Users: You must shutdown Skype first before you start up
WAMP, due to a port conflict. After WAMP starts up, you can open Skype
again.
• Visit http://localhost/ in your browser to make sure it’s working
• Open the “www” folder under the WAMP installation.
• Create a folder named “ci_doctrine_day1″. We will put our files here.
Install CodeIgniter
• Download CodeIgniter
• Extract and copy the contents into your new “ci_doctrine_day1″ folder.
• You may delete the “user_guide” folder.
Your new folders should look like this:

• Go to http://localhost/ci_doctrine_day1
You should see this:
CodeIgniter Crash Course: Controllers
Controllers are called by CodeIgniter on every page load.
They are located under:
system/application/controllers/
The url structure looks like this:
http://localhost/ci_doctrine_day1/index.php/CONTROLLER_NAME/FUNCTION_NAME
For example if you open this url:
http://localhost/ci_doctrine_day1/index.php/hello/world
CodeIgniter will look for a controller class named “Hello” and call it’s method named
“world()”.
So let’s create our first controller.
Our First Controller
• Create this file: system/application/controllers/hello.php
view source
print?
01 <?php
02 // system/application/controllers/hello.php

03
04 class Hello extends Controller {

05
06 function world() {

07 echo "Hello CodeIgniter!";


08 }
09
10 }
• Go to: http://localhost/ci_doctrine_day1/index.php/hello/world
You should see:
Hello CodeIgniter!
Please Note:
• The class must extend Controller.
• The class name must be capitalized.
• The file name must be lowercase.
Recommended Reading:
• http://codeigniter.com/user_guide/general/controllers.html
Install Doctrine
CodeIgniter allows us to add plug-ins. That’s how we will be installing it.
• Create this folder: system/application/plugins
• Create this folder: system/application/plugins/doctrine
• Download Doctrine
• Extract the files. Find the folder named “lib” and copy it to
system/application/plugins/doctrine.
Now your folders should look like this:

• Create the plug-in file: system/application/plugins/doctrine_pi.php


view source
print?
01 <?php
02 // system/application/plugins/doctrine_pi.php
03
04 // load Doctrine library

05 require_once APPPATH.'/plugins/doctrine/lib/Doctrine.php';
06

07 // load database configuration from CodeIgniter


08 require_once APPPATH.'/config/database.php';

09
10 // this will allow Doctrine to load Model classes automatically

11 spl_autoload_register(array('Doctrine', 'autoload'));
12

13 // we load our database connections into Doctrine_Manager


14 // this loop allows us to use multiple connections later on

foreach ($db as $connection_name => $db_values)


15
{
16

17 // first we must convert to dsn format


18 $dsn = $db[$connection_name]['dbdriver'] .

19 '://' . $db[$connection_name]['username'] .
20 ':' . $db[$connection_name]['password'].

21 '@' . $db[$connection_name]['hostname'] .
22 '/' . $db[$connection_name]['database'];

23
24 Doctrine_Manager::connection($dsn,$connection_name);

25 }
26
27 // CodeIgniter's Model class needs to be loaded
28 require_once BASEPATH.'/libraries/Model.php';

29
30 // telling Doctrine where our models are located

31 Doctrine::loadModels(APPPATH.'/models');
32

33 // (OPTIONAL) CONFIGURATION BELOW


34

35 // this will allow us to use "mutators"


36 Doctrine_Manager::getInstance()->setAttribute(

37 Doctrine::ATTR_AUTO_ACCESSOR_OVERRIDE, true);
38

// this sets all table columns to notnull and unsigned (for ints) by
39
default
40 Doctrine_Manager::getInstance()->setAttribute(

41 Doctrine::ATTR_DEFAULT_COLUMN_OPTIONS,
42 array('notnull' => true, 'unsigned' => true));

43
44 // set the default primary key to be named 'id', integer, 4 bytes

45 Doctrine_Manager::getInstance()->setAttribute(
46 Doctrine::ATTR_DEFAULT_IDENTIFIER_OPTIONS,

array('name' => 'id', 'type' => 'integer', 'length' =>


47
4));
Read the comments in the code for explanations. However, don’t worry if you don’t understand
all of it for now.
Database Setup and Configuration
• Open phpMyAdmin: http://localhost/phpmyadmin/
• Create a database named “ci_doctrine”
• Edit file: system/application/config/database.php
• Find the lines below and input the values.
view source
print?
1 // in system/application/config/database.php
2 // ...

3
4 $db['default']['hostname'] = "localhost";

5 $db['default']['username'] = "root";
6 $db['default']['password'] = "";

7 $db['default']['database'] = "ci_doctrine";
8

9 // ...
We just edited the database configuration file of CodeIgniter.
More Configuration
Almost done.
config.php
• Edit file: system/application/config/config.php
view source
print?
1 // in system/application/config/config.php
2 // ...

3
4 $config['base_url'] = "http://localhost/ci_doctrine_day1/";

5
6 // ...
Now CodeIgniter knows the url of our site.
autoload.php
• Edit file: system/application/config/autoload.php
view source
print?
1 // in system/application/config/autoload.php
2 // ...

3
4 $autoload['plugin'] = array('doctrine');

5
6 // ...
This makes sure the Doctrine plug-in is always loaded.
Finished!
Now we’re ready to rock. Let’s start testing our setup.
Our First Doctrine Model
Create a user Table
• Open phpMyAdmin: http://localhost/phpmyadmin/
• Go to database “ci_doctrine”
• Create a table named “user” with columns:
id => int, primary key, auto_increment,
username => varchar(255), unique,
password => varchar(255),
first_name => varchar(255),
last_name => varchar(255)
You may use this query:
CREATE TABLE `ci_doctrine`.`user` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`username` VARCHAR( 255 ) NOT NULL ,
`password` VARCHAR( 255 ) NOT NULL ,
`first_name` VARCHAR( 255 ) NOT NULL ,
`last_name` VARCHAR( 255 ) NOT NULL ,
UNIQUE (
`username`
)
)

Create the Model


• Create file: system/application/models/user.php
view source
print?
01 <?php
02 // system/application/models/user.php

03 class User extends Doctrine_Record {


04
05 public function setTableDefinition() {
06 $this->hasColumn('username', 'string', 255);

07 $this->hasColumn('password', 'string', 255);


08 $this->hasColumn('first_name', 'string', 255);

09 $this->hasColumn('last_name', 'string', 255);


10 }

11
12 }
Note:
• We extend Doctrine_Record, instead of Model (which you normally would
with CodeIgniter models).
• Inside the function setTableDefinition() we need to define the table structure.
• By default, Doctrine will look for a table with same name as the class. In this
case: “user”. (this can be changed)
• In our doctrine_pi.php file above in this tutorial, we specified for a default
primary key named “id”. Therefore we don’t need to put it again in our User
class.
Testing the Model: Add Some Users
• Edit our controller we created earlier:
system/application/controllers/hello.php
view source
print?
01 <?php
02 // system/application/controllers/hello.php

03
04 class Hello extends Controller {

05
06 function world() {

07 echo "Hello CodeIgniter!";


08 }

09
10 function user_test() {

11
12 $u = new User;

13 $u->username = 'johndoe';
14 $u->password = 'secret';

15 $u->first_name = 'John';
16 $u->last_name = 'Doe';

17 $u->save();
18

19 $u2 = new User;


20 $u2->username = 'phprocks';

21 $u2->password = 'mypass';
22 $u2->first_name = 'Codeigniter';

23 $u2->last_name = 'Doctrine';
24 $u2->save();

25
26 echo "added 2 users";

27 }
28

29 }
We just generated 2 objects, and populated them with some data. Simply calling save() should
save them into our database.
Note:
• We are able to access the fields as parameters (e.g. $u->username), even
though we did not create these as class parameters. Isn’t Doctrine nice?
• If you are familiar with CodeIgniter, you might remember that you need to
call $this->load->model() function to load models. However since we
registered the autoload function of Doctrine, just saying “new User;” is
enough.
• We didn’t create the “save()” function, because it comes from the
Doctrine_Record class we extended. It saves the objects to the database.
There are many other functions and goodies that come with Doctrine classes,
we will see later in the tutorials.
• Open: http://localhost/ci_doctrine_day1/index.php/hello/user_test
You should see output:
added 2 users
• Now go back to phpMyAdmin: http://localhost/phpmyadmin/
• Browse the table ‘user’
Voila! Now you should be able see the 2 new records that just got created.
Day 2 – The Basics
Let’s get started and make sure we have a fresh CodeIgniter+Doctrine install ready for some
code.
Fresh Quick CodeIgniter+Doctrine Install
Fresh Install Instructions:
(if you want to use your files from Day 1, skip to the next section instead)
• Have your PHP+MySQL web server ready. Recommended: WAMP (for Mac:
MAMP)
• Download my CodeIgniter+Doctrine bundle (v1.0).
• Extract and put the ci_doctrine folder into your web folder.
• Create a database named ci_doctrine for our project.
• Make sure your database info is correct in:
system/application/config/database.php.
• Make sure base_url is correct in: system/application/config/config.php.
• See if its working: http://localhost/ci_doctrine/.
Done!
If you already have the files from Day 1:
(if you used the fresh install from the section above, skip this)
• Rename or Copy your install folder to: ci_doctrine. (I decided to do this so
we don’t keep changing our site url on every tutorial)
• Delete: system/application/models/user.php
• Delete: system/application/controllers/hello.php
• Drop table: user
• Edit: system/application/config/config.php
view source
print?
1 // in system/application/config/config.php
2 $config['base_url'] = "http://localhost/ci_doctrine/";
CodeIgniter URL structure
Url’s in CodeIgniter can look like these:
This url invokes the controller class named “CONTROLLER_NAME”, and call it’s method
(function) named “METHOD_NAME”:
http://localhost/ci_doctrine/index.php/CONTROLLER_NAME/METHOD_NAME
Same as before, except it calls a method named index() by default:
http://localhost/ci_doctrine/index.php/CONTROLLER_NAME
Same as before. This time, it passes a “VALUE” as an argument to the controller method:
http://localhost/ci_doctrine/index.php/CONTROLLER_NAME/METHOD_NAME/VALUE
The value can be a number or a string.
You can keep appending more values in the url for passing additional arguments.
For More Info: Passing uri segments
CodeIgniter MVC (Model – View – Controller)
Models
We are going to be using Doctrine Model‘s, instead of CodeIgniter. I will explain it later in the
tutorial.
If you still want to learn about CodeIgniter Models, read: CodeIgniter Models
Views
• Views are created under system/application/views, and are named like
my_view.php
• They are the output templates. They can contain html, javascript and
more.
• Views also usually contain inline PHP code. (to display messages, run loops
etc…)
• Controllers typically load views for displaying output.
Official docs: CodeIgniter Views
Controllers
We already covered this in Day 1. Look for the section called “CodeIgniter Crash Course:
Controllers”.
Controller and View together
Here is a sample View (system/application/views/my_view.php):
view source
print?
01 Some plain text.
02

03 <div>
04 You can use HTML.

05 </div>
06

07 <div>
08 Display variables passed from a controller: <br />

09 <?php echo $message; ?> <br />


10 <?php echo $another_message; ?>

11 </div>
12

13 You can even do loops: <br />


14 <?php for ($i = 0; $i < 3; $i++)
{

15
16 echo "Counter shows: $i <br />";

17
18 } ?>

19
20 Or in alternate syntax: <br />

<?php for ($i = 0; $i < 3; $i++): ?


21
>
22

Counter shows: <?php echo $i; ?> <br


23
/>
24

25 <?php endfor; ?>


Here is a sample Controller (system/application/controllers/sample_controller.php), that loads a
view:
view source
print?
01 class Sample_controller extends Controller {
02

03 function index() {
04 $data = array();

05 $data['message'] = "index was called";


06 $data['another_message'] = "blah blah";

07
08 $this->load->view('my_view',$data);

09 }
10
11 }
Both of these url’s will work:
http://localhost/ci_doctrine/index.php/sample_controller/index

http://localhost/ci_doctrine/index.php/sample_controller
Browser would display:
Some plain text.
You can use HTML.
Display variables passed from a controller:
index was called
blah blah
You can even do loops:
Counter shows: 0
Counter shows: 1
Counter shows: 2
Or in alternate syntax:
Counter shows: 0
Counter shows: 1
Counter shows: 2
Note:
• View contains a combination of raw output and simple inline PHP.
• index() is the default Controller function, so we don’t have to put it in the
url.
• $this->load->view(‘my_view’,$data) loads the view and outputs it to the
browser.
• First argument ‘my_view’ is the name of the view file, without the .php
part.
• Second argument $data is an array, which passes variables to the view.
• Example: $data['message'] becomes $message, and
$data['another_message'] becomes $another_message, in our view.
(If you created the files above, to test the code, you can delete them now. We’re not going to use
them again in our project.)
Doctrine Models
Models are classes that represent data (typically from your database).
For example, you might have a user table. So we can build a Model Class named “User” to
represent the records in that table.
Our Model class should be able to peform CRUD (Create, Read, Update and Delete) operations.
Luckily, Doctrine will be a great help in accomplishing this with minimal and clean code.
Differences in Usage (compared to CodeIgniter Models)
• They extend the Doctrine_Record class (instead of the “Model” class).
• They can be loaded like this: $u = new User(); (instead of this: $u = $this-
>load->model(‘user’);)
thanks to the Doctrine autoload we registered in our plugin.
• It is not PHP4 compatible.
That is all you need to know for now. It should be an easy transition for those of you already
working with CodeIgniter.
What does a Doctrine Model look like?
Here is a little PREVIEW of the kind of Model’s we are going to be building. We’ll get into
more details in the next tutorials.
view source
print?
01 <?php
02 class User extends Doctrine_Record

03 {
04 // define table columns in this function

05 public function setTableDefinition() {


06

07 $this->hasColumn('username', 'string', 255);


08 $this->hasColumn('password', 'string', 255);

09 $this->hasColumn('email', 'string', 255, array(


10 'email' => true // It can validate e-mail input

11 ));
12

13 // supports many column types, including enum


14 $this->hasColumn('status', 'enum', null,

15 array('values' => array('unverified', 'verified', 'disabled'))


16 );

17
18 $this->hasColumn('referer_id', 'integer', 4);

19 }
20

21 // setup some options


22 public function setUp() {

23
24 // creates a relationship with a model named Post
25 $this->hasMany('Post as Posts', array(
26 'local' => 'id',

27 'foreign' => 'post_id'


28 ));

29
30 // can even have a relationship with itself

31 $this->hasOne('User as Referer', array(


32 'local' => 'referer_id',

33 'foreign' => 'id'


34 ));

35
// causes 'created_at' and 'updated_at' fields to be updated
36
automatically

37 $this->actAs('Timestampable');
38

39 // password field gets a mutator assigned, for automatic encryption


40 $this->hasMutator('password', 'md5Password');

41
42 }

43
44 // a mutator function

45 public function md5Password($value) {


46 $this->_set('password', md5($value));

47 }
48
49 }
Once we create Doctrine Model’s like this, we can do all kinds of database operations. Doctrine
can even create the table based on the model information alone.
Also keep in mind that there are other ways of creating Doctrine Models, such as using schema
files. But we will not get into that until later.
Day 3 – User Signup Form
Let’s build a Doctrine Model
Since we are building a user signup form today, it is best to start with a “User” Model.
Our model will be extending the Doctrine_Record class, which will represent records in a table
named “user”.
So let’s make one:
• Create a file named user.php under system/application/models
• We name the class User, matching the file name.
The class name is capitalized, but the filename is always lowercase. (This
is a CodeIgniter rule, rather than Doctrine, but we’re still going go with it for
consistency.)
• We add a method named setTableDefinition(), which will contain column
info.
• We also add a method named setUp(), which will contain additional options.
system/application/models/user.php
view source
print?
01 <?php
02 class User extends Doctrine_Record {

03
04 public function setTableDefinition() {

$this->hasColumn('username', 'string', 255, array('unique' =>


05
'true'));
06 $this->hasColumn('password', 'string', 255);

07 $this->hasColumn('email', 'string', 255, array('unique' => 'true'));


08 }

09
10 public function setUp() {

11 $this->setTableName('user');
12 $this->actAs('Timestampable');

13 }
14 }

Looks simple right? Now let’s go over the code.


$this->hasColumn()
Structure:
view source
print?
$this->hasColumn($name, $type = null, $length = null, $options =
1
array());
This function call tells Doctrine about the table column structure. Everything except the $name
field is optional.
Supported $type‘s are: Boolean, Integer, Float, Decimal, String, Array, Object, Blob, Clob,
Timestamp, Time, Date, Enum, Gzip.
$options are passed in an array, as ‘name’ => ‘value’ pairs. We passed an option named unique
for 2 columns, which means we will have unique indexes on them.
We don’t need to get into more detail on this, but if you want to read more, read Doctrine docs.
$this->setTableName()
Structure:
view source
print?
1 $this->setTableName($table_name);
Simply sets the table name the Model will use. This can have a totally different name than the
Model.
$this->actAs()
Adds a behavior to the model.
The one we used was Timestampable. It adds 2 fields to the table and manages them. They are
called created_at and updated_at. And you guessed it, they keep track of the records create and
update times.
Note: When we add this behavior, we don’t need those 2 columns specified inside the
setTableDefinition() function.
Adding Mutators
Let’s say we want our password field to be automatically encrypted for security. This is where
Mutators come in.
So, make the following changes to our model.
system/application/models/user.php (new code is highlighted)
view source
print?
01 <?php
02 class User extends Doctrine_Record {

03
04 public function setTableDefinition() {

$this->hasColumn('username', 'string', 255, array('unique' =>


05
'true'));
06 $this->hasColumn('password', 'string', 255);
07 $this->hasColumn('email', 'string', 255, array('unique' => 'true'));
08

09 }
10

11 public function setUp() {


12 $this->setTableName('user');

13 $this->actAs('Timestampable');
14 $this->hasMutator('password', '_encrypt_password');

15 }
16

17 protected function _encrypt_password($value) {


18 $salt = '#*seCrEt!@-*%';

19 $this->_set('password', md5($salt . $value));


20 }

21 }
First we added a Mutator function named _encrypt_password to the password column.
Then we implemented it as a method inside our Model class.
Note: For mutators to work, auto_accessor_override option needs to be enabled. We have
already done it, in our plugin file doctrine_pi.php.
Let Doctrine create the tables

Why create the user table manually ourselves when we can just let Doctrine do it for us.
We will use the Doctrine static class to create our tables like this:
view source
print?
1 Doctrine::createTablesFromModels();
So let’s create a simple and reusable script that we can rebuild our database with.
• Create this controller:
system/application/controllers/doctrine_tools.php
view source
print?
01 <?php
02 class Doctrine_Tools extends Controller {
03
04 function create_tables() {

05 echo 'Reminder: Make sure the tables do not exist already.<br />
06 <form action="" method="POST">

<input type="submit" name="action" value="Create Tables"><br


07
/><br />';
08

09 if ($this->input->post('action')) {
10 Doctrine::createTablesFromModels();

11 echo "Done!";
12 }

13 }
14

15 }
Only pay attention to the highlighted line for now. It simply reads our Doctrine Model class files,
and creates tables for them in the database.
I know the code is a bit ugly, but we’re just going to use this controller temporarily as we write
our initial models.
• First make sure user table does NOT exist. If it does, drop it using
phpmyadmin.
• Go to: http://localhost/ci_doctrine/index.php/doctrine_tools/create_tables and
click the button.
• Check out our new table.
Note: The id column got generated automatically. We already have a setting in our
doctrine_pi.php plugin file:
view source
print?
1 // set the default primary key to be named 'id', integer, 4 bytes
2 Doctrine_Manager::getInstance()->setAttribute(

3 Doctrine::ATTR_DEFAULT_IDENTIFIER_OPTIONS,

array('name' => 'id', 'type' => 'integer', 'length' =>


4
4));
Now let’s do some more CodeIgniter tweaking.
Removing “index.php” from CodeIgniter urls
We would like to have url’s like this:
http://localhost/ci_doctrine/welcome
instead of this:
http://localhost/ci_doctrine/index.php/welcome
Shorter, cleaner and nicer.
follow these steps:
• Our web server needs mod_rewrite enabled.
For WAMP users: Left-Click the system tray icon -> Apache -> Apache
Modules -> rewrite_module
• Create a file named .htaccess under our project folder “ci_doctrine”.
• Copy-paste the following:
.htaccess:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /ci_doctrine/index.php/$1 [L]
</IfModule>
<IfModule !mod_rewrite.c>
ErrorDocument 404 /ci_doctrine/index.php
</IfModule>
Now we need to tell CodeIgniter that we’re removing index.php from our url’s.
• Edit: system/application/config.php
view source
print?
01 /*
02 |--------------------------------------------------------------------------

03 | Index File
04 |--------------------------------------------------------------------------

05 |
06 | Typically this will be your index.php file, unless you've renamed it to

07 | something else. If you are using mod_rewrite to remove the page set this
08 | variable so that it is blank.

09 |
10 */

11 $config['index_page'] = "";
That’s it!
To make sure it’s working, visit: http://localhost/ci_doctrine/welcome.
You should see the welcome page.
Building the Signup Form
Before we start coding, let’s decide how we’re going to do this
The Game plan
We want a Controller named signup, so the url for the signup form becomes
http://localhost/ci_doctrine/signup
We would like this signup controller to load a View named signup_form by default, which will
contain the html for the form.
The form will submit to a method named submit inside the signup controller i.e. signup/submit.
From there we need to validate the user input. Display errors if needed.
Finally save the new user to the database using our new Doctrine Model.
“Signup” Controller
• Create this file: system/application/controllers/signup.php
view source
print?
01 <?php
02

03 class Signup extends Controller {


04

05 public function __construct() {


06 parent::Controller();

07 }
08

09 public function index() {


10 $this->load->view('signup_form');

11 }
12

13 }
Now let’s go over the code.
•First we created a constructor method (line 5). This will get called first every
time.
Constructors are optional, however there is a reason I decided to create one,
which we will see in a bit.
• In the constructor, we always must call the parent constructor first (line 6).
• In the index() method, (which acts as the default method for the controller),
we load a View named signup_form.
“signup_form” View
• Create this file: system/application/views/signup_form.php
view source
print?
01 <!DOCTYPE html>
02 <html lang="en">

03 <head>
04 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

05 <title>Signup Form</title>
06 </head>

07 <body>
08

09 <div id="signup_form">
10

11 <p class="heading">New User Signup</p>


12

13 </div>
14

15 </body>
16 </html>
For now it’s just a simple HTML file.
• Go to: http://localhost/ci_doctrine/signup
You should see the output:
New User Signup
CodeIgniter Helpers
Helpers are located in system/helpers. They contain procedural utility functions that make our
lives easier.
In this tutorial, we will be using the url and form Helpers.
They can be loaded like this:
view source
print?
1 $this->load->helper('url');
Or load multiple helpers at once:
view source
print?
1 $this->load->helper(array('form','url'));
CodeIgniter Libraries
Libraries are very similar to Helpers, but they tend to be Object Oriented instead.
We will be using the form_validation library, and load it like this:
view source
print?
1 $this->load->library('form_validation');
Once it’s loaded, we can access it like this:
view source
print?
1 $this->form_validation->//...
Add them to the controller
• Edit: system/application/controllers/signup.php
view source
print?
01 <?php
02

03 class Signup extends Controller {


04

05 public function __construct() {


06 parent::Controller();

07
08 $this->load->helper(array('form','url'));

09 $this->load->library('form_validation');
10 }

11
12 public function index() {

13 $this->load->view('signup_form');
14 }

15
16 }
Note: When you load Helpers and Libraries inside the Constructor, they become available in all
methods of that Controller. For example, now index() function can use them.
Note2: When Views are loaded, they also can use the Helpers and Libraries that the Controller
had loaded.
Form Helper
Now that we have it loaded, let’s use our Form Helper functions.
• Edit: system/application/views/signup_form.php
view source
print?
01 <!DOCTYPE html>
02 <html lang="en">

03 <head>
04 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

05 <title>Signup Form</title>
06 </head>

07 <body>
08

09 <div id="signup_form">
10

11 <p class="heading">New User Signup</p>


12

13 <?php echo form_open('signup/submit'); ?>


14

15 <p>
16 <label for="username">Username: </label>

17 <?php echo form_input('username'); ?>


18 </p>

19 <p>
20 <label for="password">Password: </label>

21 <?php echo form_password('password'); ?>


22 </p>

23 <p>
24 <label for="passconf">Confirm Password: </label>

25 <?php echo form_password('passconf'); ?>


26 </p>

27 <p>
28 <label for="email">E-mail: </label>

29 <?php echo form_input('email'); ?>


30 </p>

31 <p>
32 <?php echo form_submit('submit','Create my account'); ?>

33 </p>
34

35 <?php echo form_close(); ?>


36 </div>

37
38 </body>

39 </html>
I highlighted all the different kinds of functions we just called. These functions create the html
for our form tags and elements.
• Open http://localhost/ci_doctrine/signup
And in the source code you can see:
view source
print?
01 <!DOCTYPE html>
02 <html lang="en">

03 <head>
04 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

05 <title>Signup Form</title>
06 </head>

07 <body>
08

09 <div id="signup_form">
10

11 <p class="heading">New User Signup</p>


12

13 <form action="http://localhost/ci_doctrine/signup/submit" method="post">


14
15 <p>
16 <label for="username">Username: </label>

<input type="text" name="username" value="" />


17
</p>
18 <p>

19 <label for="password">Password: </label>


<input type="password" name="password" value="" />
20
</p>

21 <p>
22

23 <label for="passconf">Confirm Password: </label>


<input type="password" name="passconf" value="" />
24
</p>

25 <p>
26 <label for="email">E-mail: </label>

<input type="text" name="email" value="" />


27
</p>
28 <p>

<input type="submit" name="submit" value="Create my account" />


29
</p>
30 </form>

31
32 </div>

33
34 </body>

35 </html>
Let's give it Style
• Create a folder named css directly under our project folder ci_doctrine
• Create file: css/style.css
view source
print?
01 body {
02 font-family: "Trebuchet MS",Arial;

03 font-size: 14px;
04 background-color: #212426;

05 color: #11151E;
06 }

07
08 input, textarea, select {

09 font-family:inherit;
10 font-size:inherit;

11 font-weight:inherit;
12 }

13
14 #signup_form {

15 margin-left: auto;
16 margin-right: auto;

17 width: 360px;
18

19 font-size: 16px;
20

21 }
22

23 #signup_form .heading {
24 text-align: center;
25 font-size: 22px;
26 font-weight: bold;

27 color: #B9AA81;
28 }

29
30 #signup_form form {

31
32 background-color: #B9AA81;

33 padding: 10px;
34

35 border-radius: 8px;
36 -moz-border-radius: 8px;

37 -webkit-border-radius: 8px;
38

39 }
40

41 #signup_form form label {


42 font-weight: bold;

43 }
44

45 #signup_form form input[type=text],input[type=password] {


46 width: 316px;

47 font-weight: bold;
48 padding: 8px;
49
50 border: 1px solid #FFF;

51 border-radius: 4px;
52 -moz-border-radius: 4px;

53 -webkit-border-radius: 4px;
54 }

55
56 #signup_form form input[type=submit] {

57 display: block;
58 margin: auto;

59 width: 200px;
60 font-size: 18px;

61 background-color: #FFF;
62 border: 1px solid #BBB;

63 }
64

65 #signup_form form input[type=submit]:hover {


66 border-color: #000;

67 }
68

69 #signup_form .error {
70 font-size: 13px;

71 color: #690C07;
72 font-style: italic;

73 }
This tutorial is not about CSS, so there is not much to explain here.
• Edit: system/application/views/signup_form.php again.
• Add the highlighted lines:
view source
print?
1 <!DOCTYPE html>
2 <html lang="en">

3 <head>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

5 <title>Signup Form</title>
6 <link rel="stylesheet" href="<?php echo base_url(); ?>css/style.css"

7 type="text/css" media="all">
8 </head>

9 <body>
As you can see, we just called the base_url() function, which returns http://localhost/ci_doctrine/
in this case. This function is part of the URL Helper.
• Go to: http://localhost/ci_doctrine/signup, you will see:
Form Validation
If you press the submit button now, you will receive a 404 error:

Because our submit() method does not exist.


• Edit: system/application/controllers/signup.php
view source
print?
01 <?php
02
03 class Signup extends Controller {
04

05 public function __construct() {


06 parent::Controller();

07
08 $this->load->helper(array('form','url'));

09 $this->load->library('form_validation');
10 }

11
12 public function index() {

13 $this->load->view('signup_form');
14 }

15
16 public function submit() {

17
18 if ($this->_submit_validate() === FALSE) {

19 $this->index();
20 return;

21 }
22

23 $this->load->view('submit_success');
24

25 }
26
27 private function _submit_validate() {
28

29 // validation rules
30 $this->form_validation->set_rules('username', 'Username',

31 'required|alpha_numeric|min_length[6]|max_length[12]');
32

33 $this->form_validation->set_rules('password', 'Password',
34 'required|min_length[6]|max_length[12]');

35
36 $this->form_validation->set_rules('passconf', 'Confirm Password',

37 'required|matches[password]');
38

39 $this->form_validation->set_rules('email', 'E-mail',
40 'required|valid_email');

41
42 return $this->form_validation->run();

43
44 }

45 }
Let's go over all the code we just added:
• We add 2 methods: submit() and _submit_validate().
• submit() get's called when the form is submitted to signup/submit.
First it validates the input. If it fails, it calls the index() method, which display
the Signup Form again.
• If no errors found, we load a View named submit_success, which we will
create in a bit.
• _submit_validate() is just a private function we created, that contains the
form validation stuff.
And it returns the result of the Validation (true or false).
Let's look at how the form validation functions work:
When we loaded the Form_Validation Library earlier
First we set the rules like this:
view source
print?
1 $this->form_validation->set_rules('username', 'Username',
2 'required|alpha_numeric|min_length[6]|max_length[12]');
The first parameter is the name of the form field.
The second parameter is the literal name for it, for display purposes.
The third parameter is a list of validation rules, separated by the pipe character "|".
You can see a list of validation rules here.
Finally we the run the validation by calling run() (line 42), which will return FALSE if a user
input does not validate.
Displaying Validation Errors
Next thing we need to do is display the errors to the user.
• Edit: system/application/views/signup_form.php
view source
print?
01 <!DOCTYPE html>
02 <html lang="en">

03 <head>
04 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

05 <title>Signup Form</title>

<link rel="stylesheet" href="<?php echo base_url(); ?


06
>css/style.css"

07 type="text/css" media="all">
08 </head>

09 <body>
10

11 <div id="signup_form">
12

13 <p class="heading">New User Signup</p>


14

15 <?php echo form_open('signup/submit'); ?>


16

17 <?php echo validation_errors('<p class="error">','</p>'); ?>


18

19 <p>
20 <label for="username">Username: </label>

21 <?php echo form_input('username',set_value('username')); ?>


22 </p>

23 <p>
24 <label for="password">Password: </label>

25 <?php echo form_password('password'); ?>


26 </p>

27 <p>
28 <label for="passconf">Confirm Password: </label>

29 <?php echo form_password('passconf'); ?>


30 </p>

31 <p>
32 <label for="email">E-mail: </label>

33 <?php echo form_input('email',set_value('email')); ?>


34 </p>

35 <p>
36 <?php echo form_submit('submit','Create my account'); ?>

37 </p>
38 <?php echo form_close(); ?>
39
40 </div>

41
42 </body>

43 </html>
validation_errors() displays a list of the errors returned from the form validation (line 17).
The first and second arguments we passed are the html codes to be used to enclose each error
output.
Also, On lines 21 and 33 we now pass a second argument to the form_input() function call.
This way, when the form is re-displayed to the user, it will be populated with the values he
entered previously, so he doesn't have to start all over again.
Submit Success View
We will keep this one simple for now.
• Create: system/application/views/submit_success.php
view source
print?
1 Success!
Test The Form
• Enter some invalid data into the form and submit.
http://localhost/ci_doctrine/signup
And if you enter everything correctly, you should see the Success! message.
Saving the User
Now that our form works, we're going save the new user to the database using our Doctrine
Model.
• Edit the submit() function under
system/application/controllers/signup.php:
view source
print?
01 <?php
02
03 class Signup extends Controller {
04 // ...

05
06 public function submit() {

07
08 if ($this->_submit_validate() === FALSE) {

09 $this->index();
10 return;

11 }
12

13 $u = new User();
14 $u->username = $this->input->post('username');

15 $u->password = $this->input->post('password');
16 $u->email = $this->input->post('email');

17 $u->save();
18

19 $this->load->view('submit_success');
20

21 }
22

23 // ...
24 }

As you can see, all we do is create a User object, assign the parameters, and call save(), and
Doctrine should save the new record to the database. It's that simple.
Note: We are using $this->input->post() to get the submitted form values. When working with
CodeIgniter, we do NOT use superglobals such as $_POST directly. This provides added
security benefits.
Note2: We do NOT use any SQL filtering such as mysql_real_escape_string() when assigning
the user input to the Doctrine Model, because Doctrine will take care of filtering for us.
Test the Form
• Go to: http://localhost/ci_doctrine/signup, and submit it with proper values.
• Check the contents of the table:

It's working great. But we're not quite done yet.


One Little Problem
Try submitting the form again with the same username and e-mail, and you will see:
view source
print?
1 <br />
<b>Fatal error</b>: Uncaught exception 'Doctrine_Connection_Mysql_Exception'
with message 'SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate
2 entry 'testing' for key 'username'' in
C:\wamp\www\ci_doctrine\system\application\plugins\doctrine\lib\Doctrine\Conn
ection.php:1084

3 Stack trace:
#0
C:\wamp\www\ci_doctrine\system\application\plugins\doctrine\lib\Doctrine\Conn
4 ection\Statement.php(253): Doctrine_Connection-
&gt;rethrowException(Object(PDOException),
Object(Doctrine_Connection_Statement))

#1
5 C:\wamp\www\ci_doctrine\system\application\plugins\doctrine\lib\Doctrine\Conn
ection.php(1049): Doctrine_Connection_Statement-&gt;execute(Array)
#2
6 C:\wamp\www\ci_doctrine\system\application\plugins\doctrine\lib\Doctrine\Conn
ection.php(693): Doctrine_Connection-&gt;exec('INSERT INTO use...', Array)

#3
C:\wamp\www\ci_doctrine\system\application\plugins\doctrine\lib\Doctrine\Conn
7
ection\UnitOfWork.php(595): Doctrine_Connection-
&gt;insert(Object(Doctrine_Table), Array)
#4 C:\wamp\www\ci_doctrine\system\application\plugins\doctrine\lib\Doctrine
in
8
<b>C:\wamp\www\ci_doctrine\system\application\plugins\doctrine\lib\Doctrine\
Connection.php</b> on line <b>1084</b><br />
The problem is, we tried to insert a duplicate value into a unique table column.
Handling Duplicates
We could simply check for the existence of a duplicate with something like this:
view source
print?
1 $user_table = Doctrine::getTable('User');
2 if ($user_table->findOneByUsername($username)) {

3 // ... username already exists!


4}

This is the first time we are looking into fetching records using Doctrine.
In the first line, we get the table object for our User Model. The name we pass is the name of
the Model, and NOT the name of the table. This is important to know, in case your model and
tables are named differently.
Then we call a magic function named findOneBy*(). It is magic, because it can be called on any
other property, like findOneByEmail(). You basically need to append the property name, (which
can be in camelCase format).
We could just add this code to our _submit_validate() function, for both username and email
fields, however that's not quite what I want to do.
Extending CodeIgniter Libraries
Since we're using Doctrine to save time and avoid code duplication, it's only fitting that we
continue with the same idea here.
We might need to check for duplicate records for other Models or from other Controllers later
on. So we're going to build a reusable form validation rule, by extending the Form Validation
class.
This way, other forms will have access to the same functionality later on, without having to
duplicate code.
• Create: system/application/libraries/MY_Form_validation.php
view source
print?
01 <?php if (!defined('BASEPATH')) exit('No direct script access allowed');
02

03 class MY_Form_validation extends CI_Form_validation {


04

05 function unique($value, $params)


06 {

07 $CI =& get_instance();


08

09 $CI->form_validation->set_message('unique',
10 'The %s is already being used.');

11
12 list($model, $field) = explode(".", $params, 2);

13
14 $find = "findOneBy".$field;

15
16 if (Doctrine::getTable($model)->$find($value)) {

17 return false;
18 } else {

19 return true;
20 }

21
22 }
23 }
Let's go over the code:
• Line 3: The new class needs to have the prefix MY_, and extends the core
class named CI_Form_validation.
• Line 5: We are going to create a validation rule named unique. With this
form_validation class, names of the methods match the names of the rules,
due to how the parent class is setup.
• Line 5: First argument $value is value of the form field input.
• Line 5: Second argument $params is the parameter passed to the rule. Our
rule will have this structure: unique[model.field]. You will see it in a bit.
• Lines 7-9: We need to get the CodeIgniter super object, so that we can
access the form_validation instance, and set the error message.
• Lines 12+: extract from the model.field values, build the name of the
findOneBy function, and check for existing records.
You can read more about creating libraries in the docs.
Using the new form validation rule
• Edit 2 lines in: system/application/controllers/signup.php
view source
print?
01 <?php
02

03 class Signup extends Controller {


04

05 // ...
06

07 private function _submit_validate() {


08

09 // validation rules
10 $this->form_validation->set_rules('username', 'Username',

'required|alpha_numeric|min_length[6]|max_length[12]|
11
unique[User.username]');
12

13 // ...
14
15 $this->form_validation->set_rules('email', 'E-mail',
16 'required|valid_email|unique[User.email]');

17
18 // ...

19 }
20 // ...

21 }
As you can see, now we can use our new form validation rule named unique. We also provide
the model name and the field name to this rule, in square brackets.
Test the form again
• Go to: http://localhost/ci_doctrine/signup
• Try to signup with the same values twice.
You will see 2 errors:
Day 4 – User Login
Login Form View
First thing we are going to do is to create the View for the Login Form.
We are going to make this one very similar to the Signup Form we created in the last article.
• Create: system/application/views/login_form.php
view source
print?
01 <!DOCTYPE html>
02 <html lang="en">

03 <head>
04 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

05 <title>Login Form</title>

<link rel="stylesheet" href="<?php echo base_url(); ?


06
>css/style.css"

07 type="text/css" media="all">
08 </head>

09 <body>
10

11 <div id="signup_form">
12

13 <p class="heading">User Login</p>


14

15 <?php echo form_open('login/submit'); ?>


16

17 <?php echo validation_errors('<p class="error">','</p>'); ?>


18
19 <p>
20 <label for="username">Username: </label>

21 <?php echo form_input('username',set_value('username')); ?>


22 </p>

23 <p>
24 <label for="password">Password: </label>

25 <?php echo form_password('password'); ?>


26 </p>

27 <p>
28 <?php echo form_submit('submit','Login'); ?>

29 </p>
30

31 <?php echo form_close(); ?>


32 <p>

33 <?php echo anchor('signup','Create an Account'); ?>


34 </p>

35
36 </div>

37
38 </body>

39 </html>
All I did was copy the contents of the signup form, change the page title, remove some form
fields and added a link at the bottom to the signup form.
anchor()
On Line 33 in the file above, I used a function named anchor(). This is part of the URL Helper
in CodeIgniter. It helps us create links with ease. The first argument is the Controller Name,
and the second argument is the Link Text. You can also pass an optional third argument for
extra html attributes. The result is:
view source
print?
1 <a href="http://localhost/ci_doctrine/signup">Create an Account</a>
Style the links
I am just going to make a small addition to the stylesheet so our links look better.
• Edit: css/style.css
view source
print?
01 body {
02 font-family: "Trebuchet MS",Arial;

03 font-size: 14px;
04 background-color: #212426;

05 color: #11151E;
06 }

07
08 a {

09 color: #FFF;
10 }

11
12 a:hover {

13 color: #B9AA81;
14 }

15
16 /* ... */
I only added the highlighted lines.
Link from the Signup Form
Likewise, we will add a link from the Signup Form to the new Login Form.
• Edit: system/application/views/signup_form.php
view source
print?
01 <!DOCTYPE html>
02 <html lang="en">
03 <head>
04 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

05 <title>Signup Form</title>

<link rel="stylesheet" href="<?php echo base_url(); ?


06
>css/style.css"

07 type="text/css" media="all">
08 </head>

09 <body>
10

11 <div id="signup_form">
12

13 <p class="heading">New User Signup</p>


14

15 <?php echo form_open('signup/submit'); ?>


16

17 <?php echo validation_errors('<p class="error">','</p>'); ?>


18

19 <p>
20 <label for="username">Username: </label>

21 <?php echo form_input('username',set_value('username')); ?>


22 </p>

23 <p>
24 <label for="password">Password: </label>

25 <?php echo form_password('password'); ?>


26 </p>

27 <p>
28 <label for="passconf">Confirm Password: </label>

29 <?php echo form_password('passconf'); ?>


30 </p>

31 <p>
32 <label for="email">E-mail: </label>

33 <?php echo form_input('email',set_value('email')); ?>


34 </p>

35 <p>
36 <?php echo form_submit('submit','Create my account'); ?>

37 </p>
38 <?php echo form_close(); ?>

39 <p>
40 <?php echo anchor('login','Login Form'); ?>

41 </p>
42

43 </div>
44

45 </body>
46 </html>
I only added the highlighted lines.
Note: We just linked to a Controller named login. This Controller does not exist yet. So let’s
create it.
Login Controller
First we are going create a sort of skeleton structure for our new Controller.
• Create: system/application/controllers/login.php
view source
print?
01 <?php
02 class Login extends Controller {

03
04 public function __construct() {

05 parent::Controller();
06

07 $this->load->helper(array('form','url'));
08 $this->load->library('form_validation');

09 }
10

11 public function index() {


12 $this->load->view('login_form');

13 }
14

15 public function submit() {


16

17 if ($this->_submit_validate() === FALSE) {


18 $this->index();

19 return;
20 }

21
22 redirect('/');

23
24 }
25
26 private function _submit_validate() {

27
28 }

29
30 }
This looks very similar to the submit Controller we created in the last article. The only new
thing is the highlighted line.
redirect()
This function is part of the URL Helper. It forwards the surfer to a specified Controller. In this
case we only passed ‘/’, which will basically send the surfer to the Home Page or in other words
the Default Controller.
So the user will end up at http://localhost/ci_doctrine/.
Test The Login Controller+View
• Go here: http://localhost/ci_doctrine/login

• Now click the link that says Create an Account.


You should see our old Signup Form page.
Works great!
Default Controller
When someone visits the main page of our website (i.e. there is no controller name in the url), it
calls the Default Controller. Right now if you go to http://localhost/ci_doctrine/, you will see
this:
Because CodeIgniter is loading a Controller named welcome by default.
Let’s say we would like to have a Default Controller named home instead.
• Create: system/application/controllers/home.php
view source
print?
1 <?php
2 class Home extends Controller {

3
4 public function index() {

5 echo "Home Sweet Home!";


6 }

7
8}
Routes File
• Edit: system/application/config/routes.php
Changing just one line:
view source
print?
1 // ...
2 $route['default_controller'] = "home";

3 $route['scaffolding_trigger'] = "";
4 // ...
That’s it. Now when you go to http://localhost/ci_doctrine/, you will see our controller:
Home Sweet Home!
The Routes File has some other purposes, which we will not get into right now. But you can read
more about it here.
User Login (Authentication)
Now that our forms are in place, we need to add authentication functionality to our application.
First I want to show you how to accomplish it in a simple and most common way. Once we
cover that, I will show you a better way of handling this.
Authentication with Form Validation
• Edit: system/application/controllers/login.php
view source
print?
01 <?php
02 class Login extends Controller {

03
04 public function __construct() {

05 parent::Controller();
06

07 $this->load->helper(array('form','url'));
08 $this->load->library('form_validation');

09 }
10

11 public function index() {


12 $this->load->view('login_form');

13 }
14

15 public function submit() {


16

17 if ($this->_submit_validate() === FALSE) {


18 $this->index();
19 return;
20 }

21
22 redirect('/');

23
24 }

25
26 private function _submit_validate() {

27
28 $this->form_validation->set_rules('username', 'Username',

29 'trim|required|callback_authenticate');
30

31 $this->form_validation->set_rules('password', 'Password',
32 'trim|required');

33
$this->form_validation->set_message('authenticate','Invalid login.
34
Please try again.');

35
36 return $this->form_validation->run();

37
38 }

39
40 public function authenticate() {

41
42 // get User object by username
if ($u = Doctrine::getTable('User')->findOneByUsername($this->input-
43
>post('username'))) {
44

45 // this mutates (encrypts) the input password


46 $u_input = new User();

47 $u_input->password = $this->input->post('password');
48

49 // password match (comparing encrypted passwords)


50 if ($u->password == $u_input->password) {

51 unset($u_input);
52 return TRUE;

53 }
54 unset($u_input);

55 }
56

57 return FALSE;
58 }

59 }
Highlighted functions have been updated.
First I added some form validation rules inside the _submit_validate() function, like in the last
article. But this time I added a Callback rule. Notice on Line 29: callback_authenticate. The
format is callback_[function_name].
On Line 34 I set a custom message for this validation rule, in case it fails.
Then I added the function named authenticate. This callback function needs to return TRUE
when everything is good, and FALSE when the validation fails.
Lines 43-53: This is where I perform the username and password check for the login. First I
attempt to get the User Record searching by the input username. (We learned about the
getTable and findOneBy* functions in the last article).
Applying the Mutator to an external value
Remember we added a Mutator to the password field in the last article?
Since the stored passwords are encrypted, I need to apply the same encryption to the user input,
before I can compare the two.
That’s why I create a new User object named $u_input. on Line 46 and assigned the input
password to it. This is how I trigger the Mutator function on the password that was entered in
the form. (If anyone knows a better of doing this, let me know!)
Now the encrypted version of the input password resides in $u_input->password. So I was able
to compare it to $u->password.
I also unset the $u_input object, just to make sure it doesn’t get saved as a new record by
accident.
Testing the Form
First create a new user by going to the Signup Form at http://localhost/ci_doctrine/signup.
Now go to: http://localhost/ci_doctrine/login and use a wrong login. You should see:

When you use a correct login, you should get forwarded back to the home page.
Home Sweet Home!
Our Login Form works!
Now the Better Way!
I mentioned I was going to change things a bit and show you how to do this in a better way.
What’s wrong with the current method?
1. I don’t like how the authentication code is all inside the Controller. This is
not good for code reusability.
2. It should be part of the User Model, since it’s directly related to it.
3. We stored the User Record in a variable named $u. But it’s inside a function
and not global. So, if other Controllers need to use it, they can’t.
4. Turning the object $u directly into a Global Variable would be a bad design
decision.
5. We do not want to store the whole user object inside the session. Doctrine
Models have a lot of internal data, which could quickly inflate the size of our
session storage. Only thing we are going store in the session is the user_id.
Singleton Pattern to the rescue!
Some of you may not know what Singleton Pattern means. It’s one of the most popular design
patterns, and I will show you how we are going to use it.
With this new class I am going to create, we will have a better design for our authentication
functionality, and we will also make the User Model instance for the logged in user globally
available in our application.
Singleton Pattern for Current User
First let’s build the skeleton:
• Create: system/application/models/current_user.php
view source
print?
01 <?php
02 class Current_User {

03
04 private static $user;

05
06 private function __construct() {}

07

public static function user()


08
{

09
10 if(!isset(self::$user)) {

11
12 // put the User record into $this->user

13 // ...
14

15 }
16

17 return self::$user;
18 }

19
20 public function __clone() {

21 trigger_error('Clone is not allowed.', E_USER_ERROR);


22 }

23
24 }
Now let’s go over the code:
• There is a reason I called the class Current_User. Because this static class
will always return an instance of the User class, for the current logged in
user.
• This class does NOT extend Doctrine_Record, because we are not really
creating a Doctrine Model.
• The $user variable will contain the User object for the logged in user. And we
will access it by Current_User::user(). That is the syntax for calling a static
class function.
• We will never create an instance of this Current_User class, so the
constructor is set to private.
• We also disallow cloning of this class so the __clone() function triggers an
error.
Now I’m going to add a bit more code to make this functional.
• Edit: system/application/models/current_user.php
view source
print?
01 <?php
02 class Current_User {

03
04 private static $user;

05
06 private function __construct() {}

07

public static function user()


08
{
09
10 if(!isset(self::$user)) {

11
12 $CI =& get_instance();

13 $CI->load->library('session');
14

15 if (!$user_id = $CI->session->userdata('user_id')) {
16 return FALSE;

17 }
18

19 if (!$u = Doctrine::getTable('User')->find($user_id)) {
20 return FALSE;

21 }
22

23 self::$user = $u;
24 }

25
26 return self::$user;

27 }
28

public static function login($username, $password)


29
{
30

31 // get User object by username


32 if ($u = Doctrine::getTable('User')->findOneByUsername($username)) {
33
34 // this mutates (encrypts) the input password

35 $u_input = new User();


36 $u_input->password = $password;

37
38 // password match (comparing encrypted passwords)

39 if ($u->password == $u_input->password) {
40 unset($u_input);

41
42 $CI =& get_instance();

43 $CI->load->library('session');
44 $CI->session->set_userdata('user_id',$u->id);

45 self::$user = $u;
46

47 return TRUE;
48 }

49
50 unset($u_input);

51 }
52

53 // login failed
54 return FALSE;

55
56 }
57
58 public function __clone() {

59 trigger_error('Clone is not allowed.', E_USER_ERROR);


60 }

61
62 }
Let’s go over the code in user() method first:
• The usage for this function is: Current_User::user() and it returns the User
object for the logged in user.
• Line 10: We check to see if the user object is already there. If it is, we return
it at line 26 .
• Lines 12-17: We load the session library, so we can read/write in the
session. If the user is logged in, his id should be stored there. If we don’t see
the user_id, it returns FALSE.
• We had to use the $CI object to load the session library because this is a
static class.
• Lines 19-21: Fetch the User object by id. If we can’t, return FALSE. Note that
we used a function named find() to look up by id.
• Lines 23 and 26: Everything went well, so we store the user object in the
static variable $user and return it.
Now, when we call Current_User::user() , we can get the User object as if it’s a global
variable. And if it returns false, it means the user is NOT logged in.
Let’s go over the login() method:
• I copied most of the code from the login Controller we created earlier. The
code is from the authenticate() method.
• I added loading of the session library at line 42-43, so I could store the
user_id in the session at line 44
• I also added line 45. Once the user gets logged in, the $user static variable
is set, so it can be returned by the user() function later.
By the way, we just learned how to use Sessions in CodeIgniter. It’s not very complicated. All
you need to do is to load the library and you can use the userdata() and set_userdata() function
to read and write the variables. You can read more about it here.
Now we can call the static function like this: Current_User::login($username,$password) ,
and it will take care of the rest.
Phew! I hope that was not very complicated. But I’m sure you will soon understand why we
went through all of that.
Simplified Login Controller
We are about the see the first benefit of creating all that code above.
• Edit: system/application/controllers/login.php
Only editing the authenticate() function:
view source
print?
01 <?php
02 class Login extends Controller {

03
04 // ...

05
06 public function authenticate() {

07
08 return Current_User::login($this->input->post('username'),

09 $this->input->post('password'));
10

11 }
12 }

See how easy that is now?


When we call Current_User::login() it attempts to login the user, and returns TRUE on
success. If the login information is incorrect, it returns FALSE.
Once the user is logged in this way, we can retrieve the User object for the logged in user,
anywhere in our application by simply calling Current_User::user().
Also note that we didn’t need to put “$this->load->model(‘Current_User’)” in the code, and we
will never have to. The Current_User class will always be globally available in the application,
because of the Doctrine autoloader that was registered in the
“system/application/plugins/doctrine_pi.php” file.
Logout
Now we are going to let users logout by clicking a link. But first:
Autoloading Libraries
It seems like we are going to be using the Session Library almost everywhere in our application.
Let’s have it autoloaded by CodeIgniter so we don’t have to keep calling $this->library-
>load(‘session’) all the time.
• Edit: system/application/config/autoload.php
Find this line:
view source
print?
1 // ...
2 $autoload['libraries'] = array('session');

3 // ...
Let’s also autoload the URL Helper.
• Edit: system/application/config/autoload.php
Find this line:
view source
print?
1 // ...
2 $autoload['helper'] = array('url');

3 // ...
Now that’s out of the way.
Logout Controller
• Create: system/application/controllers/logout.php
view source
print?
01 <?php
02 class Logout extends Controller {

03
04 public function index() {

05
06 $this->session->sess_destroy();

07 $this->load->view('logout');
08 }

09
10 }
We remove all of the session data by calling $this->session->sess_destroy(). Now the user will
no longer be logged in.
Logout View
• Create: system/application/views/logout.php
view source
print?
01 <!DOCTYPE html>
02 <html lang="en">

03 <head>
04 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

05 <title>Logout</title>
06 <link rel="stylesheet" href="<?php echo base_url(); ?>css/style.css"
07 type="text/css" media="all">
08 <meta http-equiv="refresh" content="3;url=<?php echo base_url(); ?>">

09 </head>
10 <body>

11
12 <div>

13
14 <p>

15 You are now logged out.


16 </p>

17 <p>
18 Redirecting you <?php echo anchor('/','home'); ?>.

19 </p>
20

21 </div>
22

23 </body>
24 </html>
This logout page will display a short message and redirect the surfer using a meta refresh.
Style Changes
I have made some css changes, so just copy paste the following:
• Edit: css/style.css
view source
print?
01 body {
02 font-family: "Trebuchet MS",Arial;

03 font-size: 14px;
04 background-color: #212426;
05 color: #B9AA81;
06 }

07
08 a {

09 color: #FFF;
10 }

11
12 a:hover {

13 color: #B9AA81;
14 }

15
16 input, textarea, select {

17 font-family:inherit;
18 font-size:inherit;

19 font-weight:inherit;
20 }

21
22 #signup_form {

23 margin-left: auto;
24 margin-right: auto;

25 width: 360px;
26

27 font-size: 16px;
28

29 }
30

31 #signup_form .heading {
32 text-align: center;

33 font-size: 22px;
34 font-weight: bold;

35 color: #B9AA81;
36 }

37
38 #signup_form form {

39
40 background-color: #B9AA81;

41 padding: 10px;
42

43 border-radius: 8px;
44 -moz-border-radius: 8px;

45 -webkit-border-radius: 8px;
46

47 }
48

49 #signup_form form label {


50 font-weight: bold;

51 color: #11151E;
52 }

53
54 #signup_form form input[type=text],input[type=password] {
55 width: 316px;
56 font-weight: bold;

57 padding: 8px;
58

59 border: 1px solid #FFF;


60 border-radius: 4px;

61 -moz-border-radius: 4px;
62 -webkit-border-radius: 4px;

63 }
64

65 #signup_form form input[type=submit] {


66 display: block;

67 margin: auto;
68 width: 200px;

69 font-size: 18px;
70 background-color: #FFF;

71 border: 1px solid #BBB;


72

73 }
74

75 #signup_form form input[type=submit]:hover {


76 border-color: #000;

77 }
78

79 #signup_form .error {
80 font-size: 13px;

81 color: #690C07;
82 font-style: italic;

83 }
Home View
Now we are going to see how easy it is to fetch user information for the logged in user.
• Create: system/application/views/home.php
view source
print?
01 <!DOCTYPE html>
02 <html lang="en">

03 <head>
04 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

05 <title>Home</title>

<link rel="stylesheet" href="<?php echo base_url(); ?


06
>css/style.css"

07 type="text/css" media="all">
08 </head>

09 <body>
10

11 <div>
12

13 <?php if(Current_User::user()): ?>


<h2>Hello <em><?php echo Current_User::user()->username; ?
14
></em>.</h2>

15 <h2><?php echo anchor('logout','Logout'); ?></h2>


16 <?php else: ?>

17 <h2>New Users: <?php echo anchor('signup','Create an Account'); ?


>.</h2>
18 <h2>Members: <?php echo anchor('login','Login'); ?>.</h2>

19 <?php endif; ?>


20

21 </div>
22

23 </body>
24 </html>
Home Controller
• Edit: system/application/controllers/home.php
view source
print?
1 <?php
2 class Home extends Controller {

3
4 public function index() {

5 $this->load->view('home');
6 }

7
8}
That’s it!
Let’s Test It
• Go to: http://localhost/ci_doctrine/logout to make sure you are logged out.
First you will see:

And it will forward you home:


• Click the Login link to be forwarded to our Login Form.

• Enter a correct login info and you will be redirected home again.
Now you should see:
In this case my username is burak, and it was displayed back to me.
Voila!
Day 5 – Database CRUD
CRUD stands for Create, Read, Update and Delete, the basic operations
performed on the database. With regular CodeIgniter setups, most developers
would use the Active Record Class for performing such operations. But we are not
going to be doing that.

In Doctrine, there can be multiple ways of accomplishing the same task. This gives you
flexibility to pick the best approach suitable for each situation. Therefore we shall explore these
different methods for each part of the CRUD.
1. CREATE
The act of inserting new data/records into the database:
Record Object
This is the way we have been creating our user records in the previous tutorials. It’s pretty
simple:
view source
print?
1 $u = new User();
2

3 $u->username = 'myuser';
4 $u->password = 'mypass';

5
6 $u->save();
When the save() method is called, the new record is created.
fromArray()
Sometimes you have all the data already in an array. Instead of assigning every value to the
record object separately, you can simply use the fromArray() method.
view source
print?
01 $data = array(
02 'username' => 'myuser',

03 'password' => 'mypass',


04 'email' => '[email protected]'

05 );
06
07 $u = new User();
08

09 $u->fromArray($data);
10

11 $u->save();
flush()
When you call the flush() method on the Doctrine_Connection object, all unsaved record
objects automatically get saved. And Doctrine performs this in a single transaction.
view source
print?
01 $u = new User();
02 $u->username = 'myuser';

03 $u->password = 'mypass';
04

05 $u2 = new User();


06 $u2->username = 'foouser';

07 $u2->password = 'foopass';
08

09 $conn = Doctrine_Manager::connection();
10

11 $conn->flush();
This way you don’t need to call the save() method for each object.
Raw SQL
We can just give a plain SQL query to the Doctrine_Connection object and call the execute()
method.
This also supports binding of values to the query (with the usage of ‘?’), which automatically
filters against SQL injection.
view source
print?
1 $data = array('myuser','mypass');
2

3 $conn = Doctrine_Manager::connection();
4

$conn->execute('INSERT INTO user (username, password) VALUES (?,?)',


5
$data);
2. READ
The act of fetching records from the database.
Most of these are performed on Doctrine_Table objects, which is obtained by calling
Doctrine::getTable() and passing the model class name to it.
find()
The find() method fetches a record by the Primary Key value.
view source
print?
1 $user_id = 1;
2

3 $u = Doctrine::getTable('User')->find($user_id);
4

5 echo $u->username;
findOneBy*()
This is a magic method. You can simply append the column name, and it will search the table by
that column.
For example, to find a user record by username:
view source
print?
1 $username = 'myuser';
2

3 $u = Doctrine::getTable('User')->findOneByUsername($username);
4

5 echo $u->username;
It fetches only one record.
findBy*()
Another magic method, which works similarly, but fetches multiple rows. It returns a
Doctrine_Collection object.
You can treat this returned object just like an array, to get to the individual record.
(let’s assume we have a column named ‘status’):
view source
print?
1 $users = Doctrine::getTable('User')->findByStatus('active');
2

3 echo $users[0]->username;
4 echo $users[1]->username;
Even if the actual column name is all lowercase, it can be used as capitalized in that magic
function call (status vs. Status).
DQL
This is our first look at DQL in these tutorials. It stands for Doctrine Query Language. It is
actually a major feature. Most advanced queries are performed using DQL.
In this example, we first create the query object, and add the components of the query to it. All
the method calls can be chained nicely. Finally we execute the query, which returns us a
Doctrine_Collection object, like that last example.
view source
print?
01 $status = 'active';
02

03 $q = Doctrine_Query::create()
04 ->select('username')

05 ->from('User')
06 ->where('status = ?', $status)

07 ->limit(20);
08

09 $users = $q->execute();
10

11 echo $users[0]->username;
12 echo $users[1]->username;
We will see many more examples of DQL in our project. But if you want more info right now,
there is a long documentation chapter here.
toArray()
A Doctrine_Record object contains a lot of stuff behind the scenes. If you ever try to print_r()
that object directly, you will see a long list of data.
However, when you simply want the data of the record, you can convert it to an array with the
toArray() call.
view source
print?
1 $user_id = 1;
2

3 $u = Doctrine::getTable('User')->find($user_id);
4

5 $u_arr = $u->toArray();
6

7 print_r($u_arr);
What you get will be similar to what you get from a mysql_fetch_assoc() call.
3. UPDATE
The act of updating existing records.
Record Object
You can make direct changes to any Doctrine_Record object. Once you call save(), it will
perform an UPDATE.
view source
print?
1 $user_id = 1;
2

3 $u = Doctrine::getTable('User')->find($user_id);
4

5 $u->password = 'newpassword';
6

7 $u->save();
DQL
When you want to update multiple rows at once, the preferred way is to use DQL.
view source
print?
1 $status = 'active';
2

3 $q = Doctrine_Query::create()
4 ->update('User')

5 ->set('status', '?', $status)


6 ->where('id < 10');
7
8 $numrows = $q->execute();

9 echo "$numrows records updated";


The execute() call in this case returns the number of updated rows.
4. DELETE
The act of deleting records from the database.
Record Object
You can simply call the delete() method to delete a Doctrine_Record object directly.
view source
print?
1 $user_id = 1;
2

3 $u = Doctrine::getTable('User')->find($user_id);
4

5 $u->delete();
DQL
When deleting multiple records, use DQL.
view source
print?
1 $q = Doctrine_Query::create()
2 ->delete('User')

3 ->where('id < 10');


4

5 $numrows = $q->execute();
6 echo "$numrows records deleted";
The execute() method call will return the number of deleted records.
Day 6 – Models with Relationships
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:
view source
print?
1 class Post extends Doctrine_Record {
2

3 public function setTableDefinition() {


4 $this->hasColumn('content', 'string', 65535);

5 $this->hasColumn('user_id', 'integer');
6 }

7
8}
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:
view source
print?
01 class Post extends Doctrine_Record {
02

03 public function setTableDefinition() {


04 $this->hasColumn('content', 'string', 65535);

05 $this->hasColumn('user_id', 'integer');
06 }

07
08 public function setUp() {

09 $this->hasOne('User', array(
10 'local' => 'user_id',

11 'foreign' => 'id'


12 ));

13 }
14

15 }
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:
view source
print?
01 class User extends Doctrine_Record {
02

03 public function setTableDefinition() {


$this->hasColumn('username', 'string', 255, array('unique' =>
04
'true'));

05 $this->hasColumn('password', 'string', 255);


06 }

07
08 public function setUp() {

09 $this->hasMany('Post as Posts', array(


10 'local' => 'id',

11 'foreign' => 'user_id'


12 ));

13 }
14 }

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
view source
print?
01 <?php
02 class Category extends Doctrine_Record {

03
04 public function setTableDefinition() {

05 $this->hasColumn('title', 'string', 255);


06 }

07
08 public function setUp() {

09 $this->hasMany('Forum as Forums', array(


10 'local' => 'id',

11 'foreign' => 'category_id'


12 ));

13 }
14 }

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
view source
print?
01 <?php
02 class Forum extends Doctrine_Record {

03
04 public function setTableDefinition() {

05 $this->hasColumn('title', 'string', 255);


06 $this->hasColumn('description', 'string', 255);

07 $this->hasColumn('category_id', 'integer', 4);


08 }

09
10 public function setUp() {

11 $this->hasOne('Category', array(
12 'local' => 'category_id',

13 'foreign' => 'id'


14 ));

15 $this->hasMany('Thread as Threads', array(


16 'local' => 'id',
17 'foreign' => 'forum_id'
18 ));

19 }
20

21 }
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
view source
print?
01 <?php
02 class Thread extends Doctrine_Record {

03
04 public function setTableDefinition() {

05 $this->hasColumn('title', 'string', 255);


06 $this->hasColumn('forum_id', 'integer', 4);

07 }
08

09 public function setUp() {


10 $this->hasOne('Forum', array(

11 'local' => 'forum_id',


12 'foreign' => 'id'

13 ));
14 $this->hasMany('Post as Posts', array(

15 'local' => 'id',


16 'foreign' => 'thread_id'
17 ));
18 }

19
20 }
Post Model
• Create: system/application/models/post.php
view source
print?
01 <?php
02 class Post extends Doctrine_Record {

03
04 public function setTableDefinition() {

05 $this->hasColumn('content', 'string', 65535);


06 $this->hasColumn('thread_id', 'integer', 4);

07 $this->hasColumn('user_id', 'integer', 4);


08 }

09
10 public function setUp() {

11 $this->actAs('Timestampable');
12 $this->hasOne('Thread', array(

13 'local' => 'thread_id',


14 'foreign' => 'id'

15 ));
16 $this->hasOne('User', array(

17 'local' => 'user_id',


18 'foreign' => 'id'

19 ));
20 }

21
22 }
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
view source
print?
01 <?php
02 class User extends Doctrine_Record {

03
04 // ...

05
06 public function setUp() {

07 $this->setTableName('user');
08 $this->actAs('Timestampable');

09 $this->hasMutator('password', '_encrypt_password');
10

11 $this->hasMany('Post as Posts', array(


12 'local' => 'id',

13 'foreign' => 'user_id'


14 ));

15 }
16 //...

17 }
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
view source
print?
01 <?php
02 class Test extends Controller {

03
04 function index() {

05
06 $category = new Category();

07 $category->title = "The CodeIgniter Lounge";


08 $category->save();

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

13
14 $forum->Category = $category;

15
16 $forum->save();

17 }
18

19 }
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
view source
print?
01 <?php
02 class Test extends Controller {

03
04 function index() {

05
06 $category = new Category();

07 $category->title = "The CodeIgniter Lounge";


08

09 $forum = new Forum();


10 $forum->title = "Introduce Yourself!";

$forum->description = "Use this forum to introduce yourself to the


11
CodeIgniter community, or to announce your new CI powered site.";
12 // add category to the forum

13 $forum->Category = $category;
14

15 $forum2 = new Forum();


16 $forum2->title = "The Lounge";

$forum2->description = "CodeIgniter's social forum where you can


17 discuss anything not related to development. No topics off limits... but be
civil.";
18

19 // you can also add the other way around


20 // add forum to the category

21 $category->Forums []= $forum2;


22

23 // a different syntax (array style)


24

25 $category2 = new Category();


26 $category2['title'] = "CodeIgniter Development Forums";

27
28 $forum3 = new Forum();

29 $forum3['title'] = "CodeIgniter Discussion";


$forum3['description'] = "This forum is for general topics related
30
to CodeIgniter";

31
32 $forum4 = new Forum();

33 $forum4['title'] = "Code and Application Development";


34 $forum4['description'] = "Use the forum to discuss anything related
to programming and code development.";

35
36 $category2['Forums'] []= $forum3;

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


38

39 // flush() saves all unsaved objects


40 $conn = Doctrine_Manager::connection();

41 $conn->flush();
42

43 echo "Success!";
44

45 }
46

47 }
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.
• Go to: http://localhost/ci_doctrine/test
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.
Day 7 – Fixtures & Forum List
First, Some Styling
Before we get started, let’s add some style so that pages look somewhat decent. Just copy-paste,
and don’t read it. CSS is not our focus today.
• Edit: css/style.css
view source
print?
001 body {
002 font-family: "Trebuchet MS",Arial;

003 font-size: 14px;


004 background-color: #212426;

005 color: #B9AA81;


006 }

007
008 a {

009 color: #FFF;


010 }

011
012 a:hover {

013 color: #B9AA81;


014 }

015
016 input, textarea, select {

017 font-family:inherit;
018 font-size:inherit;

019 font-weight:inherit;
020 }
021
022 /* FORUMS -----------------------------------------*/

023
024 div.forums {

025 width: 720px;


026 margin: auto;

027 }
028

029 .forums h2 {
030 font-size: 16px;

031 color: #000;

padding: 5px 10px 5px


032
10px;

033 margin: 0px;


034 background-color: #BBB;

035 -moz-border-radius-topleft: 6px;


036 -moz-border-radius-topright: 6px;

037 -webkit-border-top-left-radius: 6px;


038 -webkit-border-top-right-radius: 6px;

039 }
040

041 .forums h3 {
042 font-size: 15px;

043 margin: 0px;


044 }
045
046 .category {

047 margin-bottom: 40px;


048 }

049
050 .forum {

051 border-bottom: 1px solid #666;


052 padding: 10px;

053 }
054

055 .forum .description {


056 font-size: 14px;

057 }
058

059 /* SIGNUP FORM ------------------------------------*/


060 #signup_form {

061 margin-left: auto;


062 margin-right: auto;

063 width: 360px;


064 font-size: 16px;

065 }
066

067 #signup_form .heading {


068 text-align: center;

069 font-size: 22px;


070 font-weight: bold;

071 color: #B9AA81;


072 }

073
074 #signup_form form {

075 background-color: #B9AA81;


076 padding: 10px;

077 border-radius: 8px;


078 -moz-border-radius: 8px;

079 -webkit-border-radius: 8px;


080 }

081
082 #signup_form form label {

083 font-weight: bold;


084 color: #11151E;

085 }
086

087 #signup_form form input[type=text],input[type=password] {


088 width: 316px;

089 font-weight: bold;


090 padding: 8px;

091 border: 1px solid #FFF;


092 border-radius: 4px;

093 -moz-border-radius: 4px;


094 -webkit-border-radius: 4px;
095 }
096

097 #signup_form form input[type=submit] {


098 display: block;

099 margin: auto;


100 width: 200px;

101 font-size: 18px;


102 background-color: #FFF;

103 border: 1px solid #BBB;


104

105 }
106

107 #signup_form form input[type=submit]:hover {


108 border-color: #000;

109 }
110

111 #signup_form .error {


112 font-size: 13px;

113 color: #690C07;


114 font-style: italic;

115 }
Ok, now we’re ready to get more coding done!
The Home Page
We are about to work on the Controller and the View for our Home Page (i.e. Forum List). We
want this page to show all of the Forums broken down by Categories.
Home Controller
• Edit: system/application/controllers/home.php
view source
print?
01 <?php
02 class Home extends Controller {

03
04 public function index() {

05
06 $vars['categories'] = Doctrine::getTable('Category')->findAll();

07
08 $this->load->view('home', $vars);

09 }
10

11 }
As mentioned before, variables are passed to a View in an array, and the array indexes become
variable names. In other words, $vars['categories'] becomes $categories inside the View.
Doctrine::getTable(‘Category’)->findAll(); gives us all of the Category records, in the form of
a Doctrine_Collection object.
Home View
(I’m going build this page in a few steps)
• Edit: system/application/views/home.php
First we make a blank page, with a container div and a heading:
view source
print?
01 <!DOCTYPE html>
02 <html lang="en">

03 <head>
04 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

05 <title>Home</title>

<link rel="stylesheet" href="<?php echo base_url(); ?


06
>css/style.css"

07 type="text/css" media="all">
08 </head>
09 <body>
10

11 <div class="forums">
12

13 <h1>CI+Doctrine Message Board</h1>


14

15 </div>
16

17 </body>
18 </html>
Next, we loop thru the $categories object, and display titles for each Category:
view source
print?
01 <!DOCTYPE html>
02 <html lang="en">

03 <head>
04 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

05 <title>Home</title>

<link rel="stylesheet" href="<?php echo base_url(); ?


06
>css/style.css"

07 type="text/css" media="all">
08 </head>

09 <body>
10

11 <div class="forums">
12

13 <h1>CI+Doctrine Message Board</h1>


14
<?php foreach($categories as $category): ?
15
>
16

17 <div class="category">
18

19 <h2><?php echo $category->title; ?></h2>


20

21 </div>
22

23 <?php endforeach; ?>


24

25 </div>
26

27 </body>
28 </html>
Note: $categories is a Doctrine_Collection object here, but it can be treated like an array. So we
can use foreach on it.
Now we are going to show each Forum, their descriptions, and number of Threads.
view source
print?
01 <!DOCTYPE html>
02 <html lang="en">

03 <head>
04 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

05 <title>Home</title>

<link rel="stylesheet" href="<?php echo base_url(); ?


06
>css/style.css"

07 type="text/css" media="all">
08 </head>

09 <body>
10

11 <div class="forums">
12

13 <h1>CI+Doctrine Message Board</h1>


14

<?php foreach($categories as $category): ?


15
>
16

17 <div class="category">
18

19 <h2><?php echo $category->title; ?></h2>


20

21 <?php foreach($category->Forums as $forum): ?>


22

23 <div class="forum">
24

25 <h3>

<?php echo anchor('forums/'.$forum->id, $forum-


26
>title) ?>

(<?php echo $forum->Threads->count(); ?>


27
threads)
28 </h3>

29
30 <div class="description">

31 <?php echo $forum->description; ?>


32 </div>

33
34 </div>

35
36 <?php endforeach; ?>

37
38 </div>

39
40 <?php endforeach; ?>

41
42 </div>

43
44 </body>

45 </html>
As you can see, getting all Forums under a Category is as easy as calling $category->Forums.
And again, we can treat it as an array and loop thru it.
Line 26: We are creating a link to the Forum. It will link to a Controller named Forums and
pass the id in the url. And the link text is the Forum title.
Note: In CodeIgniter, Controllers and Models cannot have the same name. That’s why I linked
to ‘forums’ instead of ‘forum’. Some people might prefer ‘view_forum’ or ‘show_forum’ etc…
Line 27: This is the first time we are using count(). That’s how we get the number of Threads
that belong to that Forum object.
The Result
• Go to: http://localhost/ci_doctrine/
You will see this:
Looks nice. But the links are broken since we haven’t created the Forums Controller (i.e.
Forum Page) yet.
Now, we could go ahead and build the Forum Page, but it’s just not going to look very good
since we have no Threads or Posts whatsoever. This is a great time to start talking about Data
Fixtures.
Doctrine Data Fixtures
What are Data Fixtures?
“Data fixtures are meant for loading small sets of test data through your models to populate
your database with data to test against.” (from here).
This will do the job for filling the database with some data, so we can do proper testing as we
develop our project.
In the last article, we created a Test Controller for inserting some data. That worked well for this
tutorial, and we saw some code examples along the way. But on a real development
environment, it’s better to just use Data Fixtures.
Creating Fixtures
First, we need to have a place to put our Fixtures. You can put this folder anywhere, but I would
like to create it directly under the applications folder.
• Create Folder: system/application/fixtures
• Create: system/application/fixtures/data.yml
That’s right, we are creating a YAML file. Don’t worry if you are not familiar with this file
format.
We are going to start small with an example:
view source
print?
1 User:
2 Admin:

3 username: Administrator
4 password: testing

5 email: [email protected]
6 Test:

7 username: TestUser
8 password: mypass

9 email: [email protected]
Even if you are not familiar with YAML, the structure is easy to understand.
This code just creates 2 User records.
Note: I used 2 spaces for indentation, but it can be any number of spaces. And Tabs do NOT
work.
• First line is the name of the Model we are loading data for.
• Line 2 and 6: These are just names for these records. They can be anything.
We can use them later in the fixture to reference these records.
• The rest should be obvious. We are assigning data to each field for these
records.
Now, we need to load this fixture into the database.
• Edit: system/application/controllers/doctrine_tools.php
view source
print?
01 <?php
02 class Doctrine_Tools extends Controller {

03
04 // ....

05
06 function load_fixtures() {

07 echo 'This will delete all existing data!<br />


08 <form action="" method="POST">

<input type="submit" name="action" value="Load Fixtures"><br


09
/><br />';
10
11 if ($this->input->post('action')) {
12

13 Doctrine_Manager::connection()->execute(
14 'SET FOREIGN_KEY_CHECKS = 0');

15
16 Doctrine::loadData(APPPATH.'/fixtures');

17 echo "Done!";
18 }

19 }
20

21 }
We added a function named load_fixtures() to this Controller.
Doctrine:loadData() does the work. Every time it is called, it will purge existing data from the
tables, and load the Data Fixtures from the given path. If you don’t want the table purge to
happen, you need to pass a second argument as true.
(I also had to disable FOREIGN_KEY_CHECKS to avoid some foreign key related errors
during the purge.)
• Go to: http://localhost/ci_doctrine/doctrine_tools/load_fixtures and click the
button.
• Now check the user table in phpmyadmin.
Now that everything seems to be working, let’s expand our fixture so we have more data to work
with.
• Edit: system/application/fixtures/data.yml
view source
print?
01 User:
02 Admin:

03 username: Administrator
04 password: testing

05 email: [email protected]
06 Test:

07 username: TestUser
08 password: mypass

09 email: [email protected]
10 Foo:

11 username: Foobar
12 password: mypass

13 email: [email protected]
14

15 Forum:
16 Forum_1:

17 title: Introduce Yourself!


18 description: >

19 Use this forum to introduce yourself to the CodeIgniter community,


20 or to announce your new CI powered site.

21 Forum_2:
22 title: The Lounge
23 description: >
24 CodeIgniter's social forum where you can discuss anything not related

25 to development. No topics off limits... but be civil.


26 Forum_3:

27 title: CodeIgniter Discussion


28 description: This forum is for general topics related to CodeIgniter.

29 Forum_4:
30 title: Code and Application Development

31 description: >
32 Use the forum to discuss anything related to

33 programming and code development.


34 Forum_5:

35 title: Ignited Code


36 description: >

Use this forum to post plugins, libraries, or other code


37
contributions,
38 or to ask questions about any of them.

39
40 Category:

41 Lounge:
42 title: The CodeIgniter Lounge

43 Forums: [Forum_1, Forum_2]


44 Dev:

45 title: CodeIgniter Development Forums


46 Forums: [Forum_3, Forum_4, Forum_5]
47
48 Thread:

49 Thread_1:
50 title: Hi there!

51 Forum: Forum_1
52 Thread_2:

53 title: Greetings to all


54 Forum: Forum_1

55
56 Post:

57 Post_1:
58 Thread: Thread_1

59 User: Test
60 created_at: 2009-11-20 01:20:30

61 content: >
62 Hello everyone! My name is Test, and I go to school at

63 Test Institute of Technology in the US.


64 I just found CodeIgniter some time last week and have been

65 reading through the documentation trying to get myself acquainted.


66

Hopefully the forums will be a great help! I already have some


67
questions.
68 Thanks!

69 Post_2:
70 Thread: Thread_1

71 User: Admin
72 created_at: 2009-11-20 02:15:33

73 content: Welcome Test! Nice to meet you.


74 Post_3:

75 Thread: Thread_2
76 User: Foo

77 created_at: 2009-11-19 12:14:50


78 content: I am new here. Just wanted to say hi.
This is just a bunch of structured data, nothing complicated. For each Model, we create a few
records. And we have a few new things to mention:
Line 18: Here you can see the syntax for adding a long string value in multiple lines.
Line 51: We are setting up the relationships between records. This line says Thread_1 belongs
to Forum_1.
Line 43: Again, we are setting up relationships, but this time we are assigning multiple records,
and you see the syntax for that.
• Go to: http://localhost/ci_doctrine/doctrine_tools/load_fixtures and click the
button.
• And, go to phpmyadmin and look at all the tables.

We just created 15 records!


See the Results
• Go to: http://localhost/ci_doctrine/
We can see all the Categories and Forums we just created. Also there are 2 Threads now, as we
can see from the count.
Day 8 – Hooks, Profiling & DQL
Before we start going into Hooks, Profiling and DQL, let’s make a small addition to our Message
Board home page.
User Controls
In Message Boards, there is usually a section in the top right, with user specific info and links. I
am not sure if there is a better name for it, but I’m going to call it “User Controls”.
On the CodeIgniter Forums it looks like this:

And if you are not logged in, it looks like this:

Now we are going add something similar, but simpler, to our Message Board.
Home View
• Edit: system/application/views/home.php
view source
print?
01 <!DOCTYPE html>
02 <html lang="en">

03 <head>
04 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

05 <title>Home</title>
06 <link rel="stylesheet" href="<?php echo base_url(); ?>css/style.css"

07 type="text/css" media="all">
08 </head>

09 <body>
10

11 <div class="forums">
12

13 <div class="user_controls">
14 <?php if ($user = Current_User::user()): ?>

15 Hello, <em><?php echo $user->username; ?></em> <br/>


16 <?php echo anchor('logout', 'Logout'); ?>

17 <?php else: ?>


18 <?php echo anchor('login','Login'); ?> |

19 <?php echo anchor('signup', 'Register'); ?>


20 <?php endif; ?>

21 </div>
22

23 <h1>CI+Doctrine Message Board</h1>


24

25 <?php foreach($categories as $category): ?>


26

27 <div class="category">
28

29 <h2><?php echo $category->title; ?></h2>


30

31 <?php foreach($category->Forums as $forum): ?>


32

33 <div class="forum">
34

35 <h3>

<?php echo anchor('forums/'.$forum->id, $forum-


36
>title) ?>

37 (<?php echo $forum->Threads->count(); ?> threads)


38 </h3>

39
40 <div class="description">

41 <?php echo $forum->description; ?>


42 </div>

43
44 </div>

45
46 <?php endforeach; ?>

47
48 </div>

49
50 <?php endforeach; ?>

51
52 </div>
53
54 </body>

55 </html>
We just added the highlighted lines.
Line 13: We are using the Current_User class, to see if the user is logged in. If they are logged
in, we will get an instance of the User Model. If not, it will return false.
Lines 15-16: We display the username, and link to the Logout Controller.
Lines 18-19: This section is for people who are not logged in. We link to the Login Controller
and Signup Controller.
So we pretty much have just utilized Models and Controllers we have built before.
We need to make a small addition to the css.
• Edit: css/style.css
• Add this to the end of the file.
view source
print?
1 /* USER CONTROL BOX -------------------------------*/
2 .user_controls {

3 float: right;
4 text-align: right;

5}
Testing
• Go to: http://localhost/ci_doctrine/
The links are showing up at the top right corner.
• Click Login.
• Login as Testuser:mypass .
Now we can see the username and the Logout link.
We will add more features to this later in other tutorials.
Profiling with Doctrine
Using Doctrine, our code is cleaner, smaller and easier to read and maintain. But it is sometimes
a good idea to look at a few things that are going on behind the scenes.
For example, Doctrine executes several SQL queries for us, while we use our Models. It would
be nice to know what these queries are, so we can see if there is more room for optimization.
This can be accomplished by Profiling.
For this, we are going to be using a Doctrine component called Doctrine_Connection_Profiler.
Profiling the Home Page
Let’s look at what queries are executed on the home page. We are going to make a quick and
dirty test for now. Later in the article we’ll make a more re-usable version.
There is going to be lots of new code here, but I will explain them line by line:
• Edit: system/application/controllers/test.php
view source
print?
01 <?php
02 class Test extends Controller {

03
04 //...
05
06 function home_profiler() {

07
08 // set up the profiler

09 $profiler = new Doctrine_Connection_Profiler();


foreach (Doctrine_Manager::getInstance()->getConnections() as $conn)
10
{

11 $conn->setListener($profiler);
12 }

13
14 // copied from home controller

15 $vars['categories'] = Doctrine::getTable('Category')->findAll();
16

17 $this->load->view('home', $vars);
18

19 // analyze the profiler data


20 $time = 0;

21 $events = array();
foreach ($profiler as $event)
22
{

23 $time += $event->getElapsedSecs();
if ($event->getName() == 'query' || $event->getName() ==
24
'execute') {

25 $event_details = array(
26 "type" => $event->getName(),

27 "query" => $event->getQuery(),


28 "time" => sprintf("%f", $event->getElapsedSecs())

29 );
30 if (count($event->getParams())) {

31 $event_details["params"] = $event->getParams();
32 }

33 $events []= $event_details;


34 }

35 }
36 print_r($events);

37 echo "\nTotal Doctrine time: " . $time . "\n";


38 echo "Peak Memory: " . memory_get_peak_usage() . "\n";

39 }
40

41 }
Lines 15-17 are just copied from the home Controller. Before that, we set up the Profiler, and
after that we look at the data gathered by the Profiler.
Lines 9-12: First we create a Profiler object. Then we attach it to every Doctrine connection. In
our case, it will be just one connection. Now it’s ready to listen and record all Doctrine events.
Line 22: The Profiler object let’s us loop through it and get each Event object. Events can be
executed queries and also other things like fetching row data etc.. But we are only going to look
at query events.
Lines 20,23 and 37: Each Event has information on how much time it took. So we will add them
up in $time variable and display it at the end.
Line 24: We are only interested in SQL queries. These can be types ‘query’ or ‘execute’. If a
query has assigned parameters (for example ‘WHERE id = ?’), then it is of type ‘execute’,
otherwise it is ‘query’.
Lines 25-32: We create an array ($event_details) with information about the query. The type, the
query SQL, and the time it took to run. Also if there were any parameters, we add that too.
Lines 33 and 36: We add it all into the $events array, and just dump it at the end with print_r().
Line 38: We use the memory_get_peak_usage() PHP function to find out what the maximum
memory usage was during the script execution.
• Go to: http://localhost/ci_doctrine/test/home_profiler
• View Source:
Array
(
[0] => Array
(
[type] => query
[query] => SELECT c.id AS c__id, c.title AS c__title FROM
category c
[time] => 0.000220
)

[1] => Array


(
[type] => execute
[query] => SELECT u.id AS u__id, u.username AS u__username,
u.password AS u__password, u.email AS u__email, u.created_at AS
u__created_at, u.updated_at AS u__updated_at FROM user u WHERE u.id = ? LIMIT
1
[time] => 0.000310
[params] => Array
(
[0] => 2
)

[2] => Array


(
[type] => execute
[query] => SELECT f.id AS f__id, f.title AS f__title,
f.description AS f__description, f.category_id AS f__category_id FROM forum f
WHERE f.category_id IN (?)
[time] => 0.000322
[params] => Array
(
[0] => 1
)

[3] => Array


(
[type] => execute
[query] => SELECT t.id AS t__id, t.title AS t__title, t.forum_id
AS t__forum_id FROM thread t WHERE t.forum_id IN (?)
[time] => 0.000208
[params] => Array
(
[0] => 1
)

[4] => Array


(
[type] => execute
[query] => SELECT t.id AS t__id, t.title AS t__title, t.forum_id
AS t__forum_id FROM thread t WHERE t.forum_id IN (?)
[time] => 0.000169
[params] => Array
(
[0] => 2
)

[5] => Array


(
[type] => execute
[query] => SELECT f.id AS f__id, f.title AS f__title,
f.description AS f__description, f.category_id AS f__category_id FROM forum f
WHERE f.category_id IN (?)
[time] => 0.000322
[params] => Array
(
[0] => 2
)

[6] => Array


(
[type] => execute
[query] => SELECT t.id AS t__id, t.title AS t__title, t.forum_id
AS t__forum_id FROM thread t WHERE t.forum_id IN (?)
[time] => 0.000238
[params] => Array
(
[0] => 3
)

[7] => Array


(
[type] => execute
[query] => SELECT t.id AS t__id, t.title AS t__title, t.forum_id
AS t__forum_id FROM thread t WHERE t.forum_id IN (?)
[time] => 0.000171
[params] => Array
(
[0] => 4
)

[8] => Array


(
[type] => execute
[query] => SELECT t.id AS t__id, t.title AS t__title, t.forum_id
AS t__forum_id FROM thread t WHERE t.forum_id IN (?)
[time] => 0.000160
[params] => Array
(
[0] => 5
)

)
)

Total Doctrine time: 0.013517618179321


Peak Memory: 5849232
As you can see, there were 9 queries executed by Doctrine. That seems too many. This happens
because of the way Doctrine does ‘lazy loading’. It fetches data from the database as we access
the objects and their parameters.
Later in the article, we are going to see how we can optimize this with DQL.
Let’s see how we can use the Profiler for the entire application, in a re-usable way. For that,
we’re going to look at CodeIgniter Hooks.
CodeIgniter Hooks
With Hooks, we can tell CodeIgniter to perform an operation, at a given point in the application
flow.
For example, we are going to initialize the Profiler, before any Controller function is called.
Enabling Hooks
Hooks are disabled by default, so we must first enable them.
• Edit: system/application/config/config.php
view source
print?
01 // ...
02

03 /*
04 |--------------------------------------------------------------------------

05 | Enable/Disable System Hooks


06 |--------------------------------------------------------------------------

07 |
08 | If you would like to use the "hooks" feature you must enable it by

09 | setting this variable to TRUE (boolean). See the user guide for details.
10 |

11 */
12 $config['enable_hooks'] = TRUE;

13
14 // ...
Just changed it from FALSE to TRUE. Now we can use Hooks.
Creating a Profiler Hook
Let’s create the functions that will be called for this hook.
• Create: system/application/hooks/doctrine_profiler_hooks.php
view source
print?
01 <?php
02 class Doctrine_Profiler_Hooks {

03
04 public static $profiler;

05
06 public function profiler_start() {

07
08 self::$profiler = new Doctrine_Connection_Profiler();

foreach (Doctrine_Manager::getInstance()->getConnections() as $conn)


09
{
10 $conn->setListener(self::$profiler);

11 }
12

13 }
14

15 public function profiler_end() {


16

17 // analyze the profiler data


18 $time = 0;

19 $events = array();
foreach (self::$profiler as $event)
20
{
21 $time += $event->getElapsedSecs();
if ($event->getName() == 'query' || $event->getName() ==
22
'execute') {

23 $event_details = array(
24 "type" => $event->getName(),

25 "query" => $event->getQuery(),


26 "time" => sprintf("%f", $event->getElapsedSecs())

27 );
28 if (count($event->getParams())) {

29 $event_details["params"] = $event->getParams();
30 }

31 $events []= $event_details;


32 }

33 }
34

$output = "<"."?php if ( ! defined('BASEPATH')) exit('No direct


35
script access allowed'); ?".">\n\n";
36 $output .= print_r($events,1);

37 $output .= "\nTotal Doctrine time: " . $time . "\n";


38 $output .= "Peak Memory: " . memory_get_peak_usage() . "";

39
40 file_put_contents(BASEPATH."/logs/doctrine_profiler.php", $output);

41 }
42 }

The code is mostly the same as before. This time we are storing the $profiler as a static variable
in this class. Also, we are writing the output to a log file instead of dumping it into the browser.
Adding the Hooks
Now we need to add the hooks to the config so they get executed.
• Edit: system/application/config/hooks.php
view source
print?
01 <?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
02 /*

03 | -------------------------------------------------------------------------
04 | Hooks

05 | -------------------------------------------------------------------------
06 | This file lets you define "hooks" to extend CI without hacking the core

07 | files. Please see the user guide for info:


08 |

09 | http://codeigniter.com/user_guide/general/hooks.html
10 |

11 */
12

13 $hook['post_controller_constructor'][] = array(
14 'class' => 'Doctrine_Profiler_Hooks',

15 'function' => 'profiler_start',


16 'filename' => 'doctrine_profiler_hooks.php',

17 'filepath' => 'hooks',


18 );

19
20 $hook['post_controller'][] = array(

'class' =>
21
'Doctrine_Profiler_Hooks',
22 'function' => 'profiler_end',
23 'filename' => 'doctrine_profiler_hooks.php',
24 'filepath' => 'hooks',

25 );
26

27 /* End of file hooks.php */


28 /* Location: ./system/application/config/hooks.php */
So we store the hooks inside the $hook array. The index of the array is name of the hook. In this
case we used the ‘post_controller_constructor’ and ‘post_controller’ Hooks.
The array structure should be easy to understand. We point to the class name, function, file path,
file name, so that CodeIgniter can find our code for execution.
(Note: I would have preferred to use the “pre_controller” Hook, because it happens before even
the Controller constructor is called. However, the plug-ins are not initialized at that point,
including Doctrine, so we need to use “post_controller_constructor” instead.)
Testing
• Go to: http://localhost/ci_doctrine/
You should just see the homepage with no errors. If you are getting file access errors, make sure
the system/logs folder is writable.
• Open: system/logs/doctrine_profiler.php
You should see something like this:
view source
print?
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
001
?>
002

003 Array
004 (

005 [0] => Array


006 (

007 [type] => query


[query] => SELECT c.id AS c__id, c.title AS c__title FROM
008
category c

009 [time] => 0.000220


010 )
011
012 [1] => Array

013 (
014 [type] => execute

[query] => SELECT u.id AS u__id, u.username AS u__username,


u.password AS u__password, u.email AS u__email, u.created_at AS
015
u__created_at, u.updated_at AS u__updated_at FROM user u WHERE u.id = ?
LIMIT 1
016 [time] => 0.000310

017 [params] => Array


018 (

019 [0] => 2


020 )

021
022 )

023
024 [2] => Array

025 (
026 [type] => execute

[query] => SELECT f.id AS f__id, f.title AS f__title,


027 f.description AS f__description, f.category_id AS f__category_id FROM
forum f WHERE f.category_id IN (?)
028 [time] => 0.000322

029 [params] => Array


030 (

031 [0] => 1


032 )
033
034 )

035
036 [3] => Array

037 (
038 [type] => execute

[query] => SELECT t.id AS t__id, t.title AS t__title,


039
t.forum_id AS t__forum_id FROM thread t WHERE t.forum_id IN (?)
040 [time] => 0.000208

041 [params] => Array


042 (

043 [0] => 1


044 )

045
046 )

047
048 [4] => Array

049 (
050 [type] => execute

[query] => SELECT t.id AS t__id, t.title AS t__title,


051
t.forum_id AS t__forum_id FROM thread t WHERE t.forum_id IN (?)
052 [time] => 0.000169

053 [params] => Array


054 (

055 [0] => 2


056 )
057
058 )

059
060 [5] => Array

061 (
062 [type] => execute

[query] => SELECT f.id AS f__id, f.title AS f__title,


063 f.description AS f__description, f.category_id AS f__category_id FROM
forum f WHERE f.category_id IN (?)
064 [time] => 0.000322

065 [params] => Array


066 (

067 [0] => 2


068 )

069
070 )

071
072 [6] => Array

073 (
074 [type] => execute

[query] => SELECT t.id AS t__id, t.title AS t__title,


075
t.forum_id AS t__forum_id FROM thread t WHERE t.forum_id IN (?)
076 [time] => 0.000238

077 [params] => Array


078 (

079 [0] => 3


080 )
081
082 )

083
084 [7] => Array

085 (
086 [type] => execute

[query] => SELECT t.id AS t__id, t.title AS t__title,


087
t.forum_id AS t__forum_id FROM thread t WHERE t.forum_id IN (?)
088 [time] => 0.000171

089 [params] => Array


090 (

091 [0] => 4


092 )

093
094 )

095
096 [8] => Array

097 (
098 [type] => execute

[query] => SELECT t.id AS t__id, t.title AS t__title,


099
t.forum_id AS t__forum_id FROM thread t WHERE t.forum_id IN (?)
100 [time] => 0.000160

101 [params] => Array


102 (

103 [0] => 5


104 )

105
106 )

107
108 )

109
110 Total Doctrine time: 0.013517618179321

111 Peak Memory: 5849232


Let’s see how we can optimize this and reduce the number of queries executed.
Optimizing with DQL
In the Day 5 – CRUD article, we had a first look at DQL. Now we are going to use it in our
application.
If you are familiar with the CodeIgniter Active Records class, there are some similarities.
However DQL is much more powerful.
Reducing the Number of Queries
• Edit: system/application/controllers/home.php
view source
print?
01 <?php
02 class Home extends Controller {

03
04 public function index() {

05
06 // $vars['categories'] = Doctrine::getTable('Category')->findAll();

07 $vars['categories'] = Doctrine_Query::create()
08 ->select('c.title, f.title, f.description')

09 ->from('Category c, c.Forums f')


10 ->execute();

11
12 $this->load->view('home', $vars);
13 }
14

15 }
We just commented out the findAll() line, and added a DQL call instead.
In the select() call we specify which fields we want to fetch.
In the from() call, we put the Models and use short aliases for them (c and f). Note that, we used
c.Forums instead c.Forum, even though the Model was named Forum. This is because we are
joining two models here, and in the Category Model definition, we referred to the Forum model
as Forums:
view source
print?
01 <?php
02

03 // models/category.php
04

05 class Category extends Doctrine_Record {


06

07 public function setTableDefinition() {


08 $this->hasColumn('title', 'string', 255);

09 }
10

11 public function setUp() {


12 $this->hasMany('Forum as Forums', array(

13 'local' => 'id',


14 'foreign' => 'category_id'

15 ));
16 }

17 }
It was just more suitable to name in plural since it’s a one-to-many relationship.
Another thing to note is that we did not specify an ON clause in DQL. Normally when you join
tables in SQL, you would specify the columns you are joining on. But since our model
definitions know how these relationships are set up, ON clause is not necessary in this case.
Finally, we run execute() to fetch the Doctrine_Collection object.
Let’s load the home page again, and then look at the profiler output.
• First go to: http://localhost/ci_doctrine/
• Then open: system/logs/doctrine_profiler.php
You should see something like this:
view source
print?
<?php if ( ! defined('BASEPATH')) exit('No direct script access
01
allowed'); ?>
02

03 Array
04 (

05 [0] => Array


06 (

07 [type] => query


[query] => SELECT c.id AS c__id, c.title AS c__title, f.id AS
08 f__id, f.title AS f__title, f.description AS f__description FROM category c
LEFT JOIN forum f ON c.id = f.category_id

09 [time] => 0.000311


10 )

11
12 [1] => Array

13 (
14 [type] => execute

[query] => SELECT u.id AS u__id, u.username AS u__username,


u.password AS u__password, u.email AS u__email, u.created_at AS
15
u__created_at, u.updated_at AS u__updated_at FROM user u WHERE u.id = ?
LIMIT 1
16 [time] => 0.000307

17 [params] => Array


18 (

19 [0] => 2
20 )

21
22 )

23
24 [2] => Array

25 (
26 [type] => execute

[query] => SELECT t.id AS t__id, t.title AS t__title,


27
t.forum_id AS t__forum_id FROM thread t WHERE t.forum_id IN (?)
28 [time] => 0.000274

29 [params] => Array


30 (

31 [0] => 1
32 )

33
34 )

35
36 [3] => Array

37 (
38 [type] => execute

[query] => SELECT t.id AS t__id, t.title AS t__title,


39
t.forum_id AS t__forum_id FROM thread t WHERE t.forum_id IN (?)
40 [time] => 0.000172
41 [params] => Array
42 (

43 [0] => 2
44 )

45
46 )

47
48 [4] => Array

49 (
50 [type] => execute

[query] => SELECT t.id AS t__id, t.title AS t__title,


51
t.forum_id AS t__forum_id FROM thread t WHERE t.forum_id IN (?)
52 [time] => 0.000214

53 [params] => Array


54 (

55 [0] => 3
56 )

57
58 )

59
60 [5] => Array

61 (
62 [type] => execute

[query] => SELECT t.id AS t__id, t.title AS t__title,


63
t.forum_id AS t__forum_id FROM thread t WHERE t.forum_id IN (?)
64 [time] => 0.000154
65 [params] => Array
66 (

67 [0] => 4
68 )

69
70 )

71
72 [6] => Array

73 (
74 [type] => execute

[query] => SELECT t.id AS t__id, t.title AS t__title,


75
t.forum_id AS t__forum_id FROM thread t WHERE t.forum_id IN (?)
76 [time] => 0.000311

77 [params] => Array


78 (

79 [0] => 5
80 )

81
82 )

83
84 )

85
86 Total Doctrine time: 0.012369155883789

87 Peak Memory: 5856440


Now we went down to 7 queries instead of 9. However, there is still room for improvement.
Getting Count
Currently, most of these queries are caused by the count() call in our Home View.
view source
print?
1 (<?php echo $forum->Threads->count(); ?> threads)
Now we are going to fetch the thread count with DQL so that these extra queries do not need to
run.
• Edit: system/application/controllers/home.php
view source
print?
01 <?php
02 class Home extends Controller {

03
04 public function index() {

05
06 // $vars['categories'] = Doctrine::getTable('Category')->findAll();

07 $vars['categories'] = Doctrine_Query::create()
08 ->select('c.title, f.title, f.description')

09 ->addSelect('t.id, COUNT(t.id) as num_threads')


10 ->from('Category c, c.Forums f')

11 ->leftJoin('f.Threads t')
12 ->groupBy('f.id')

13 ->execute();
14 $this->load->view('home', $vars);

15 }
16

17 }
Highlighted lines are where the changes are.
Line 11: Now we are selecting from the Threads Model too, so we can count them. I could have
added the ‘Threads’ model into the from() call in Line 10. But I wanted to demonstrate another
way you can do joins in DQL. There is also innerjoin() if you need it.
Line 9: COUNT(t.id) will give us the number of threads. I could have added this into the select()
in Line 8, but for demonstration purposes again, I used the addSelect() function.
Line 12: Since we want individual Forums returned, and also are counting the Threads per
Forum, we are grouping the results by the Forum id field.
If you wanted to do the same thing in raw SQL, the query would look like this:
SELECT c.id, c.title, f.id, f.title, f.description, COUNT(t.id) AS
num_threads FROM category c LEFT JOIN forum f ON c.id = f.category_id LEFT
JOIN thread t ON f.id = t.forum_id GROUP BY f.id
One More Change
Now we need to do one more change. Since we are going to use the returned ‘num_threads’
value instead of calling $forum->Threads->count(), we need to change a line in the Home View.
• Edit: system/application/views/home.php around line #37
• Just change the highlighted line.
view source
print?
01 <!-- -->
02 <div class="forum">

03
04 <h3>

05 <?php echo anchor('forums/'.$forum->id, $forum->title) ?>


06 (<?php echo $forum->Threads[0]->num_threads; ?> threads)

07 </h3>
08

09 <div class="description">
10 <?php echo $forum->description; ?>

11 </div>
12

13 </div>
14 <!-- -->
At the first glance, the structure might seem a bit odd. Why not ‘$forum->Threads-
>num_threads’? or even ‘$forum->num_threads’?
This has to do with the way the query is structured. We called COUNT() on the Threads.id
field. Therefore the returned data belongs to the Threads relationship, and not to the $forum
object directly.
Also, the relationship is one-to-many. Therefore $forum->Threads is a Doctrine_Collection by
default, instead of a Doctrine_Record. So we treat it like an array, and add the [0] index first
before we can get the data in num_threads.
The result is: $forum->Threads[0]->num_threads
If you are ever unsure about the structure of the returned Doctrine_Collection object, you can
convert it to an array using toArray(true) and dump it on the screen. Passing ‘true’ makes it
‘deep’, otherwise you only get the outermost object.
For example, if you do this:
view source
print?
1 $vars['categories'] = Doctrine_Query::create()
2 ->select('c.title, f.title, f.description')

3 ->addSelect('COUNT(t.id) as num_threads')
4 ->from('Category c, c.Forums f')

5 ->leftJoin('f.Threads t')
6 ->groupBy('f.id')

7 ->execute();
8 print_r($vars['categories']->toArray(true));
You can get an output like this:
Array
(
[0] => Array
(
[id] => 1
[title] => The CodeIgniter Lounge
[Forums] => Array
(
[0] => Array
(
[id] => 1
[title] => Introduce Yourself!
[description] => Use this forum to introduce
yourself to the CodeIgniter community, or to announce your new CI powered
site.

[category_id] => 1
[Category] =>
[Threads] => Array
(
[0] => Array
(
[id] =>
[title] =>
[forum_id] => 1
[Forum] =>
[num_threads] => 2
)

)
)

[1] => Array


(
[id] => 2
[title] => The Lounge
[description] => CodeIgniter's social forum where
you can discuss anything not related to development. No topics off limits...
but be civil.

[category_id] => 1
[Category] =>
[Threads] => Array
(
[0] => Array
(
[id] =>
[title] =>
[forum_id] => 2
[Forum] =>
[num_threads] => 0
)

[num_threads] => 2
)

[1] => Array


(
[id] => 2
[title] => CodeIgniter Development Forums
[Forums] => Array
(
[0] => Array
(
[id] => 3
[title] => CodeIgniter Discussion
[description] => This forum is for general topics
related to CodeIgniter.
[category_id] => 2
[Category] =>
[Threads] => Array
(
[0] => Array
(
[id] =>
[title] =>
[forum_id] => 3
[Forum] =>
[num_threads] => 0
)
)

[1] => Array


(
[id] => 4
[title] => Code and Application Development
[description] => Use the forum to discuss
anything related to programming and code development.

[category_id] => 2
[Category] =>
[Threads] => Array
(
[0] => Array
(
[id] =>
[title] =>
[forum_id] => 4
[Forum] =>
[num_threads] => 0
)

[2] => Array


(
[id] => 5
[title] => Ignited Code
[description] => Use this forum to post plugins,
libraries, or other code contributions, or to ask questions about any of them.

[category_id] => 2
[Category] =>
[Threads] => Array
(
[0] => Array
(
[id] =>
[title] =>
[forum_id] => 5
[Forum] =>
[num_threads] => 0
)

[num_threads] => 0
)
)
Profile Again
Let’s look at the profiling results again to see how the new DQL performed.
• First go to: http://localhost/ci_doctrine/
• Then open: system/logs/doctrine_profiler.php
You should see something like this:
view source
print?
<?php if ( ! defined('BASEPATH')) exit('No direct script access
01
allowed'); ?>
02

03 Array
04 (

05 [0] => Array


06 (

07 [type] => query


[query] => SELECT c.id AS c__id, c.title AS c__title, f.id AS
f__id, f.title AS f__title, f.description AS f__description, COUNT(t.id) AS
08
t__0 FROM category c LEFT JOIN forum f ON c.id = f.category_id LEFT JOIN
thread t ON f.id = t.forum_id GROUP BY f.id

09 [time] => 0.000493


10 )

11
12 [1] => Array

13 (
14 [type] => execute

[query] => SELECT u.id AS u__id, u.username AS u__username,


u.password AS u__password, u.email AS u__email, u.created_at AS
15
u__created_at, u.updated_at AS u__updated_at FROM user u WHERE u.id = ?
LIMIT 1
16 [time] => 0.000354

17 [params] => Array


18 (

19 [0] => 2
20 )

21
22 )

23
24 )

25
26 Total Doctrine time: 0.011896848678589

27 Peak Memory: 6010440


Nice, we are all the way down to 2 queries!
The first one is for fetching all Categories, their Forums, and Thread counts all at once. And the
second query was done by the Current_User class for getting user details. You can’t go much
lower than this.
Conclusions
I admit that our code became a little more complicated when we switched from using findAll() to
DQL. But if your web application is expecting a significant amount of traffic, you might need to
utilize such optimizations. Also, with DQL you can achieve more complicated queries that
would not be practical with the magic functions like findAll() or findBy*().
Optimization is a debatable subject. Some prefer to do it early, in small steps. Some prefer to do
it all the way at the end of development. But as you gain experience, your initial code tends to be
more optimized in the first place, and may not need many changes later on.
I guess the point is, using an ORM like Doctrine does not magically optimize your database
interactions. You still need to put an effort into it.
Day 9 – Templates & Data Hydrators
Templates
What and Why?
The term Template might be a little ambiguous right now. Maybe you are already thinking of
Views as Templates. Which is mostly true. However, there is a problem with using separate
Views for every single page in our application. There will be headers, footers and other content
that will be repeated everywhere. That’s why there is a need for actual Templates for our Views.
For example, we will be creating a new View for the Forum pages. It will look mostly the same
as the home page, using the same css stylesheet, same header, same user control panel, etc…
There will be other Views later that will be the same way. If we ever make a change to any of
these common parts, we don’t want to go through every single View. There should be Views that
have the common html output that we can quickly edit. Using Templates will enable us to do just
that, and maybe more.
Creating a Template
Let’s create our first Template, which is actually going to be a View itself. The idea is to keep all
the common elements in one file, and take out the variable stuff.
For example, take a look at this:

The content section is definitely going to change from page to page. Also, I would like to take
out the user controls section, so it can have its own View. We’re going to replace these with
some code instead.
• Create: system/application/views/template.php
view source
print?
01 <!DOCTYPE html>
02 <html lang="en">

03 <head>
04 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

05 <title><?php echo $title; ?> | CI+Doctrine Message Board</title>


<link rel="stylesheet" href="<?php echo base_url(); ?
06
>css/style.css"

07 type="text/css" media="all">
08 </head>

09 <body>
10

<div class="<?php echo $container_css; ?>


11
container">
12

13 <div class="user_controls">
14 <?php $this->load->view('user_controls'); ?>

15 </div>
16

17 <h1>CI+Doctrine Message Board</h1>


18

19 <?php $this->load->view($content_view); ?>


20

21 </div>
22

23 </body>
24 </html>
I have copied the contents of the Home View we created before, and replaced a few things.
Line 14: The code for the user_controls div will now be found in a View named
user_controls.php.
Line 19: $content_view is the name of the View that will be loaded to fill up the content area.
For example, we will use a View named forum_list for the home page. The controller will have
to pass this variable.
Line 5: Now the page title can be passed as a $title variable.
Line 11: We can apply a different css class for the container div on every page.
As you can see, a View can load other Views. That’s a big help in creating Templates. Now, all
Controllers can just load this Template View, and they can pass the name for a content View that
will be loaded in the middle of the page.
Now let’s create the user_controls View.
• Create: system/application/views/user_controls.php
view source
print?
1 <?php if ($user = Current_User::user()): ?>
2 Hello, <em><?php echo $user->username; ?></em> <br/>

3 <?php echo anchor('logout', 'Logout'); ?>


4 <?php else: ?>

5 <?php echo anchor('login','Login'); ?> |


6 <?php echo anchor('signup', 'Register'); ?>

7 <?php endif; ?>


Basically it’s the same code as before, but now it is in its own View file.
Now let’s create the forum_list View.
• Create: system/application/views/forum_list.php
view source
print?
<?php foreach($categories as $category): ?
01
>
02 <div class="category">

03
04 <h2><?php echo $category->title; ?></h2>

05
06 <?php foreach($category->Forums as $forum): ?>

07 <div class="forum">
08
09 <h3>

<?php echo anchor('forums/display/'.$forum->id, $forum->title) ?


10
>

(<?php echo $forum->Threads[0]->num_threads; ?>


11
threads)
12 </h3>

13
14 <div class="description">

15 <?php echo $forum->description; ?>


16 </div>

17
18 </div>

19 <?php endforeach; ?>


20

21 </div>
22 <?php endforeach; ?>
This code is also taken from the middle section of the old Home View. (I only made a small
change to the link at line 10.)
Now we can get rid of the Home View file.
• Delete: system/application/views/home.php
Updating the Home Controller
This new Template needs to know what content to load, what the title of the page is, etc… So we
need to update the Home Controller.
• Edit: system/application/controllers/home.php
view source
print?
01 <?php
02 class Home extends Controller {

03
04 public function index() {
05
06 $vars['categories'] = Doctrine_Query::create()

07 ->select('c.title, f.title, f.description')


08 ->addSelect('t.id, COUNT(t.id) as num_threads')

09 ->from('Category c, c.Forums f')


10 ->leftJoin('f.Threads t')

11 ->groupBy('f.id')
12 ->execute();

13
14 $vars['title'] = 'Home';

15 $vars['content_view'] = 'forum_list';
16 $vars['container_css'] = 'forums';

17
18 $this->load->view('template', $vars);

19 }
20

21 }
The changes are only the highlighted lines. We are now passing the page title, name of the
content View, and a CSS class name for styling purposes. Then we load the View named
‘template’ instead of ‘home’ (since we deleted that one).
See the Results
• Go to: http://localhost/ci_doctrine/
The page should look exactly the same as before:
Later in this article we are going to use this Template to build the Forum page, that lists Threads
in a Forum.
Data Hydrators (Doctrine)
Our Doctrine Models extend the Doctrine_Record class. We have been using instances of this
class for representing and persisting the database records. Also, we have been using instances of
the Doctrine_Collection class when dealing with multiple records (e.g. fetching multiple rows
with DQL).
With Data Hydrators, we can work with the data in other structure formats. For example, with
DQL we can have it return the results of a SELECT query as an Array instead of
Doctrine_Collection.
HYDRATE_SCALAR
This returns an array similar to what you would get from doing raw SQL queries.
view source
print?
01 $category = Doctrine_Query::create()
02 ->select('c.*, f.*')

03 ->from('Category c, c.Forums f')


04 ->where('c.id = ?', 1)

05 ->setHydrationMode(Doctrine::HYDRATE_SCALAR)
06 ->execute();

07
08 print_r($category);

09 /* output:
10 Array

11 (
12 [0] => Array

13 (
14 [c_id] => 1

15 [c_title] => The CodeIgniter Lounge


16 [f_id] => 1

17 [f_title] => Introduce Yourself!


[f_description] => Use this forum to introduce yourself to the
18
CodeIgniter community, or to announce your new CI powered site.

19
20 [f_category_id] => 1

21 )
22

23 [1] => Array


24 (

25 [c_id] => 1
26 [c_title] => The CodeIgniter Lounge

27 [f_id] => 2
28 [f_title] => The Lounge

29 [f_description] => CodeIgniter's social forum where you can


discuss anything not related to development. No topics off limits... but be
civil.
30

31 [f_category_id] => 1
32 )

33
34 )

35 */
We fetched the Category with id 1, and it’s associated Forums. There were 2 Forums under that
Category, so the result contains 2 rows of data.
If I run the same raw query from phpMyAdmin, it looks like this:

Note that the Category title is repeated in both rows, because of the nature of JOIN queries. That
is also the case in the Array that was returned by DQL.
HYDRATE_ARRAY
This also returns an Array. But this time, it will have a nice multi-tier structure based on the
Model relationships.
view source
print?
01 $category = Doctrine_Query::create()
02 ->select('c.*, f.*')

03 ->from('Category c, c.Forums f')


04 ->where('c.id = ?', 1)

05 ->setHydrationMode(Doctrine::HYDRATE_ARRAY)
06 ->execute();

07
08 print_r($category);

09 /* output:
10 Array

11 (
12 [0] => Array

13 (
14 [id] => 1

15 [title] => The CodeIgniter Lounge


16 [Forums] => Array

17 (
18 [0] => Array

19 (
20 [id] => 1

21 [title] => Introduce Yourself!


[description] => Use this forum to introduce
22 yourself to the CodeIgniter community, or to announce your new CI powered
site.

23
24 [category_id] => 1
25 )
26

27 [1] => Array


28 (

29 [id] => 2
30 [title] => The Lounge

[description] => CodeIgniter's social forum


31 where you can discuss anything not related to development. No topics off
limits... but be civil.
32

33 [category_id] => 1
34 )

35
36 )

37
38 )

39
40 )

41 */
Now we have a multidimensional Array. As you can see, this time the Category info is not
repeated. And Forums is a sub-array under the Category. Also the keys of the array retain their
original names, like ‘title’, instead of ‘c_title’.
Other Hydrators
By default Doctrine is using HYDRATE_RECORD, that causes objects to be returned. And
there are a few other Hydrators you can read about here: http://www.doctrine-
project.org/documentation/manual/1_2/en/data-hydrators
Why?
Hydrators can have certain performance benefits. Creating an array is faster than creating an
object that has lots of overhead. And arrays take less memory too.
Also there are things you can do with arrays that you cannot do with a Doctrine object. If you
prefer to handle the data as an array, Hydration is the way to go.
We are going to be using HYDRATE_ARRAY to fetch a list of Threads in the following
sections, to demonstrate the usage.
CSS Changes
Before we move on, I want to make some CSS changes, so the new pages we build will be
styled.
• Edit: css/style.css
view source
print?
01 body {
02 font-family: "Trebuchet MS",Arial;

03 font-size: 14px;
04 background-color: #212426;

05 color: #B9AA81;
06 }

07 a {color: #FFF;}
08 a:hover {color: #B9AA81;}

09 input, textarea, select {


10 font-family:inherit; font-size:inherit; font-weight:inherit;

11 }
12 .container {width: 720px; margin: auto;}

13 /* FORUMS -----------------------------------------*/
14 .forums.container h2 {

15 font-size: 16px;
16 color: #000;

padding: 5px 10px 5px


17
10px;
18 margin: 0px;

19 background-color: #BBB;
20 -moz-border-radius-topleft: 6px;
21 -moz-border-radius-topright: 6px;
22 -webkit-border-top-left-radius: 6px;

23 -webkit-border-top-right-radius: 6px;
24 }

.forums.container h3 {font-size: 15px; margin:


25
0px;}
26 .forums.container .category {margin-bottom: 40px;}

.forums.container .forum {border-bottom: 1px solid #666; padding:


27
10px;}
28 .forums.container .forum .description {font-size: 14px;}

29 /* FORUM -----------------------------------------*/
30 .forum.container h2 {

31 font-size: 16px;
32 color: #000;

padding: 5px 10px 5px


33
10px;
34 margin: 0px;

35 background-color: #BBB;
36 -moz-border-radius-topleft: 6px;

37 -moz-border-radius-topright: 6px;
38 -webkit-border-top-left-radius: 6px;

39 -webkit-border-top-right-radius: 6px;
40 }

41 .forum.container h3 {font-size: 15px; margin: 0px 0px 5px 0px;}


42 .forum.container .thread {border-bottom: 1px solid #666; padding: 10px;}

43 /* SIGNUP FORM ------------------------------------*/


44 #signup_form {margin: auto; width: 360px; font-size: 16px;}

45 #signup_form .heading {
46 text-align: center; font-size: 22px; font-weight: bold; color: #B9AA81;

47 }
48 #signup_form form {

49 background-color: #B9AA81;
50 padding: 10px;

51 -moz-border-radius: 8px;
52 -webkit-border-radius: 8px;

53 }
54 #signup_form form label {font-weight: bold; color: #11151E;}

55 #signup_form form input[type=text],input[type=password] {


56 width: 316px;

57 font-weight: bold;
58 padding: 8px;

59 border: 1px solid #FFF;


60 -moz-border-radius: 4px;

61 -webkit-border-radius: 4px;
62 }

63 #signup_form form input[type=submit] {


64 display: block;

65 margin: auto;
66 width: 200px;

67 font-size: 18px;
68 background-color: #FFF;
69 border: 1px solid #BBB;
70 }

71 #signup_form form input[type=submit]:hover {border-color: #000;}


72 #signup_form .error {font-size: 13px; color: #690C07; font-style: italic; }

73 /* USER CONTROL BOX -------------------------------*/


74 .user_controls {float: right; text-align: right;}
Forum Pages
Now, let’s use what we have, to build the Forum Pages. These pages will list all the Threads, in a
given Forum.
Currently, on the Home page we have links like this:

We created these links, because we expect to have a Controller named ‘Forums’ that has a
function named ‘Display’.
There is also an ID number at the end of the URL. That is passed to the Controller function as a
parameter.
This Controller is supposed to load a page that lists all the Threads in that particular Forum.
Forums Controller
The skeleton structure first:
• Create: system/application/controllers/forums.php
view source
print?
1 <?php
2 class Forums extends Controller {

3
4 public function display($id) {
5
6 }

7
8}
It looks like any other Controller. But this time we are expecting a parameter being passed to the
display() method. The string that comes after /forums/display/ in the URL automatically gets
passed as the first parameter to that method.
For example:
http://localhost/ci_doctrine/forums/display/1
Will call display(1) in the Forums Class. We are going to use that parameter as the Forum ID.
Then we need to display the Forum page.
The Controller needs to obtain the following pieces of data, and pass them on to the View:
• The title of the Forum.
• Each Thread under the Forum.
• For each Thread, we need Thread title, create date, author name and
number of replies.
Please note that the create date of the Thread is not part of the Thread Model (or table), because
of the way we designed the Models. The first Post inside of the Thread carries a create date
already, so we did not want to duplicate that data again. Same thing goes for the author (or
user_id) of the Thread.
Now let’s add some code to make this happen:
view source
print?
01 <?php
02 class Forums extends Controller {

03
04 public function display($id) {

05
06 $forum = Doctrine::getTable('Forum')->find($id);

07
08 $vars['title'] = $forum['title'];

09 $vars['threads'] = $forum->getThreadsArray();
10 $vars['content_view'] = 'forum';

11 $vars['container_css'] = 'forum';
12

13 $this->load->view('template', $vars);
14

15 }
16

17 }
First we get an instance of the Forum object with the id $id. Then we create the array of variables
that will be passed to the template View.
At line 9 you can see that I am attempting to call a method named getThreadsArray(). This
does not exist yet, so let’s build it.
The idea is to create a method that can return all associated Threads in an array structure. But we
also want each Thread to have information such as: number of replies, date (of first post), author
of (first post).
• Edit: system/application/models/forum.php
view source
print?
01 <?php
02 class Forum extends Doctrine_Record {

03
04 // ...

05
06 public function getThreadsArray() {

07
08 $threads = Doctrine_Query::create()

09 ->select('t.title')
10 ->addSelect('p.id, (COUNT(p.id) - 1) as num_replies')

11 ->addSelect('MIN(p.id) as first_post_id')
12 ->from('Thread t, t.Posts p')

13 ->where('t.forum_id = ?', $this->id)


14 ->groupBy('t.id')
15 ->setHydrationMode(Doctrine::HYDRATE_ARRAY)
16 ->execute();

17

foreach ($threads as &$thread)


18
{

19
20 $post = Doctrine_Query::create()

21 ->select('p.created_at, u.username')
22 ->from('Post p, p.User u')

23 ->where('p.id = ?', $thread['Posts'][0]['first_post_id'])


24 ->setHydrationMode(Doctrine::HYDRATE_ARRAY)

25 ->fetchOne();
26

27 $thread['num_replies'] = $thread['Posts'][0]['num_replies'];
28 $thread['created_at'] = $post['created_at'];

29 $thread['username'] = $post['User']['username'];
30 $thread['user_id'] = $post['User']['id'];

31 unset($thread['Posts']);
32

33 }
34

35 return $threads;
36

37 }
In the first DQL query:
Look at the from() call first. We are joining 2 Models (Thread, Post) because we need
information from both.
Now look at the select() and addSelect() calls to see what we are getting:
- We select the title for every Thread.
- Then we select the number of Posts under each Thread by using COUNT(p.id), and subtract 1
to get number of replies.
- Finally we are also getting the ID for the first Post in that Thread using MIN(p.id). That will be
used later in the second DQL query.
where(‘t.forum_id = ?’, $this->id) indicates which Forum id we are looking up.
groupBy(‘t.id’) groups the results by the Thread id, so the Post count works properly.
Then, we loop through the Threads to get some more data into each Thread:
In the first query we fetched the ID of the first post on each Thread and stored it as first_post_id.
So this DQL query now gets data for that Post and also the username of its author by joining to
the related User Model.
The rest of the code, I just arranged all the values in $thread so it has a nice structure when
returned.
Just to give you a better picture, if you were to print_r $threads , it would look this this:
Array
(
[0] => Array
(
[id] => 1
[title] => Hi there!
[num_replies] => 1
[first_post_id] => 1
[created_at] => 2009-12-13 13:00:09
[username] => TestUser
[user_id] => 2
)

[1] => Array


(
[id] => 2
[title] => Greetings to all
[num_replies] => 0
[first_post_id] => 3
[created_at] => 2009-12-13 13:00:09
[username] => Foobar
[user_id] => 3
)

)
I did unset($thread['Posts']) in the code just to keep the resulting array cleaner.
Note that we used HYDRATE_ARRAY, like we talked about earlier. This allowed us to do 2
things that we couldn’t have done with Doctrine_Collection objects:
- When we foreach() the Threads, we are able to get each element by reference (&$thread), and
modify them.
- We are able to create new array elements and assign values to them, like this:
$thread['username'] = $post['User']['username'];.
Now, we need to create the View that will be for the content section of this page.
Forum View
In the Controller we passed this: $vars['content_view'] = ‘forum’;
So the Template will look for a View named ‘forum’.
• Create: system/application/views/forum.php
view source
print?
<h2><?php echo $title ?
01
></h2>
02

<?php foreach($threads as $thread): ?


03
>
04 <div class="thread">

05
06 <h3>

<?php echo anchor('threads/display/'.$thread['id'],


07
$thread['title']) ?>
08 (<?php echo $thread['num_replies']; ?> replies)

09 </h3>
10

11 <div>
12 Author: <em><?php echo anchor('profile/display/'.$thread['user_id'],

13 $thread['username']); ?></em>,
14 Posted: <em><?php echo $thread['created_at']; ?></em>

15 </div>
16

17 </div>
18 <?php endforeach; ?>
There is nothing new here. Just putting the data into HTML.
The Result
• Go to the Home Page and click on the first Forum.
You should see this:
Seems to be working!
Day 10 – Pagination
Upgrading Doctrine
Before we get started, let’s upgrade Doctrine.
When I wrote the first article in this series, Doctrine 1.1 was the current stable version at the
time. Since then, 1.2 has come out. Luckily it is backwards compatible with 1.1.
So, if you are like me and have been using Doctrine 1.1, just follow these steps to upgrade to 1.2.
• Delete folder: system/application/plugins/doctrine/lib
• Download Doctrine 1.2 and extract.
• Copy the lib folder to system/application/plugins/doctrine
One Small Fix
At some point I did find an issue that wasn’t backwards compatible. I modified the previous
articles to fix that issue but some of you may not have done this if you read those articles before I
changed them.
• Edit: system/application/controllers/home.php
view source
print?
01 <?php
02 class Home extends Controller {

03
04 public function index() {

05
06 $vars['categories'] = Doctrine_Query::create()

07 ->select('c.title, f.title, f.description')


08 ->addSelect('t.id, COUNT(t.id) as num_threads')

09 ->from('Category c, c.Forums f')


10 ->leftJoin('f.Threads t')

11 ->groupBy('f.id')
12 ->execute();

13
14 $vars['title'] = 'Home';

15 $vars['content_view'] = 'forum_list';
16 $vars['container_css'] = 'forums';

17
18 $this->load->view('template', $vars);

19 }
20

21 }
It’s just the highlighted line. The ‘t.id’ needed to be added to the addselect() or COUNT(t.id) did
not work properly.
That is all. If your website is loading without errors, everything should be fine.
What is Pagination?
Displaying data in multiple pages is Pagination.
There are two main parts to this. First we need to generate links to the pages.
Pagination Links
Here is an example from CodeIgniter Forums:

That looks simple, but we also need to consider what happens when we start going to other
pages:

Now there is a “Prev” link which we didn’t have before. And we see links to 5 page numbers
instead of 3.
Things change even more if we go deeper:

Now there is also a “<< First" link, because the link for Page 1 is no longer visible.
One more:

The “Last >>” link disappeared because we already have a link to Page 89, which is the last
page.
Another example from Digg.com:
As you can see they are doing it quite differently. It’s all a matter of preference.
Let’s look at the way they do the links.
CodeIgniter Forums:
http://codeigniter.com/forums/viewforum/59/

http://codeigniter.com/forums/viewforum/59/P25/

http://codeigniter.com/forums/viewforum/59/P50/
Those were the links for the first, second and third page. The first page gets no parameter as it
works with default values. But the second and third pages have these “P25″ and “P50″
parameters at the end. Strangely enough these are not page numbers. They actually represent the
offset value for the records instead, as they display 25 records per page.
Digg.com:
http://digg.com/all/upcoming

http://digg.com/all/upcoming/page2

http://digg.com/all/upcoming/page3
Again, we see a difference between the two websites. Digg.com prefers to use actual page
numbers instead of offset numbers.
Paginating Data
This is the second part to this Pagination subject. Our code needs to be responsible for displaying
the correct set of data on each page.
In terms of raw SQL, here is how you can get a set of records for a given page:
SELECT * FROM table LIMIT 50, 25
The LIMIT clause does the job. In this particular query, we fetch 25 records, starting after the
first 50 records. So, 50 is the offset and 25 is the limit, i.e. the number of records.
With Doctrine we will be using DQL rather than raw SQL queries.
Let’s say we have a URL like they do on CodeIgniter forums:
http://codeigniter.com/forums/viewforum/59/P125/
That would translate into having the offset 125. And the limit would always be 25, because
internally we decide to display 25 threads per page.
If we have a Digg style pagination URL:
http://digg.com/all/upcoming/page7
We need to multiply 7 with the “per page” count. So it could be 175 (7*25) offset and 25 limit, if
the per page limit is set to 25. But we are not going to be using this method.
CodeIgniter Pagination Library
CodeIgniter comes with a nice simple Pagination Library, which we will be using. First, I will
explain it briefly.
Let’s put some code in a test Controller.
• Edit: system/application/controllers/test.php
view source
print?
01 class Test extends Controller {
02

03 function paginate() {
04

05 $this->load->library('pagination');
06

07 $config['base_url'] = base_url() . "test/paginate";


08 $config['total_rows'] = '200';

09 $config['per_page'] = '10';
10

11 $this->pagination->initialize($config);
12

13 echo $this->pagination->create_links();
14

15 }
16

17 }
The code is very simple. We just provide the base url, number of total records, and the number of
records per page.
The results look like this:
And the links look like this:
http://localhost/ci_doctrine/test/paginate/

http://localhost/ci_doctrine/test/paginate/10

http://localhost/ci_doctrine/test/paginate/20

http://localhost/ci_doctrine/test/paginate/30

http://localhost/ci_doctrine/test/paginate/40
So this library puts the offset number, rather than the page number at the end of the URL’s.
Also, with this library you can customize the HTML structure of the links. You can see more
information about that in the documentation.
Adding More Data
First we are going to add some more data to our database so we have something to paginate.
All we have to do is add more stuff to the fixture file we have been using before:
• Edit: system/fixtures/data.yml
view source
print?
001 User:
002 Admin:

003 username: Administrator


004 password: testing

005 email: [email protected]


006 Test:

007 username: TestUser


008 password: mypass
009 email: [email protected]
010 Foo:

011 username: Foobar


012 password: mypass

013 email: [email protected]


014

015 Forum:
016 Forum_1:

017 title: Introduce Yourself!


018 description: >

019 Use this forum to introduce yourself to the CodeIgniter community,


020 or to announce your new CI powered site.

021 Forum_2:
022 title: The Lounge

023 description: >


CodeIgniter's social forum where you can discuss anything not
024
related

025 to development. No topics off limits... but be civil.


026 Forum_3:

027 title: CodeIgniter Discussion


028 description: This forum is for general topics related to CodeIgniter.

029 Forum_4:
030 title: Code and Application Development

031 description: >


032 Use the forum to discuss anything related to
033 programming and code development.
034 Forum_5:

035 title: Ignited Code


036 description: >

Use this forum to post plugins, libraries, or other code


037
contributions,
038 or to ask questions about any of them.

039
040 Category:

041 Lounge:
042 title: The CodeIgniter Lounge

043 Forums: [Forum_1, Forum_2]


044 Dev:

045 title: CodeIgniter Development Forums


046 Forums: [Forum_3, Forum_4, Forum_5]

047
048 Thread:

049 Thread_1:
050 title: Hi there!

051 Forum: Forum_1


052 Thread_2:

053 title: Greetings to all


054 Forum: Forum_1

055 Thread_3:
056 title: Sed vitae ligula erat
057 Forum: Forum_3
058 Thread_4:

059 title: Vivamus laoreet quam vitae mauris tempus


060 Forum: Forum_3

061 Thread_5:
062 title: Maecenas vel dolor odio

063 Forum: Forum_3


064 Thread_6:

065 title: In nec justo at orci fermentum


066 Forum: Forum_3

067 Thread_7:
068 title: Nullam volutpat laoreet orci

069 Forum: Forum_3


070 Thread_8:

071 title: Sed sodales augue vel elit


072 Forum: Forum_3

073 Thread_9:
074 title: Integer posuere luctus metus

075 Forum: Forum_3


076 Thread_10:

077 title: Maecenas ut mauris eget odio pharetra


078 Forum: Forum_3

079 Thread_11:
080 title: Pellentesque lacinia nibh vel lacus
081 Forum: Forum_3
082 Thread_12:

083 title: Nunc facilisis nibh a nulla laoreet in ultricies


084 Forum: Forum_3

085 Thread_13:
086 title: Maecenas pretium nisi eget nunc rutrum quis

087 Forum: Forum_3


088 Thread_14:

089 title: Etiam at ligula leo


090 Forum: Forum_3

091 Thread_15:
092 title: Vivamus tempus semper libero

093 Forum: Forum_3


094 Thread_16:

095 title: Phasellus venenatis consectetur quam


096 Forum: Forum_3

097 Thread_17:
098 title: Nam sagittis elementum turpis

099 Forum: Forum_3


100 Thread_18:

101 title: Sed at odio id ante rutrum sodales


102 Forum: Forum_3

103 Thread_19:
104 title: Praesent eget lorem nec odio
105 Forum: Forum_3
106 Thread_20:

107 title: Donec at enim sit amet quam


108 Forum: Forum_3

109 Thread_21:
110 title: Ut sit amet ante nec leo volutpat

111 Forum: Forum_3


112 Thread_22:

113 title: Pellentesque accumsan orci nec


114 Forum: Forum_3

115 Thread_23:
116 title: Curabitur convallis sapien in dolor feugiat

117 Forum: Forum_3


118 Thread_24:

119 title: Aenean sodales massa in dui ultrices


120 Forum: Forum_3

121
122 Post:

123 Post_1:
124 Thread: Thread_1

125 User: Test


126 created_at: '2009-11-20 01:20:30'

127 updated_at: '2009-11-20 01:20:30'


128 content: >
129 Hello everyone! My name is Test, and I go to school at
130 Test Institute of Technology in the US.

131 I just found CodeIgniter some time last week and have been
132 reading through the documentation trying to get myself acquainted.

133
Hopefully the forums will be a great help! I already have some
134
questions.

135 Thanks!
136 Post_2:

137 Thread: Thread_1


138 User: Admin

139 created_at: '2009-11-20 02:15:33'


140 updated_at: '2009-11-20 02:15:33'

141 content: Welcome Test! Nice to meet you.


142 Post_3:

143 Thread: Thread_2


144 User: Foo

145 created_at: '2009-11-19 12:14:50'


146 updated_at: '2009-11-19 12:14:50'

147 content: I am new here. Just wanted to say hi.


148 Post_4:

149 Thread: Thread_3


150 User: Foo

151 created_at: '2009-12-01 12:14:50'


152 updated_at: '2009-12-01 12:14:50'
content: Vivamus ultricies hendrerit justo, sit amet semper nulla
153
scelerisque pulvinar.
154 Post_5:

155 Thread: Thread_4


156 User: Foo

157 created_at: '2009-12-02 12:14:50'


158 updated_at: '2009-12-02 12:14:50'

159 content: Sed luctus enim ut magna pellentesque mollis.


160 Post_6:

161 Thread: Thread_5


162 User: Foo

163 created_at: '2009-12-03 12:14:50'


164 updated_at: '2009-12-03 12:14:50'

165 content: Nam id nisi dolor, vel interdum turpis.


166 Post_7:

167 Thread: Thread_6


168 User: Foo

169 created_at: '2009-12-04 12:14:50'


170 updated_at: '2009-12-04 12:14:50'

content: Donec volutpat accumsan lorem, at euismod metus lobortis


171
viverra.
172 Post_8:

173 Thread: Thread_7


174 User: Foo

175 created_at: '2009-12-05 12:14:50'


176 updated_at: '2009-12-05 12:14:50'

177 content: Nulla vestibulum erat ac nisi convallis rutrum.


178 Post_9:

179 Thread: Thread_8


180 User: Foo

181 created_at: '2009-12-06 12:14:50'


182 updated_at: '2009-12-06 12:14:50'

183 content: Nulla pharetra tortor id ante sollicitudin sodales.


184 Post_10:

185 Thread: Thread_9


186 User: Foo

187 created_at: '2009-12-07 12:14:50'


188 updated_at: '2009-12-07 12:14:50'

189 content: Cras id metus a elit mattis blandit et aliquet diam.


190 Post_11:

191 Thread: Thread_10


192 User: Foo

193 created_at: '2009-12-08 12:14:50'


194 updated_at: '2009-12-08 12:14:50'

195 content: Nunc non felis vitae dolor posuere aliquam non et augue.
196 Post_12:

197 Thread: Thread_11


198 User: Foo

199 created_at: '2009-12-09 12:14:50'


200 updated_at: '2009-12-09 12:14:50'

201 content: Nam et velit ac tellus interdum adipiscing.


202 Post_13:
203 Thread: Thread_12
204 User: Foo

205 created_at: '2009-12-10 12:14:50'


206 updated_at: '2009-12-10 12:14:50'

207 content: Donec viverra leo mauris, ac convallis turpis.


208 Post_14:

209 Thread: Thread_13


210 User: Foo

211 created_at: '2009-12-11 12:14:50'


212 updated_at: '2009-12-11 12:14:50'

content: Integer sagittis nisl ut nisi euismod in dignissim massa


213
sagittis.
214 Post_15:

215 Thread: Thread_14


216 User: Foo

217 created_at: '2009-12-12 12:14:50'


218 updated_at: '2009-12-12 12:14:50'

219 content: Integer vel lectus mollis quam sollicitudin porta.


220 Post_16:

221 Thread: Thread_15


222 User: Foo

223 created_at: '2009-12-13 12:14:50'


224 updated_at: '2009-12-13 12:14:50'

225 content: Etiam tempor luctus sem, at consequat enim posuere in.
226 Post_17:

227 Thread: Thread_16


228 User: Foo

229 created_at: '2009-12-14 12:14:50'


230 updated_at: '2009-12-14 12:14:50'

231 content: Proin placerat lectus dolor, quis viverra ante.


232 Post_18:

233 Thread: Thread_17


234 User: Foo

235 created_at: '2009-12-15 12:14:50'


236 updated_at: '2009-12-15 12:14:50'

content: Maecenas ullamcorper commodo leo, lobortis molestie turpis


237
cursus sit amet.
238 Post_19:

239 Thread: Thread_18


240 User: Foo

241 created_at: '2009-12-16 12:14:50'


242 updated_at: '2009-12-16 12:14:50'

content: Integer tincidunt facilisis dolor, vitae pellentesque turpis


243
rutrum sed.
244 Post_20:

245 Thread: Thread_19


246 User: Foo

247 created_at: '2009-12-17 12:14:50'


248 updated_at: '2009-12-17 12:14:50'

249 content: Donec eget lacus a nibh volutpat venenatis quis a felis.
250 Post_21:

251 Thread: Thread_20


252 User: Foo

253 created_at: '2009-12-18 12:14:50'


254 updated_at: '2009-12-18 12:14:50'

255 content: Nam fringilla tellus quis augue elementum eleifend.


256 Post_22:

257 Thread: Thread_21


258 User: Foo

259 created_at: '2009-12-19 12:14:50'


260 updated_at: '2009-12-19 12:14:50'

content: Nullam sollicitudin nulla at orci accumsan eget ultrices felis


261
vehicula.
262 Post_23:

263 Thread: Thread_22


264 User: Foo

265 created_at: '2009-12-20 12:14:50'


266 updated_at: '2009-12-20 12:14:50'

content: Nullam sit amet purus nec mauris convallis tincidunt sodales
267
eget nunc.
268 Post_24:

269 Thread: Thread_23


270 User: Foo

271 created_at: '2009-12-21 12:14:50'


272 updated_at: '2009-12-21 12:14:50'

273 content: Praesent in eros non elit ultricies tincidunt a nec tellus.
274 Post_25:

275 Thread: Thread_24


276 User: Foo

277 created_at: '2009-12-22 12:14:50'


278 updated_at: '2009-12-22 12:14:50'

279 content: Aenean ac nisl a sapien pulvinar gravida et quis metus.


Now load this fixture:
• Go to: http://localhost/ci_doctrine/doctrine_tools/load_fixtures
• Hit the button.
• Go home: http://localhost/ci_doctrine/
You should see this:

• Click the “CodeIgniter Discussion” link


You should see:
Sorting By Last Post Date
As it is, the Threads are not being sorted by a specific column, because we did not add that to the
DQL query last time. They need to be sorted by the “created_at” date of the last Post in that
Thread, in descending order. Let’s fix that now.
• Edit: system/application/models/forum.php
view source
print?
01 class Forum extends Doctrine_Record {
02

03 // ...
04

05 public function getThreadsArray() {


06

07 $threads = Doctrine_Query::create()
08 ->select('t.title')

09 ->addSelect('p.id, (COUNT(p.id) - 1) as num_replies')


10 ->addSelect('MIN(p.id) as first_post_id')

11 ->addSelect('MAX(p.created_at) as last_post_date')
12 ->from('Thread t, t.Posts p')

13 ->where('t.forum_id = ?', $this->id)


14 ->groupBy('t.id')

15 ->orderBy('last_post_date DESC')
16 ->setHydrationMode(Doctrine::HYDRATE_ARRAY)

17 ->execute();
18

foreach ($threads as &$thread)


19
{
20
21 $post = Doctrine_Query::create()
22 ->select('p.created_at, u.username')

23 ->from('Post p, p.User u')


24 ->where('p.id = ?', $thread['Posts'][0]['first_post_id'])

25 ->setHydrationMode(Doctrine::HYDRATE_ARRAY)
26 ->fetchOne();

27
28 $thread['num_replies'] = $thread['Posts'][0]['num_replies'];

29 $thread['created_at'] = $post['created_at'];
30 $thread['username'] = $post['User']['username'];

31 $thread['user_id'] = $post['User']['id'];
32 unset($thread['Posts']);

33
34 }

35
36 return $threads;

37
38 }

39
40 }
The highlighted lines have been added. They should be self-explanatory.
We should also update the View so it shows that date.
• Edit: system/application/views/forum.php
view source
print?
<h2><?php echo $title ?
01
></h2>
02
<?php foreach($threads as $thread): ?
03
>
04 <div class="thread">

05
06 <h3>

<?php echo anchor('threads/display/'.$thread['id'],


07
$thread['title']) ?>
08 (<?php echo $thread['num_replies']; ?> replies)

09 </h3>
10

11 <div>
12 Author: <em><?php echo anchor('profile/display/'.$thread['user_id'],

13 $thread['username']); ?></em>,
14 Last Post: <em><?php echo $thread['last_post_date']; ?></em>

15 </div>
16

17 </div>
18 <?php endforeach; ?>
Pagination Links
Now, to do pagination, we need 3 pieces of information:
• Total number of records. In our case, number of Threads.
• Number of records per page. We can pick any number. I will set it to 4 for
now so we can have several pages.
• Current page number or record offset. Since CodeIgniter Pagination
library works with the offset number, rather than page number, that’s what
we will use.
We need to edit the View, the Model and the Controller.
The View
Let’s set the output of the pagination links in the View.
• Edit: system/application/views/forum.php
view source
print?
01 <h2><?php echo $title ?
></h2>
02

<?php foreach($threads as $thread): ?


03
>
04 <div class="thread">

05
06 <h3>

<?php echo anchor('threads/display/'.$thread['id'],


07
$thread['title']) ?>
08 (<?php echo $thread['num_replies']; ?> replies)

09 </h3>
10

11 <div>
12 Author: <em><?php echo anchor('profile/display/'.$thread['user_id'],

13 $thread['username']); ?></em>,
14 Last Post: <em><?php echo $thread['last_post_date']; ?></em>

15 </div>
16

17 </div>
18 <?php endforeach; ?>

19
20 <?php if (isset($pagination)): ?>

21 <div class="pagination">
22 Pages: <?php echo $pagination; ?>

23 </div>
24 <?php endif; ?>
At line 20, we check to see if the $pagination variable is set, which will contain the pagination
links, because sometimes there won’t be enough records to paginate.
The Model
Now let’s modify the Forum Model.
• Edit: system/application/models/forum.php
view source
print?
01 <?php
02 class Forum extends Doctrine_Record {

03
04 public function setTableDefinition() {

05 $this->hasColumn('title', 'string', 255);


06 $this->hasColumn('description', 'string', 255);

07 $this->hasColumn('category_id', 'integer', 4);


08 }

09
10 public function setUp() {

11 $this->hasOne('Category', array(
12 'local' => 'category_id',

13 'foreign' => 'id'


14 ));

15 $this->hasMany('Thread as Threads', array(


16 'local' => 'id',

17 'foreign' => 'forum_id'


18 ));

19 }
20
21 public function numThreads() {
22

23 $result = Doctrine_Query::create()
24 ->select('COUNT(*) as num_threads')

25 ->from('Thread')
26 ->where('forum_id = ?', $this->id)

27 ->setHydrationMode(Doctrine::HYDRATE_ARRAY)
28 ->fetchOne();

29
30 return $result['num_threads'];

31
32 }

33
34 public function getThreadsArray($offset, $limit) {

35
36 $threads = Doctrine_Query::create()

37 ->select('t.title')
38 ->addSelect('p.id, (COUNT(p.id) - 1) as num_replies')

39 ->addSelect('MIN(p.id) as first_post_id')
40 ->addSelect('MAX(p.created_at) as last_post_date')

41 ->from('Thread t, t.Posts p')


42 ->where('t.forum_id = ?', $this->id)

43 ->groupBy('t.id')
44 ->orderBy('last_post_date DESC')
45 ->limit($limit)
46 ->offset($offset)

47 ->setHydrationMode(Doctrine::HYDRATE_ARRAY)
48 ->execute();

49

foreach ($threads as &$thread)


50
{

51
52 $post = Doctrine_Query::create()

53 ->select('p.created_at, u.username')
54 ->from('Post p, p.User u')

55 ->where('p.id = ?', $thread['Posts'][0]['first_post_id'])


56 ->setHydrationMode(Doctrine::HYDRATE_ARRAY)

57 ->fetchOne();
58

59 $thread['num_replies'] = $thread['Posts'][0]['num_replies'];
60 $thread['created_at'] = $post['created_at'];

61 $thread['username'] = $post['User']['username'];
62 $thread['user_id'] = $post['User']['id'];

63 unset($thread['Posts']);
64

65 }
66

67 return $threads;
68
69 }
70

71 }
Line 21: Added new method named “numThreads”, since we are going to need the total number
of Threads.
Note: I know that it would have been simpler to just use this code: “$this->Threads->count()”.
However, unfortunately Doctrine creates a very inefficient query when we do that, that’s why I
used a DQL query instead.
Line 34: Just added 2 parameters to the existing function, since we are only interested in the
Thread records for a given page.
Lines 45-46: Here we use the given $offset and $limit parameters to limit the DQL query results.
The Controller
Finally we update the Forums Controller.
• Edit: system/application/controllers/forums.php
view source
print?
01 <?php
02 class Forums extends Controller {

03

public function display($id, $offset = 0)


04
{

05
06 $per_page = 4;

07
08 $forum = Doctrine::getTable('Forum')->find($id);

09
10 $vars['title'] = $forum['title'];

11 $vars['threads'] = $forum->getThreadsArray(
12 $offset,

13 $per_page
14 );
15 $vars['content_view'] = 'forum';
16 $vars['container_css'] = 'forum';

17
18 $num_threads = $forum->numThreads();

19
20 // do we have enough to paginate

21 if ($num_threads > $per_page) {


22 // PAGINATION

23 $this->load->library('pagination');
24 $config['base_url'] = base_url() . "forums/display/$id";

25 $config['total_rows'] = $num_threads;
26 $config['per_page'] = $per_page;

27 $config['uri_segment'] = 4;
28 $this->pagination->initialize($config);

29
30 $vars['pagination'] = $this->pagination->create_links();

31 }
32

33 $this->load->view('template', $vars);
34

35 }
36

37 }
Updated lines are highlighted.
Line 4: Now there is a second parameter named $offset, because the paginated URL’s will
contain the offset after the Forum id.
Line 6: Here we pick a number of Threads per page. Normally it could be 10, 20 or 25, but for
this test we are picking a smaller number.
Lines 12-13: We send the 2 parameters that the getThreadsArray method is expecting.
Line 21: We only do pagination if there are more records than the $per_page number.
Line 27: We have to set the ‘uri_segment’ setting for the Pagination library. This is the uri
segment that will carry the offset number. If not set, it defaults to uri segment 3. For example the
URL will look like this:
http://localhost/ci_doctrine/forums/display/53/8
“forums” is uri segment 1, “display” is uri segment 2, “53″ is uri segment 3 and the Forum id,
“8″ is the URI segment 4 and the $offset variable.
The Result
• Go to: http://localhost/ci_doctrine/
• Click the forum name: “CodeIgniter Discussion”
You should see:

Continue clicking on the pagination links:

Seems to be working
Day 11 – Record Hooks
Record Hooks
In the Day 8 article we talked about CodeIgniter Hooks. This time we are going to look at
Record Hooks with Doctrine.
Hooks are used to execute certain code when a certain event is triggered. Record Hooks are used
when something happens with a Doctrine Record. For example we can use the postInsert hook
to do some task every time a new record is inserted.
Here is a list of all available Record Hooks:
• preSave
• postSave
• preUpdate
• postUpdate
• preInsert
• postInsert
• preDelete
• postDelete
• preValidate
• postValidate
To use a one of these hooks, simply add a method with that name into the Doctrine_Record
model:
view source
print?
01 class Post extends Doctrine_Record {
02

03 public function setTableDefinition() {


04 // ...

05 }
06

07 public function setUp() {


08 // ...

09 }
10

11 public function postInsert() {


12 // a new record was inserted
13 // you can put some code here
14 }

15
16 }
Let’s look at what we are going to be doing today to utilize this feature.
Forum Display Page Improvements
In the Day 9 article we built a page for displaying all Threads under a Forum.

Let’s look at the code we used for getting the list of threads:
view source
print?
01 // from the Forum model
02

03 // ...
04 public function getThreadsArray($offset, $limit) {

05
06 $threads = Doctrine_Query::create()

07 ->select('t.title')
08 ->addSelect('p.id, (COUNT(p.id) - 1) as num_replies')

09 ->addSelect('MIN(p.id) as first_post_id')
10 ->addSelect('MAX(p.created_at) as last_post_date')

11 ->from('Thread t, t.Posts p')


12 ->where('t.forum_id = ?', $this->id)

13 ->groupBy('t.id')
14 ->orderBy('last_post_date DESC')

15 ->limit($limit)
16 ->offset($offset)

17 ->setHydrationMode(Doctrine::HYDRATE_ARRAY)
18 ->execute();

19

foreach ($threads as &$thread)


20
{

21
22 $post = Doctrine_Query::create()

23 ->select('p.created_at, u.username')
24 ->from('Post p, p.User u')

25 ->where('p.id = ?', $thread['Posts'][0]['first_post_id'])


26 ->setHydrationMode(Doctrine::HYDRATE_ARRAY)

27 ->fetchOne();
28

29 $thread['num_replies'] = $thread['Posts'][0]['num_replies'];
30 $thread['created_at'] = $post['created_at'];

31 $thread['username'] = $post['User']['username'];
32 $thread['user_id'] = $post['User']['id'];

33 unset($thread['Posts']);
34

35 }
36

37 return $threads;
38

39 }
40 // ...
There are two main DQL queries. The first one for fetching the Threads, and the second one,
inside of a loop, for fetching further details about each Thread, such as number of replies, the
username etc…
There is quite a bit going on in there. With the changes we will make today, we will be able to
reduce that into a single DQL call.
Adding a “First_Post” Relationship
We first had to get the id of the first post, and then get information on that post in another DQL
query. If we had a direct relationship with a “First_Post” record, it would simplify things.
• Edit: system/application/models/thread.php
view source
print?
01 <?php
02 class Thread extends Doctrine_Record {

03
04 public function setTableDefinition() {

05 $this->hasColumn('title', 'string', 255);


06 $this->hasColumn('forum_id', 'integer', 4);

07 $this->hasColumn('first_post_id', 'integer', 4);


08 }

09
10 public function setUp() {

11 $this->hasOne('Forum', array(
12 'local' => 'forum_id',
13 'foreign' => 'id'
14 ));

15 $this->hasMany('Post as Posts', array(


16 'local' => 'id',

17 'foreign' => 'thread_id'


18 ));

19
20 $this->hasOne('Post as First_Post', array(

21 'local' => 'first_post_id',


22 'foreign' => 'id'

23 ));
24 }

25
26 }
The highlighted lines have been added. Now each Thread will have a relationship with a Post
record, which will be the first Post in that Thread.
Setting up the Hook
When a new Post is added, if it is the first Post in that Thread, it needs to be assigned to the
First_Post relationship. This will be done with the following “postInsert” Record Hook.
• Edit: system/application/models/post.php
view source
print?
01 <?php
02 class Post extends Doctrine_Record {

03
04 // ...

05 public function postInsert() {


06

07 // is this the first post?


08 if (!$this['Thread']['First_Post']->exists()) {

09 $this['Thread']['First_Post'] = $this;
10 $this['Thread']->save();

11 }
12

13 }
14

15 }
Note that “postInsert” means, “after” insert. It has nothing to do with the class name “Post”.
This function gets invoked right after a new Post record was created.
First we check if a First_Post relationship exists, with the exist() function. If not, we assign
“$this”, which is the current Post record, to this relationship with the corresponding Thread
record.
Rebuild the Database
• Drop all tables.
You may use this query:
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE category, forum, post, thread, user;
Now we need to rebuild it all.
• Go to: http://localhost/ci_doctrine/doctrine_tools/create_tables and click the
button.
• Go to: http://localhost/ci_doctrine/doctrine_tools/load_fixtures and click the
button.
Now all the tables are rebuilt and the fixture data is reloaded.
Take a look at your thread table:
You can see the new column. And thanks to the Record Hook, the values have been assigned
already.
Simplify Things
Now we can go ahead and simplify the “getThreadsArray” method under the Forum Model.
• Edit: system/application/models/forum.php
view source
print?
01 <?php
02 class Forum extends Doctrine_Record {

03
04 // ...

05
06 public function getThreadsArray($offset, $limit) {

07
08 $threads = Doctrine_Query::create()

09 ->select('t.title')
10 ->addSelect('p.id, (COUNT(p.id) - 1) as num_replies')

11 ->addSelect('MAX(p.created_at) as last_post_date')
12 ->addSelect('fp.created_at, u.username')

13 ->from('Thread t, t.Posts p, t.First_Post fp, fp.User u')


14 ->where('t.forum_id = ?', $this->id)

15 ->groupBy('t.id')
16 ->orderBy('last_post_date DESC')

17 ->limit($limit)
18 ->offset($offset)

19 ->setHydrationMode(Doctrine::HYDRATE_ARRAY)
20 ->execute();

21
foreach ($threads as &$thread)
22
{

23
24 $thread['num_replies'] = $thread['Posts'][0]['num_replies'];

25 $thread['created_at'] = $thread['First_Post']['created_at'];
$thread['username'] = $thread['First_Post']['User']
26
['username'];

27 $thread['user_id'] = $thread['First_Post']['User']['id'];
28

29 unset($thread['Posts']);
30

31 }
32

33 return $threads;
34

35 }
36

37 }
Line 13: Now we have 2 more relationships in the from() call. First_Post and the User for the
First_Post.
Line 12: We select the created_at and username fields from these new relationships.
Line 25,26,27: These lines have been changed to use the new fields.
Also the DQL query inside the loop is now gone. This should have a positive impact on
performance.
Note that the loop now is only being used to simplify the $threads array structure, so it’s
actually optional.
Testing the Results
We haven’t really changed anything on the actual pages. So everything should look the same as
before.

You might also like