codeigniter and doctrine from scratch

223
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.

Upload: satish-kumar

Post on 28-Mar-2015

385 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: CodeIgniter and Doctrine from scratch

CodeIgniter and Doctrine from scratchDay 1 – Install and SetupWhy 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.

Page 2: CodeIgniter and Doctrine from scratch
Page 3: CodeIgniter and Doctrine from scratch

First Step: Setup your development environmentIf 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_day1You should see this:

Page 4: CodeIgniter and Doctrine from scratch

CodeIgniter Crash Course: ControllersControllers 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.phpview sourceprint ?

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 }

Page 5: CodeIgniter and Doctrine from scratch

Go to: http://localhost/ci_doctrine_day1/index.php/hello/worldYou 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 DoctrineCodeIgniter 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.phpview sourceprint ?

01 <?php

02 // system/application/plugins/doctrine_pi.php

03

04 // load Doctrine library

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

Page 6: CodeIgniter and Doctrine from scratch

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

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

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(

Page 7: CodeIgniter and Doctrine from scratch

37     Doctrine::ATTR_AUTO_ACCESSOR_OVERRIDE, true);

38

39// this sets all table columns to notnull and unsigned (for ints) by 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,

47     array('name' => 'id', 'type' => 'integer', 'length' => 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

Page 8: CodeIgniter and Doctrine from scratch

Find the lines below and input the values.view sourceprint ? 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 ConfigurationAlmost done.config.php

Edit file: system/application/config/config.phpview sourceprint ? 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.phpview sourceprint ? 1 // in system/application/config/autoload.php

2 // ...

3

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

5

Page 9: CodeIgniter and Doctrine from scratch

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 ModelCreate 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`))

Page 10: CodeIgniter and Doctrine from scratch

Create the Model Create file: system/application/models/user.php

view sourceprint ?

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     }

Page 11: CodeIgniter and Doctrine from scratch

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.phpview sourceprint ?

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';

Page 12: CodeIgniter and Doctrine from scratch

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_testYou 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.

Page 13: CodeIgniter and Doctrine from scratch
Page 14: CodeIgniter and Doctrine from scratch

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 InstallFresh 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 sourceprint ?

1 // in system/application/config/config.php

2 $config['base_url'] = "http://localhost/ci_doctrine/";

CodeIgniter URL structureUrl’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 segmentsCodeIgniter MVC (Model – View – Controller)ModelsWe are going to be using Doctrine Model‘s, instead of CodeIgniter. I will explain it later in the tutorial.

Page 15: CodeIgniter and Doctrine from scratch

If you still want to learn about CodeIgniter Models, read: CodeIgniter ModelsViews

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 ViewsControllersWe already covered this in Day 1. Look for the section called “CodeIgniter Crash Course: Controllers”.Controller and View togetherHere is a sample View (system/application/views/my_view.php):view sourceprint ? 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 />21 <?php for ($i = 0; $i < 3; $i++): ?>

Page 16: CodeIgniter and Doctrine from scratch

22

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

24

25 <?php endfor; ?>

Here is a sample Controller (system/application/controllers/sample_controller.php), that loads a view:view sourceprint ? 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 calledblah blahYou can even do loops:Counter shows: 0Counter shows: 1Counter shows: 2Or in alternate syntax:Counter shows: 0Counter shows: 1Counter 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.

Page 17: CodeIgniter and Doctrine from scratch

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 ModelsModels 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 sourceprint ?

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 input11         ));

12

13         // supports many column types, including enum

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

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

Page 18: CodeIgniter and Doctrine from scratch

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

36        // causes 'created_at' and 'updated_at' fields to be updated 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) {

Page 19: CodeIgniter and Doctrine from scratch

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.

Page 20: CodeIgniter and Doctrine from scratch

Day 3 – User Signup FormLet’s build a Doctrine ModelSince 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.phpview sourceprint ?

01 <?php

02 class User extends Doctrine_Record {

03

04     public function setTableDefinition() {

05        $this->hasColumn('username', 'string', 255, array('unique' => '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 sourceprint ? 1 $this->hasColumn($name, $type = null, $length = null, $options = array());

This function call tells Doctrine about the table column structure. Everything except the $name field is optional.

Page 21: CodeIgniter and Doctrine from scratch

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 sourceprint ? 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 MutatorsLet’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 sourceprint ?

01 <?php

02 class User extends Doctrine_Record {

03

04     public function setTableDefinition() {

05        $this->hasColumn('username', 'string', 255, array('unique' => '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');

Page 22: CodeIgniter and Doctrine from scratch

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 tablesWhy 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 sourceprint ? 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 sourceprint ?

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

07        <input type="submit" name="action" value="Create Tables"><br /><br />';

08

09         if ($this->input->post('action')) {

10             Doctrine::createTablesFromModels();

11             echo "Done!";

12         }

Page 23: CodeIgniter and Doctrine from scratch

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 sourceprint ? 1 // set the default primary key to be named 'id', integer, 4 bytes

Page 24: CodeIgniter and Doctrine from scratch

2 Doctrine_Manager::getInstance()->setAttribute(

3     Doctrine::ATTR_DEFAULT_IDENTIFIER_OPTIONS,

4     array('name' => 'id', 'type' => 'integer', 'length' => 4));

Now let’s do some more CodeIgniter tweaking.Removing “index.php” from CodeIgniter urlsWe 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 sourceprint ?

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 */

Page 25: CodeIgniter and Doctrine from scratch

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 FormBefore we start coding, let’s decide how we’re going to do thisThe Game planWe want a Controller named signup, so the url for the signup form becomes http://localhost/ci_doctrine/signupWe 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.phpview sourceprint ? 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

Page 26: CodeIgniter and Doctrine from scratch

view sourceprint ?

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 SignupCodeIgniter HelpersHelpers 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 sourceprint ? 1 $this->load->helper('url');

Or load multiple helpers at once:view sourceprint ? 1 $this->load->helper(array('form','url'));

CodeIgniter LibrariesLibraries 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

Page 27: CodeIgniter and Doctrine from scratch

print ? 1 $this->load->library('form_validation');

Once it’s loaded, we can access it like this:view sourceprint ? 1 $this->form_validation->//...

Add them to the controller Edit: system/application/controllers/signup.php

view sourceprint ? 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 HelperNow that we have it loaded, let’s use our Form Helper functions.

Edit: system/application/views/signup_form.phpview sourceprint ?

01 <!DOCTYPE html>

Page 28: CodeIgniter and Doctrine from scratch

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'); ?>

Page 29: CodeIgniter and Doctrine from scratch

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 sourceprint ?

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

Page 30: CodeIgniter and Doctrine from scratch

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>17         <input type="text" name="username" value="" />   </p>

18     <p>

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

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

22

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

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

25     <p>

26         <label for="email">E-mail: </label>27         <input type="text" name="email" value="" />  </p>

28     <p>

29         <input type="submit" name="submit" value="Create my account" />  </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 sourceprint ?

01 body {

02     font-family: "Trebuchet MS",Arial;

Page 31: CodeIgniter and Doctrine from scratch

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;

Page 32: CodeIgniter and Doctrine from scratch

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 }

Page 33: CodeIgniter and Doctrine from scratch

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 sourceprint ?

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:

Page 34: CodeIgniter and Doctrine from scratch

Form ValidationIf 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 sourceprint ? 01 <?php

02

03 class Signup extends Controller {

Page 35: CodeIgniter and Doctrine from scratch

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',

Page 36: CodeIgniter and Doctrine from scratch

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 earlierFirst we set the rules like this:view sourceprint ?

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 ErrorsNext thing we need to do is display the errors to the user.

Edit: system/application/views/signup_form.phpview sourceprint ?

Page 37: CodeIgniter and Doctrine from scratch

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     <link rel="stylesheet" href="<?php echo base_url(); ?>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>

Page 38: CodeIgniter and Doctrine from scratch

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 ViewWe will keep this one simple for now.

Create: system/application/views/submit_success.phpview sourceprint ? 1 Success!

Test The Form Enter some invalid data into the form and submit.

http://localhost/ci_doctrine/signup

Page 39: CodeIgniter and Doctrine from scratch

And if you enter everything correctly, you should see the Success! message.Saving the UserNow 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 sourceprint ? 01 <?php

02

03 class Signup extends Controller {

Page 40: CodeIgniter and Doctrine from scratch

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:

Page 41: CodeIgniter and Doctrine from scratch

It's working great. But we're not quite done yet.One Little ProblemTry submitting the form again with the same username and e-mail, and you will see:view sourceprint ?

1 <br />

2

<b>Fatal error</b>:  Uncaught exception 'Doctrine_Connection_Mysql_Exception' with message 'SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'testing' for key 'username'' in C:\wamp\www\ci_doctrine\system\application\plugins\doctrine\lib\Doctrine\Connection.php:1084

3 Stack trace:

4

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

5#1 C:\wamp\www\ci_doctrine\system\application\plugins\doctrine\lib\Doctrine\Connection.php(1049): Doctrine_Connection_Statement-&gt;execute(Array)

6 #2 C:\wamp\www\ci_doctrine\system\application\plugins\doctrine\lib\Doctrine\

Page 42: CodeIgniter and Doctrine from scratch

Connection.php(693): Doctrine_Connection-&gt;exec('INSERT INTO use...', Array)

7#3 C:\wamp\www\ci_doctrine\system\application\plugins\doctrine\lib\Doctrine\Connection\UnitOfWork.php(595): Doctrine_Connection-&gt;insert(Object(Doctrine_Table), Array)

8

#4 C:\wamp\www\ci_doctrine\system\application\plugins\doctrine\lib\Doctrine in <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 DuplicatesWe could simply check for the existence of a duplicate with something like this:view sourceprint ?

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 LibrariesSince 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.phpview sourceprint ? 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)

Page 43: CodeIgniter and Doctrine from scratch

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.phpview sourceprint ? 01 <?php

Page 44: CodeIgniter and Doctrine from scratch

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',

11            'required|alpha_numeric|min_length[6]|max_length[12]|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:

Page 45: CodeIgniter and Doctrine from scratch
Page 46: CodeIgniter and Doctrine from scratch

Day 4 – User Login

Login Form ViewFirst 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.phpview sourceprint ?

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>

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 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'); ?>

Page 47: CodeIgniter and Doctrine from scratch

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 sourceprint ? 1 <a href="http://localhost/ci_doctrine/signup">Create an Account</a>Style the linksI am just going to make a small addition to the stylesheet so our links look better.

Edit: css/style.cssview sourceprint ?

01 body {

02     font-family: "Trebuchet MS",Arial;

03     font-size: 14px;

04     background-color: #212426;

05     color: #11151E;

06 }

Page 48: CodeIgniter and Doctrine from scratch

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 FormLikewise, we will add a link from the Signup Form to the new Login Form.

Edit: system/application/views/signup_form.phpview sourceprint ?

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     <link rel="stylesheet" href="<?php echo base_url(); ?>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>'); ?>

Page 49: CodeIgniter and Doctrine from scratch

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.

Page 50: CodeIgniter and Doctrine from scratch

Login ControllerFirst we are going create a sort of skeleton structure for our new Controller.

Create: system/application/controllers/login.phpview sourceprint ?

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

Page 51: CodeIgniter and Doctrine from scratch

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.

Page 52: CodeIgniter and Doctrine from scratch

Works great!Default ControllerWhen 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:

Page 53: CodeIgniter and Doctrine from scratch

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.phpview sourceprint ?

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 sourceprint ?

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!

Page 54: CodeIgniter and Doctrine from scratch

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.phpview sourceprint ?

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     }

Page 55: CodeIgniter and Doctrine from scratch

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

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

35

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

37

38     }

39

40     public function authenticate() {

41

42         // get User object by username

43        if ($u = Doctrine::getTable('User')->findOneByUsername($this->input->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);

Page 56: CodeIgniter and Doctrine from scratch

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

Page 57: CodeIgniter and Doctrine from scratch

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 UserFirst let’s build the skeleton:

Create: system/application/models/current_user.php

Page 58: CodeIgniter and Doctrine from scratch

view sourceprint ?

01 <?php

02 class Current_User {

03

04     private static $user;

05

06     private function __construct() {}

07

08     public static function user() {

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.

Page 59: CodeIgniter and Doctrine from scratch

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.phpview sourceprint ?

01 <?php

02 class Current_User {

03

04     private static $user;

05

06     private function __construct() {}

07

08     public static function user() {

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     }

Page 60: CodeIgniter and Doctrine from scratch

28

29     public static function login($username, $password) {

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() {

Page 61: CodeIgniter and Doctrine from scratch

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 ControllerWe are about the see the first benefit of creating all that code above.

Edit: system/application/controllers/login.phpOnly editing the authenticate() function:view sourceprint ?

01 <?php

02 class Login extends Controller {

03

04     // ...

Page 62: CodeIgniter and Doctrine from scratch

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.LogoutNow we are going to let users logout by clicking a link. But first:Autoloading LibrariesIt 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.phpFind this line:view sourceprint ?

1 // ...

2 $autoload['libraries'] = array('session');

3 // ...

Let’s also autoload the URL Helper. Edit: system/application/config/autoload.php

Find this line:view sourceprint ?

1 // ...

2 $autoload['helper'] = array('url');

3 // ...

Now that’s out of the way.

Page 63: CodeIgniter and Doctrine from scratch

Logout Controller Create: system/application/controllers/logout.php

view sourceprint ?

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.phpview sourceprint ?

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>

Page 64: CodeIgniter and Doctrine from scratch

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 ChangesI have made some css changes, so just copy paste the following:

Edit: css/style.cssview sourceprint ?

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;

Page 65: CodeIgniter and Doctrine from scratch

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

Page 66: CodeIgniter and Doctrine from scratch

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 {

Page 67: CodeIgniter and Doctrine from scratch

80     font-size: 13px;

81     color: #690C07;

82     font-style: italic;

83 }

Home ViewNow we are going to see how easy it is to fetch user information for the logged in user.

Create: system/application/views/home.phpview sourceprint ?

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>

12

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

14        <h2>Hello <em><?php echo Current_User::user()->username; ?></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

Page 68: CodeIgniter and Doctrine from scratch

23 </body>

24 </html>

Home Controller Edit: system/application/controllers/home.php

view sourceprint ?

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:

Page 69: CodeIgniter and Doctrine from scratch

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:

Page 70: CodeIgniter and Doctrine from scratch

In this case my username is burak, and it was displayed back to me.Voila!

Page 71: CodeIgniter and Doctrine from scratch

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. CREATEThe act of inserting new data/records into the database:Record ObjectThis is the way we have been creating our user records in the previous tutorials. It’s pretty simple:view sourceprint ? 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 sourceprint ?

01 $data = array(

02     'username' => 'myuser',

03     'password' => 'mypass',

04     'email' => '[email protected]'05 );

06

07 $u = new User();

08

09 $u->fromArray($data);

Page 72: CodeIgniter and Doctrine from scratch

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 sourceprint ?

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 SQLWe 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 sourceprint ? 1 $data = array('myuser','mypass');

2

3 $conn = Doctrine_Manager::connection();

4

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

2. READThe 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.

Page 73: CodeIgniter and Doctrine from scratch

view sourceprint ? 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 sourceprint ? 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 sourceprint ? 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).DQLThis 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 sourceprint ?

Page 74: CodeIgniter and Doctrine from scratch

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 sourceprint ? 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. UPDATEThe act of updating existing records.Record ObjectYou can make direct changes to any Doctrine_Record object. Once you call save(), it will perform an UPDATE.view sourceprint ? 1 $user_id = 1;

Page 75: CodeIgniter and Doctrine from scratch

2

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

4

5 $u->password = 'newpassword';

6

7 $u->save();

DQLWhen you want to update multiple rows at once, the preferred way is to use DQL.view sourceprint ? 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. DELETEThe act of deleting records from the database.Record ObjectYou can simply call the delete() method to delete a Doctrine_Record object directly.view sourceprint ? 1 $user_id = 1;

2

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

4

5 $u->delete();

DQLWhen deleting multiple records, use DQL.view sourceprint ? 1 $q = Doctrine_Query::create()

Page 76: CodeIgniter and Doctrine from scratch

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.

Page 77: CodeIgniter and Doctrine from scratch

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 KeysTo setup relationships between models (and tables in the database), we need to have certain fields in our tables. These are used as Foreign Keys.

Page 78: CodeIgniter and Doctrine from scratch

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 sourceprint ? 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 RelationshipsJust 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 sourceprint ? 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

Page 79: CodeIgniter and Doctrine from scratch

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 sourceprint ? 01 class User extends Doctrine_Record {

02

03     public function setTableDefinition() {

04        $this->hasColumn('username', 'string', 255, array('unique' => '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 RelationshipsThis 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().

Page 80: CodeIgniter and Doctrine from scratch

for more infoThis 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 ModelsLet’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.phpview sourceprint ?

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.phpview sourceprint ?

01 <?php

02 class Forum extends Doctrine_Record {

03

04     public function setTableDefinition() {

Page 81: CodeIgniter and Doctrine from scratch

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.phpview sourceprint ?

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

Page 82: CodeIgniter and Doctrine from scratch

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 sourceprint ?

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',

Page 83: CodeIgniter and Doctrine from scratch

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 ModelWe already have this Model, we are just making changes.

Edit: system/application/models/user.phpview sourceprint ?

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 TablesRemember 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.

Page 84: CodeIgniter and Doctrine from scratch

Looks great! Now let’s see how we can add some data using these Models.Creating RecordsWe are going to write a quick test controller to demonstrate how we can add data.

Create: system/application/controllers/test.phpview sourceprint ?

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!";

12        $forum->description = "Use this forum to introduce yourself to the CodeIgniter community, or to announce your new CI powered site.";

13

Page 85: CodeIgniter and Doctrine from scratch

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.phpview sourceprint ?

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!";

11        $forum->description = "Use this forum to introduce yourself to the 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";

17        $forum2->description = "CodeIgniter's social forum where you can discuss anything not related to development. No topics off limits... but be civil.";

18

19         // you can also add the other way around

Page 86: CodeIgniter and Doctrine from scratch

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";

30        $forum3['description'] = "This forum is for general topics related 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.

Page 87: CodeIgniter and Doctrine from scratch

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.

Page 88: CodeIgniter and Doctrine from scratch

Day 7 – Fixtures & Forum ListFirst, Some StylingBefore 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.cssview sourceprint ?

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 }

Page 89: CodeIgniter and Doctrine from scratch

028

029 .forums h2 {

030     font-size: 16px;

031     color: #000;

032     padding: 5px 10px 5px 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 }

Page 90: CodeIgniter and Doctrine from scratch

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;

Page 91: CodeIgniter and Doctrine from scratch

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 PageWe 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.phpview source

Page 92: CodeIgniter and Doctrine from scratch

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.phpFirst we make a blank page, with a container div and a heading:view sourceprint ?

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     <h1>CI+Doctrine Message Board</h1>

Page 93: CodeIgniter and Doctrine from scratch

14

15 </div>

16

17 </body>

18 </html>

Next, we loop thru the $categories object, and display titles for each Category:view sourceprint ?

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     <h1>CI+Doctrine Message Board</h1>

14

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

16

17         <div class="category">

18

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

20

21         </div>

22

23     <?php endforeach; ?>

24

Page 94: CodeIgniter and Doctrine from scratch

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 sourceprint ?

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     <h1>CI+Doctrine Message Board</h1>

14

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

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

Page 95: CodeIgniter and Doctrine from scratch

25                     <h3>

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

27                         (<?php echo $forum->Threads->count(); ?> 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:

Page 96: CodeIgniter and Doctrine from scratch

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 FixturesWhat 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 FixturesFirst, 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 sourceprint ?

1 User:

Page 97: CodeIgniter and Doctrine from scratch

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 sourceprint ?

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

09        <input type="submit" name="action" value="Load Fixtures"><br /><br />';

10

11         if ($this->input->post('action')) {

12

13             Doctrine_Manager::connection()->execute(

14                 'SET FOREIGN_KEY_CHECKS = 0');

Page 98: CodeIgniter and Doctrine from scratch

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.ymlview sourceprint ?

01 User:

02   Admin:

03     username: Administrator

04     password: testing

05     email: [email protected]

Page 99: CodeIgniter and Doctrine from scratch

06   Test:

07     username: TestUser

08     password: mypass

09     email: [email protected]

10   Foo:

11     username: Foobar12     password: mypass13     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: >

Page 100: CodeIgniter and Doctrine from scratch

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

38       or to ask questions about any of them.

39

40 Category:

41   Lounge:

42     title: The CodeIgniter Lounge43     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

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

Page 101: CodeIgniter and Doctrine from scratch

questions.

68       Thanks!

69   Post_2:

70     Thread: Thread_1

71     User: Admin

72     created_at: 2009-11-20 02:15:3373     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/

Page 102: CodeIgniter and Doctrine from scratch

We can see all the Categories and Forums we just created. Also there are 2 Threads now, as we can see from the count.

Page 103: CodeIgniter and Doctrine from scratch

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 ControlsIn 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.phpview sourceprint ?

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>

Page 104: CodeIgniter and Doctrine from scratch

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>

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

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

38                     </h3>

Page 105: CodeIgniter and Doctrine from scratch

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 sourceprint ? 1 /* USER CONTROL BOX -------------------------------*/

2 .user_controls {

3     float: right;

4     text-align: right;

5 }

Testing Go to: http://localhost/ci_doctrine/

Page 106: CodeIgniter and Doctrine from scratch

The links are showing up at the top right corner. Click Login. Login as Testuser:mypass .

Page 107: CodeIgniter and Doctrine from scratch

Now we can see the username and the Logout link.We will add more features to this later in other tutorials.Profiling with DoctrineUsing 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 PageLet’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.phpview sourceprint ?

01 <?php

02 class Test extends Controller {

03

04 //...

Page 108: CodeIgniter and Doctrine from scratch

05

06     function home_profiler() {

07

08         // set up the profiler

09         $profiler = new Doctrine_Connection_Profiler();

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

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();

22         foreach ($profiler as $event) {

23             $time += $event->getElapsedSecs();

24            if ($event->getName() == 'query' || $event->getName() == '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;

Page 109: CodeIgniter and Doctrine from scratch

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 (

Page 110: CodeIgniter and Doctrine from scratch

[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 )

Page 111: CodeIgniter and Doctrine from scratch

)

[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.013517618179321Peak 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 HooksWith Hooks, we can tell CodeIgniter to perform an operation, at a given point in the application flow.

Page 112: CodeIgniter and Doctrine from scratch

For example, we are going to initialize the Profiler, before any Controller function is called.Enabling HooksHooks are disabled by default, so we must first enable them.

Edit: system/application/config/config.phpview sourceprint ? 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 HookLet’s create the functions that will be called for this hook.

Create: system/application/hooks/doctrine_profiler_hooks.phpview sourceprint ?

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();09         foreach (Doctrine_Manager::getInstance()->getConnections() as $conn)

Page 113: CodeIgniter and Doctrine from scratch

{

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();

20         foreach (self::$profiler as $event) {

21             $time += $event->getElapsedSecs();

22            if ($event->getName() == 'query' || $event->getName() == '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

35        $output = "<"."?php  if ( ! defined('BASEPATH')) exit('No direct 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() . "";

Page 114: CodeIgniter and Doctrine from scratch

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 HooksNow we need to add the hooks to the config so they get executed.

Edit: system/application/config/hooks.phpview sourceprint ? 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(

21     'class' => 'Doctrine_Profiler_Hooks',

Page 115: CodeIgniter and Doctrine from scratch

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.phpYou should see something like this:view sourceprint ?

001<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed'); ?>

002

003 Array

004 (

005     [0] => Array

006         (

007             [type] => query

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

009             [time] => 0.000220

010         )

011

012     [1] => Array

Page 116: CodeIgniter and Doctrine from scratch

013         (

014             [type] => execute

015

            [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

016             [time] => 0.000310

017             [params] => Array

018                 (

019                     [0] => 2

020                 )

021

022         )

023

024     [2] => Array

025         (

026             [type] => execute

027            [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 (?)

028             [time] => 0.000322

029             [params] => Array

030                 (

031                     [0] => 1

032                 )

033

034         )

035

036     [3] => Array

037         (

038             [type] => execute

039            [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 (?)

Page 117: CodeIgniter and Doctrine from scratch

040             [time] => 0.000208

041             [params] => Array

042                 (

043                     [0] => 1

044                 )

045

046         )

047

048     [4] => Array

049         (

050             [type] => execute

051            [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 (?)

052             [time] => 0.000169

053             [params] => Array

054                 (

055                     [0] => 2

056                 )

057

058         )

059

060     [5] => Array

061         (

062             [type] => execute

063            [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 (?)

064             [time] => 0.000322

065             [params] => Array

066                 (

067                     [0] => 2

068                 )

Page 118: CodeIgniter and Doctrine from scratch

069

070         )

071

072     [6] => Array

073         (

074             [type] => execute

075            [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 (?)

076             [time] => 0.000238

077             [params] => Array

078                 (

079                     [0] => 3

080                 )

081

082         )

083

084     [7] => Array

085         (

086             [type] => execute

087            [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 (?)

088             [time] => 0.000171

089             [params] => Array

090                 (

091                     [0] => 4

092                 )

093

094         )

095

096     [8] => Array

097         (

098             [type] => execute

Page 119: CodeIgniter and Doctrine from scratch

099            [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 (?)

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 DQLIn 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.phpview sourceprint ?

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();

Page 120: CodeIgniter and Doctrine from scratch

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 sourceprint ? 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.

Page 121: CodeIgniter and Doctrine from scratch

First go to: http://localhost/ci_doctrine/ Then open: system/logs/doctrine_profiler.php

You should see something like this:view sourceprint ?

01<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed'); ?>

02

03 Array

04 (

05     [0] => Array

06         (

07             [type] => query

08            [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 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

15

            [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

16             [time] => 0.000307

17             [params] => Array

18                 (

19                     [0] => 2

20                 )

21

22         )

23

24     [2] => Array

Page 122: CodeIgniter and Doctrine from scratch

25         (

26             [type] => execute

27            [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 (?)

28             [time] => 0.000274

29             [params] => Array

30                 (

31                     [0] => 1

32                 )

33

34         )

35

36     [3] => Array

37         (

38             [type] => execute

39            [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 (?)

40             [time] => 0.000172

41             [params] => Array

42                 (

43                     [0] => 2

44                 )

45

46         )

47

48     [4] => Array

49         (

50             [type] => execute

51            [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 (?)

52             [time] => 0.000214

53             [params] => Array

Page 123: CodeIgniter and Doctrine from scratch

54                 (

55                     [0] => 3

56                 )

57

58         )

59

60     [5] => Array

61         (

62             [type] => execute

63            [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 (?)

64             [time] => 0.000154

65             [params] => Array

66                 (

67                     [0] => 4

68                 )

69

70         )

71

72     [6] => Array

73         (

74             [type] => execute

75            [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 (?)

76             [time] => 0.000311

77             [params] => Array

78                 (

79                     [0] => 5

80                 )

81

82         )

Page 124: CodeIgniter and Doctrine from scratch

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 CountCurrently, most of these queries are caused by the count() call in our Home View.view sourceprint ? 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.phpview sourceprint ?

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.

Page 125: CodeIgniter and Doctrine from scratch

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.idOne More ChangeNow 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 sourceprint ?

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.

Page 126: CodeIgniter and Doctrine from scratch

The result is: $forum->Threads[0]->num_threadsIf 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 sourceprint ?

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 )

)

)

Page 127: CodeIgniter and Doctrine from scratch

[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 )

)

Page 128: CodeIgniter and Doctrine from scratch

)

[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 )

)

Page 129: CodeIgniter and Doctrine from scratch

Profile AgainLet’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 sourceprint ?

01<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed'); ?>

02

03 Array

04 (

05     [0] => Array

06         (

07             [type] => query

08

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

15

            [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

16             [time] => 0.000354

17             [params] => Array

18                 (

19                     [0] => 2

20                 )

21

22         )

Page 130: CodeIgniter and Doctrine from scratch

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.ConclusionsI 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.

Page 131: CodeIgniter and Doctrine from scratch

Day 9 – Templates & Data Hydrators

TemplatesWhat 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 TemplateLet’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.phpview sourceprint ?

Page 132: CodeIgniter and Doctrine from scratch

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>

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="<?php echo $container_css; ?> 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.

Page 133: CodeIgniter and Doctrine from scratch

Now let’s create the user_controls View. Create: system/application/views/user_controls.php

view sourceprint ?

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.phpview sourceprint ? 01 <?php foreach($categories as $category): ?>

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>

10             <?php echo anchor('forums/display/'.$forum->id, $forum->title) ?>11             (<?php echo $forum->Threads[0]->num_threads; ?> threads)

12         </h3>

13

14         <div class="description">15             <?php echo $forum->description; ?>

16         </div>

17

18     </div>

Page 134: CodeIgniter and Doctrine from scratch

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.phpUpdating the Home ControllerThis 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.phpview sourceprint ?

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

Page 135: CodeIgniter and Doctrine from scratch

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_SCALARThis returns an array similar to what you would get from doing raw SQL queries.view sourceprint ? 01 $category = Doctrine_Query::create()

Page 136: CodeIgniter and Doctrine from scratch

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!

18            [f_description] => Use this forum to introduce yourself to the 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

Page 137: CodeIgniter and Doctrine from scratch

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_ARRAYThis also returns an Array. But this time, it will have a nice multi-tier structure based on the Model relationships.view sourceprint ? 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)

Page 138: CodeIgniter and Doctrine from scratch

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!

22                            [description] => Use this forum to introduce 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

31                            [description] => CodeIgniter's social forum where you can discuss anything not related to development. No topics off limits... but be civil.

32

Page 139: CodeIgniter and Doctrine from scratch

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 HydratorsBy 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-hydratorsWhy?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 ChangesBefore we move on, I want to make some CSS changes, so the new pages we build will be styled.

Edit: css/style.cssview sourceprint ?

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;}

Page 140: CodeIgniter and Doctrine from scratch

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;

17     padding: 5px 10px 5px 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 }

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

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

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

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

29 /* FORUM -----------------------------------------*/

30 .forum.container h2 {

31     font-size: 16px;

32     color: #000;

33     padding: 5px 10px 5px 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;

Page 141: CodeIgniter and Doctrine from scratch

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;

Page 142: CodeIgniter and Doctrine from scratch

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 PagesNow, 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 ControllerThe skeleton structure first:

Create: system/application/controllers/forums.phpview sourceprint ?

1 <?php

2 class Forums extends Controller {

3

4     public function display($id) {

5

6     }

7

Page 143: CodeIgniter and Doctrine from scratch

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 sourceprint ?

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 }

Page 144: CodeIgniter and Doctrine from scratch

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.phpview sourceprint ?

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

18         foreach ($threads as &$thread) {

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)

Page 145: CodeIgniter and Doctrine from scratch

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

Page 146: CodeIgniter and Doctrine from scratch

)

[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 ViewIn the Controller we passed this: $vars['content_view'] = ‘forum’;So the Template will look for a View named ‘forum’.

Create: system/application/views/forum.phpview sourceprint ? 01 <h2><?php echo $title ?></h2>

02

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

04 <div class="thread">

05

06     <h3>

07        <?php echo anchor('threads/display/'.$thread['id'], $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>,

Page 147: CodeIgniter and Doctrine from scratch

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!

Page 148: CodeIgniter and Doctrine from scratch

Day 10 – Pagination

Upgrading DoctrineBefore 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 FixAt 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.phpview sourceprint ?

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     }

Page 149: CodeIgniter and Doctrine from scratch

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 LinksHere 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/

Page 150: CodeIgniter and Doctrine from scratch

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 DataThis 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 LibraryCodeIgniter 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.phpview sourceprint ? 01 class Test extends Controller {

02

03     function paginate() {

04

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

Page 151: CodeIgniter and Doctrine from scratch

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.

Page 152: CodeIgniter and Doctrine from scratch

Adding More DataFirst 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.ymlview sourceprint ?

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: Foobar012     password: mypass013     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: >

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

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

026   Forum_3:

Page 153: CodeIgniter and Doctrine from scratch

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

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

038       or to ask questions about any of them.

039

040 Category:

041   Lounge:

042     title: The CodeIgniter Lounge043     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

Page 154: CodeIgniter and Doctrine from scratch

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 odio063     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 orci069     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 metus075     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 lacus081     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 quis087     Forum: Forum_3

Page 155: CodeIgniter and Doctrine from scratch

088   Thread_14:

089     title: Etiam at ligula leo

090     Forum: Forum_3

091   Thread_15:

092     title: Vivamus tempus semper libero093     Forum: Forum_3

094   Thread_16:

095     title: Phasellus venenatis consectetur quam

096     Forum: Forum_3

097   Thread_17:

098     title: Nam sagittis elementum turpis099     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 odio105     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 volutpat111     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 feugiat117     Forum: Forum_3

Page 156: CodeIgniter and Doctrine from scratch

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

134      Hopefully the forums will be a great help! I already have some 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:

Page 157: CodeIgniter and Doctrine from scratch

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'

153    content: Vivamus ultricies hendrerit justo, sit amet semper nulla 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'

171    content: Donec volutpat accumsan lorem, at euismod metus lobortis 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'

Page 158: CodeIgniter and Doctrine from scratch

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'

213    content: Integer sagittis nisl ut nisi euismod in dignissim massa sagittis.

214   Post_15:

215     Thread: Thread_14

Page 159: CodeIgniter and Doctrine from scratch

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'

237    content: Maecenas ullamcorper commodo leo, lobortis molestie turpis 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'

243    content: Integer tincidunt facilisis dolor, vitae pellentesque turpis 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'

Page 160: CodeIgniter and Doctrine from scratch

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'

261    content: Nullam sollicitudin nulla at orci accumsan eget ultrices felis 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'

267    content: Nullam sit amet purus nec mauris convallis tincidunt sodales 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.

Page 161: CodeIgniter and Doctrine from scratch

Go home: http://localhost/ci_doctrine/You should see this:

Click the “CodeIgniter Discussion” linkYou should see:

Page 162: CodeIgniter and Doctrine from scratch
Page 163: CodeIgniter and Doctrine from scratch

Sorting By Last Post DateAs 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.phpview sourceprint ? 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

19         foreach ($threads as &$thread) {

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();

Page 164: CodeIgniter and Doctrine from scratch

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.phpview sourceprint ? 01 <h2><?php echo $title ?></h2>

02

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

04 <div class="thread">

05

06     <h3>

07        <?php echo anchor('threads/display/'.$thread['id'], $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'],

Page 165: CodeIgniter and Doctrine from scratch

13                                 $thread['username']); ?></em>,

14         Last Post: <em><?php echo $thread['last_post_date']; ?></em>15     </div>

16

17 </div>

18 <?php endforeach; ?>Pagination LinksNow, 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 ViewLet’s set the output of the pagination links in the View.

Edit: system/application/views/forum.phpview sourceprint ? 01 <h2><?php echo $title ?></h2>

02

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

04 <div class="thread">

05

06     <h3>

07        <?php echo anchor('threads/display/'.$thread['id'], $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>

Page 166: CodeIgniter and Doctrine from scratch

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 ModelNow let’s modify the Forum Model.

Edit: system/application/models/forum.phpview sourceprint ?

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'

Page 167: CodeIgniter and Doctrine from scratch

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)

Page 168: CodeIgniter and Doctrine from scratch

48             ->execute();

49

50         foreach ($threads as &$thread) {

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 ControllerFinally we update the Forums Controller.

Page 169: CodeIgniter and Doctrine from scratch

Edit: system/application/controllers/forums.phpview sourceprint ?

01 <?php

02 class Forums extends Controller {

03

04     public function display($id, $offset = 0) {

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);

Page 170: CodeIgniter and Doctrine from scratch

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:

Page 171: CodeIgniter and Doctrine from scratch

Continue clicking on the pagination links:

Seems to be working

Page 172: CodeIgniter and Doctrine from scratch

Day 11 – Record Hooks

Record HooksIn 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 sourceprint ? 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

Page 173: CodeIgniter and Doctrine from scratch

16 }

Let’s look at what we are going to be doing today to utilize this feature.Forum Display Page ImprovementsIn 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 sourceprint ? 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)

Page 174: CodeIgniter and Doctrine from scratch

16             ->offset($offset)

17             ->setHydrationMode(Doctrine::HYDRATE_ARRAY)

18             ->execute();

19

20         foreach ($threads as &$thread) {

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” RelationshipWe 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

Page 175: CodeIgniter and Doctrine from scratch

view sourceprint ?

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 HookWhen 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.

Page 176: CodeIgniter and Doctrine from scratch

Edit: system/application/models/post.phpview sourceprint ?

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:

Page 177: CodeIgniter and Doctrine from scratch
Page 178: CodeIgniter and Doctrine from scratch

You can see the new column. And thanks to the Record Hook, the values have been assigned already.Simplify ThingsNow we can go ahead and simplify the “getThreadsArray” method under the Forum Model.

Edit: system/application/models/forum.phpview sourceprint ?

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

22         foreach ($threads as &$thread) {

23

24             $thread['num_replies'] = $thread['Posts'][0]['num_replies'];

25             $thread['created_at'] = $thread['First_Post']['created_at'];

26             $thread['username'] = $thread['First_Post']['User']

Page 179: CodeIgniter and Doctrine from scratch

['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 ResultsWe haven’t really changed anything on the actual pages. So everything should look the same as before.