codeigniter and doctrine from scratch
TRANSCRIPT
![Page 1: CodeIgniter and Doctrine from scratch](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/1.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/2.jpg)
![Page 3: CodeIgniter and Doctrine from scratch](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/3.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/4.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/5.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/6.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/7.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/8.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/9.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/10.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/11.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/12.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/13.jpg)
![Page 14: CodeIgniter and Doctrine from scratch](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/14.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/15.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/16.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/17.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/18.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/19.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/20.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/21.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/22.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/23.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/24.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/25.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/26.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/27.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/28.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/29.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/30.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/31.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/32.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/33.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/34.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/35.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/36.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/37.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/38.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/39.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/40.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/41.jpg)
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->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->execute(Array)
6 #2 C:\wamp\www\ci_doctrine\system\application\plugins\doctrine\lib\Doctrine\
![Page 42: CodeIgniter and Doctrine from scratch](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/42.jpg)
Connection.php(693): Doctrine_Connection->exec('INSERT INTO use...', Array)
7#3 C:\wamp\www\ci_doctrine\system\application\plugins\doctrine\lib\Doctrine\Connection\UnitOfWork.php(595): Doctrine_Connection->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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/43.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/44.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/45.jpg)
![Page 46: CodeIgniter and Doctrine from scratch](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/46.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/47.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/48.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/49.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/50.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/51.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/52.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/53.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/54.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/55.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/56.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/57.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/58.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/59.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/60.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/61.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/62.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/63.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/64.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/65.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/66.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/67.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/68.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/69.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/70.jpg)
In this case my username is burak, and it was displayed back to me.Voila!
![Page 71: CodeIgniter and Doctrine from scratch](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/71.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/72.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/73.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/74.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/75.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/76.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/77.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/78.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/79.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/80.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/81.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/82.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/83.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/84.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/85.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/86.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/87.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/88.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/89.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/90.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/91.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/92.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/93.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/94.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/95.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/96.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/97.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/98.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/99.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/100.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/101.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/102.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/103.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/104.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/105.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/106.jpg)
The links are showing up at the top right corner. Click Login. Login as Testuser:mypass .
![Page 107: CodeIgniter and Doctrine from scratch](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/107.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/108.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/109.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/110.jpg)
[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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/111.jpg)
)
[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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/112.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/113.jpg)
{
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/114.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/115.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/116.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/117.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/118.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/119.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/120.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/121.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/122.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/123.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/124.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/125.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/126.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/127.jpg)
[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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/128.jpg)
)
[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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/129.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/130.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/131.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/132.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/133.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/134.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/135.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/136.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/137.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/138.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/139.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/140.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/141.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/142.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/143.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/144.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/145.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/146.jpg)
)
[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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/147.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/148.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/149.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/150.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/151.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/152.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/153.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/154.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/155.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/156.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/157.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/158.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/159.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/160.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/161.jpg)
Go home: http://localhost/ci_doctrine/You should see this:
Click the “CodeIgniter Discussion” linkYou should see:
![Page 162: CodeIgniter and Doctrine from scratch](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/162.jpg)
![Page 163: CodeIgniter and Doctrine from scratch](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/163.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/164.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/165.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/166.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/167.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/168.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/169.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/170.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/171.jpg)
Continue clicking on the pagination links:
Seems to be working
![Page 172: CodeIgniter and Doctrine from scratch](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/172.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/173.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/174.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/175.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/176.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/177.jpg)
![Page 178: CodeIgniter and Doctrine from scratch](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/178.jpg)
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](https://reader035.vdocuments.net/reader035/viewer/2022062417/551775cb4979593d228b4a1b/html5/thumbnails/179.jpg)
['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.