Try Tuts+ Premium, Get Cash Back!
Zend Framework from Scratch – Models and Integrating Doctrine ORM

Zend Framework from Scratch – Models and Integrating Doctrine ORM

Tutorial Details
  • Topic: Zend Framework
  • Version: Zend Framework 1.11, PHP 5.2.17
  • Difficulty: Intermediate
  • Estimated Completion Time: 1 Hour

Ready to take your PHP skills to the next level? In this new “From Scratch” series, we’ll focus exclusively on Zend Framework, a full-stack PHP framework created by Zend Technologies. This second tutorial on our series is entitled “Models and Integrating Doctrine ORM”.


Review

Welcome back to our Zend Framework from Scratch series! In our last tutorial, we learned some basic things about Zend Framework, such as:

  • Where to download the latest Zend Framework files
  • Where and how to set it up locally
  • Creating your first Zend Framework project and setting up a VirtualHost on your web server
  • How exactly Zend Framework implements the MVC pattern and its default application routing
  • Passing data from a controller to its view
  • Creating a site-wide layout for your Zend Framework application
  • Creating new controllers and actions

If you haven’t yet, you should give the previous tutorial a read. It will really make it easier to for you to understand some Zend Framework basics and help you understand some things we discuss in this tutorial.

In this second part of the series, we’ll be talking about a crucial part of any web application — the MODELS. We’ll also be taking a look at how to integrate the very popular Doctrine ORM with our Zend Framework project, and find out why it’s much better to use than Zend Framework’s native Zend_Db. So, without further ado, let’s begin!


What exactly are “Models”?

When I started trying to grasp the concept of MVC, I read quite a number of analogies, which attempted to explain exactly what each of these components represent. One of the best analogies I’ve read so far was from this article, Another way to think about MVC. It goes something like this:

So, let’s imagine a bank.

The safe is the Database – this is where all the most important goodies are stored, and are nicely protected from the outside world.

Then we have the bankers or in programmatic terms the Models. The bankers are the only ones who have access to the safe (the DB). They are generally fat, old and lazy, which follows quite nicely with one of the rules of MVC: *fat Models, skinny controllers*. We’ll see why and how this analogy applies a little later.
Now we’ve got our average bank workers, the gophers, the runners, the Controllers. Controllers or gophers do all the running around, that’s why they have to be fit and skinny. They take the loot or information from the bankers (the Models) and bring it to the bank customers the Views.

The bankers (Models) have been at the job for a while, therefore they make all the important decisions. Which brings us to another rule: *keep as much business logic in the Model as possible*. The controllers, our average workers, should not be making such decisions, they ask the banker for details, get the info, and pass it on to the customer (the View). Hence, we continue to follow the rule of *fat Models, skinny controllers*. The gophers do not make important decisions, but they cannot be plain dumb (thus a little business logic in the controller is OK). However, as soon as the gopher begins to think too much the banker gets upset and your bank (or you app) goes out of business. So again, always remember to offload as much business logic (or decision making) to the model.

Now, the bankers sure as hell aren’t going to talk to the customers (the View) directly, they are way too important in their cushy chairs for that. Thus another rule is followed: *Models should not talk to Views*. This communication between the banker and the customer (the Model and the View) is always handled by the gopher (the Controller). (Yes, there are some exception to this rule for super VIP customers, but let’s stick to basics for the time being).

It also happens that a single worker (Controller) has to get information from more than one banker, and that’s perfectly acceptable. However, if the bankers are related (otherwise how else would they land such nice jobs?)… the bankers (Models) will communicate with each other first, and then pass cumulative information to their gopher, who will happily deliver it to the customer (View). So here’s another rule: *Related Models provide information to the controller via their association (relation)*.

So what about our customer (the View)? Well, banks do make mistakes and the customer should be smart enough to balance their own account and make some decisions. In MVC terms we get another simple rule: *it’s quite alright for the views to contain some logic, which deals with the view or presentation*. Following our analogy, the customer will make sure not forget to wear pants while they go to the bank, but they are not going to tell the bankers how to process the transactions.

In a nutshell:

  • Models are representatives of the Database, and should be where all the business logic of an application resides
  • Controllers communicate with Models and ask them to retrieve information they need
  • This information is then passed by a Controller to the View and is rendered
  • It’s very rare that a Model directly interacts with a View, but sometimes it may happen when necessary
  • Models can talk with other Models and aren’t self-contained. They have relationships that intertwine with each other
  • These relationships make it easier and quicker for a Controller to get information, since it doesn’t have to interact with different Models – the Models can do that themselves

We can see how important Models are in any application, since it’s repsonsible for any dynamic actions that happens in an application. Now that we have a pretty clear understanding of the responsibilities of the Model, as well as the View and Controller, let’s dive into implementing the Models in our application.


Step 1 - Setting up your Zend Application to Connect to a Database

The first thing we’ll need to do is to make our Zend application connect to a database. Luckily, the zf command can take care of that. Open your Command Prompt (or Terminal), cd into your thenextsocial folder, and type in the following:

zf configure db-adapter "adapter=PDO_MYSQL&dbname=thenextsocial&host=localhost&username=[your local database username]&password=[your local database password]" -s development

If correct, you should get an output similar to:

A db configuration for the development section has been written to the application config file.

Additionally, you should see two new lines inside your application.ini file:

resources.db.adapter = "PDO_MYSQL"
resources.db.params.dbname = "thenextsocial"
resources.db.params.host = "localhost"
resources.db.params.username = "[your local database username]"
resources.db.params.password = "[your local database password]"
</p></code>

<h3>The application.ini explained</h3>
<p>The <code>application.ini</code> is a configuration file which should contain all of the configuration we have for an application. This includes, for example, what kind of database we're using, what the database name is, the username and password we'll be using with the database, even custom PHP settings like error display and include paths.</p>

<div class="tutorial_image">
<img src="http://d2o0t5hpnwv4c1.cloudfront.net/1122_zend2/images/application_ini.png" alt="The application.ini file" title="The application.ini file" />

<small>The <code>application.ini</code> file</small>
</div>

<p>I'm sure you've noticed that the <code>application.ini</code> file has sections enclosed in [square brackets]. One of the great things about the <code>application.ini</code> is that you can define different settings depending on what environment your code is in. For example, the database parameters we created earlier falls under the <code>[development : production]</code> section, which means that the set of settings under this section will be used when the application is being run on the <code>development</code> environment.</p>

<p>To add to that, you can &ldquo;inherit&rdquo; settings from another section. For example, the <code>[development : production]</code> section is the configuration for the <code>development</code> environmnent, but inherits all the settings from the <code>production</code> environment as well. This means that any setting which you haven't explicitly overwritten in <code>development</code> will use the setting from <code>production</code>. This allows you to configure settings that are the same in all environments in one place, and just override the ones that you need. Pretty nifty huh?</p>

<p>To configure our project to use the <code>development</code> configuration settings, open or create an <strong>.htaccess</strong> file inside the <strong>public_html</strong> folder, and make sure that it looks like this:</p>

1
SetEnv APPLICATION_ENV development
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ index.php [NC,L]

We can clearly see that the SetEnv APPLICATION_ENV directive sets our application’s environment. If and when we move the application to another environment, this should be the only thing we need to change. This ensures that everything our application relies on to work is defined in the application.ini, which makes sure that our application isn’t relying on any external setting. This helps eliminate the “it works on my development machine, how come it doesn’t work on the production server?” problem.


Step 2 - Create the Database and Some Tables

Before we create your our first Model for the application, we’ll need a Database that the Model will represent first. Let’s start with something simple — a User table, where we’ll save all the registered users for TheNextSocial.

Login to your MySQL database and create a database called thenextsocial. Once created, execute the following query to create a User table, and an accompanying User Settings table:

CREATE TABLE `thenextsocial`.`user` (
  `id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
  `email` VARCHAR(100) NOT NULL,
  `password` TEXT NOT NULL,
  `salt` TEXT NOT NULL,
  `date_created` DATETIME NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE INDEX `Index_email`(`email`)
)
ENGINE = InnoDB
CHARACTER SET utf8 COLLATE utf8_general_ci;

CREATE TABLE `thenextsocial`.`user_settings` (
  `id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
  `user_id` INTEGER UNSIGNED NOT NULL,
  `name` VARCHAR(100) NOT NULL,
  `value` TEXT NOT NULL,
  PRIMARY KEY (`id`),
  CONSTRAINT `FK_user_settings_user_id` FOREIGN KEY `FK_user_settings_user_id` (`user_id`)
    REFERENCES `user` (`id`)
    ON DELETE CASCADE
    ON UPDATE CASCADE
)
ENGINE = InnoDB
CHARACTER SET utf8 COLLATE utf8_general_ci;

These SQL queries should create two tables. A user table with the following columns:

  • id – a unique ID for each user
  • email – the email address of the user, also unique
  • password – the user’s password, which we’ll hash
  • salt – a random salt, which we’ll use to hash the user’s password
  • date_created – the date and time the user record was created

And a user_settings table, where we’ll store any user-related settings, with the columns:

  • id – a unique ID for each setting
  • user_id – the user_id which is a foreign key to user.id
  • name – a string of text that represents the setting
  • value – the value of the setting

It’s worth taking note that the User and User Settings table share a One-to-Many relationship, which means a single User record can be related to multiple User Settings records. This will make it easier to store any kind of information related to a user, for example, their name or profile photo.

Now that we have a few tables to play around with, let’s learn how to create our first Model: the User Model.


Step 3 - Creating your First Model

The DAO Design Pattern

As with a lot of applications, the usual way to use make models in Zend Framework is to make use of a popular design pattern called the “DAO” pattern. In this pattern we have the following components:

  • Table Data Gateway (DataSource) which connects our application to the data source, the MySQL table
  • Data Mapper (DataAccessObject) which maps the data retrieved from the database to the
  • Data Object (Data) which represents a row from our database, after the DataMapper maps the data to it

Let’s begin by creating a Table Data Gateway for the User table using the zf CLI tool:

zf create db-table User user
Creating a DbTable at thenextsocial/application/models/DbTable/User.php
Updating project profile 'thenextsocial/.zfproject.xml'

The zf create db-table takes in two parameters:

  • ClassName – the name of the class
  • database_table – the name of the table

Open the User.php file found in the application/models/DbTable folder and it should look like this:

<?php

class Application_Model_DbTable_User extends Zend_Db_Table_Abstract
{

    protected $_name = 'user';
}

Now let’s create a Data Mapper class. Again, using the zf CLI tool:

zf create model UserMapper
Creating a model at thenextsocial/application/models/UserMapper.php
Updating project profile 'thenextsocial/.zfproject.xml'                                                                           

The UserMapper.php file will be empty right now but we’ll put in some code later. For now, we need to create the Data Object, which is the User model:

zf create model User
Creating a model at thenextsocial/application/models/User.php
Updating project profile 'thenextsocial/.zfproject.xml'
</p></code>

<p>Now that we have all three components of the DAO pattern, we create the code for the files. Open the <code>UserMapper.php</code> file and put in the following code:</p>

1
<?php
class Application_Model_UserMapper
{
	protected $_db_table;
	
	public function __construct()
	{
		//Instantiate the Table Data Gateway for the User table
		$this->_db_table = new Application_Model_DbTable_User();
	}
	
	public function save(Application_Model_User $user_object)
	{
		//Create an associative array
		//of the data you want to update
		$data = array(
			'email' => $user_object->email,
			'password' => $user_object->password,
		);
		
		//Check if the user object has an ID
		//if no, it means the user is a new user
		//if yes, then it means you're updating an old user
		if( is_null($user_object->id) ) {
			$data['salt'] = $user_object->salt;
			$data['date_created'] = date('Y-m-d H:i:s');
			$this->_db_table->insert($data);
		} else {
			$this->_db_table->update($data, array('id = ?' => $user_object->id));
		}
	}
	
	public function getUserById($id)
	{
		//use the Table Gateway to find the row that
		//the id represents
		$result = $this->_db_table->find($id);
		
		//if not found, throw an exsception
		if( count($result) == 0 ) {
			throw new Exception('User not found');
		}
		
		//if found, get the result, and map it to the
		//corresponding Data Object
		$row = $result->current();
		$user_object = new Application_Model_User($row);
		
		//return the user object
		return $user_object;
	}
}

Here we have three methods:

  • __construct() – constructor for the class. Once instantiated, it creates an instance of the Table Data Gateway and stores it
  • save(Application_Model_User $user_object) – takes in a Application_Model_User and saves the data from the object to the database
  • getUserById($id) – takes in an integer $id which represents a single row from the database table, retrieves it, then returns a Application_Model_User with the data mapped

Open up User.php and put the following code in:

<?php
class Application_Model_User
{
	//declare the user's attributes
	private $id;
	private $email;
	private $password;
	private $salt;
	private $date_created;
	
	//upon construction, map the values
	//from the $user_row if available
	public function __construct($user_row = null)
	{
		if( !is_null($user_row) && $user_row instanceof Zend_Db_Table_Row ) {
			$this->id = $user_row->id;
			$this->email = $user_row->email;
			$this->password = $user_row->password;
			$this->salt = $user_row->salt;
			$this->date_created = $user_row->date_created;
		}
	}
	
	//magic function __set to set the
	//attributes of the User model
	public function __set($name, $value)
	{
		switch($name) {
			case 'id':
				//if the id isn't null, you shouldn't update it!
				if( !is_null($this->id) ) {
					throw new Exception('Cannot update User\'s id!');
				}
				break;
			case 'date_created':
				//same goes for date_created
				if( !is_null($this->date_created) ) {
					throw new Exception('Cannot update User\'s date_created');
				}
				break;
			case 'password':
				//if you're updating the password, hash it first with the salt
				$value = sha1($value.$this->salt);
				break;
		}
		
		//set the attribute with the value
		$this->$name = $value;
	}
	
	public function __get($name)
	{
		return $this->$name;
	}
}
  • __construct($user_row) – takes in an optional Zend_Db_Table_Row object, which represents one row from the database, and maps the data to itself
  • __set($name, $value) – a magic function that takes care of setting all of the attributes for the model.
  • __get($name) – a magic function that takes care of getting an attribute of the model.

Let’s try it out! If you followed the previous tutorial, you should have an IndexController.php file. Open it and put in this code that creates a new user:

public function indexAction()
{
	// action body
	$this->view->current_date_and_time = date('M d, Y - H:i:s');
	
	$user = new Application_Model_User();
	$user->email = 'nikko@test.local';
	$user->salt = sha1(time());
	$user->password = 'test';
	$user->date_created = date('Y-m-d H:i:s');
	
	$user_mapper = new Application_Model_UserMapper();
	$user_mapper->save($user);
}

Now go to http://thenextsocial.local/. Once it loads, check the thenextsocial.user table on MySQL and if everything worked, you should have a new User record!

A new User record!

A new User record!

Now, let’s try updating this record. Go back to IndexController.php and update the code to match the following:

public function indexAction()
{
	// action body
	$this->view->current_date_and_time = date('M d, Y - H:i:s');
	
	$user_mapper = new Application_Model_UserMapper();
	$user = $user_mapper->getUserById(1);
	$user->email = 'new_email@test.local';
	$user_mapper->save($user);
}

Check the MySQL table again, and you should see that the email for the record has been updated!

Updated User record

Updated User record

Congratulations! You’ve successfully created your first ever Zend Framework Model!


The Doctrine ORM

Introduction

From the Doctrine ORM website, http://doctrine-project.org/projects/orm:

Object relational mapper (ORM) for PHP that sits on top of a powerful database abstraction layer (DBAL). One of its key features is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL), inspired by Hibernates HQL. This provides developers with a powerful alternative to SQL that maintains flexibility without requiring unnecessary code duplication.

Basically, the Doctrine ORM library abstracts most, if not all of the Model implementation for an application. Some of the incredible advantages I discovered while using Doctrine with Zend Framework are:

  • Very easy to use, guaranteed to cut your development time in half
  • Works just as well with different kinds of DB’s with very little tweaking needed. For example, Doctrine made it very easy for me to port an application I worked on before from using MySQL to MSSQL
  • A scaffolding tool, called Doctrine_Cli that creates models from the database very quickly

To get started, you should download the Doctrine ORM library first from their site. I’ll be using the 1.2.4 version. Go to http://www.doctrine-project.org/projects/orm/1.2/download/1.2.4 and click on the Download 1.2.4 Package link.

Downloading the Doctrine ORM

Downloading the Doctrine ORM

Once downloaded, open it and extract the contents. Inside, you should see Doctrine-1.2.4 folder and a package.xml file. Go inside the folder and you should see a Doctrine folder, a Doctrine.php file, and a LICENSE file.

Doctrine download contents

Doctrine download contents

Copy the Doctrine folder and the Doctrine.php file and put it inside the include path of your PHP installation. If you remember how we set up Zend Framework from the last tutorial, it’s most likely the same folder you placed the Zend library files in.

Doctrine library with Zend library in PHP's include_path

Doctrine library with Zend library in PHP’s include_path

Now we’re ready to integrate it with our Zend application! Begin by opening application.ini again and adding the following configuration inside the [development : production] block:

;Doctrine settings
resources.doctrine.connection_string = "mysql://[replace with db username]:[replace with db password]@localhost/thenextsocial"
resources.doctrine.models_path = APPLICATION_PATH "/models"
resources.doctrine.generate_models_options.pearStyle = true
resources.doctrine.generate_models_options.generateTableClasses = true
resources.doctrine.generate_models_options.generateBaseClasses = true
resources.doctrine.generate_models_options.classPrefix = "Model_"
resources.doctrine.generate_models_options.baseClassPrefix = "Base_"
resources.doctrine.generate_models_options.baseClassesDirectory = 
resources.doctrine.generate_models_options.classPrefixFiles = false
resources.doctrine.generate_models_options.generateAccessors = false

Now that we have our configuration set up, open the application’s Bootstrap.php file. You’ll find this inside the thenextsocial/application folder.

The Bootstrap.php

The Bootstrap.php lets us initialize any resources we might use in our application. Basically, all resources we need to instantiate should be placed here. We’ll dive into this in more detail later in the series, but for now, all you need to know is that the format for methods here are like this:

protected function _initYourResource()
{
	//Do your resource setup here
}

Inside the Bootstra.php file, add the following code to initialize Doctrine with the application:

<?php
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
	public function _initDoctrine()
	{
		//require the Doctrine.php file
		require_once 'Doctrine.php';
		
		//Get a Zend Autoloader instance
		$loader = Zend_Loader_Autoloader::getInstance();
		
		//Autoload all the Doctrine files
		$loader->pushAutoloader(array('Doctrine', 'autoload'));
		
		//Get the Doctrine settings from application.ini
		$doctrineConfig = $this->getOption('doctrine');
		
		//Get a Doctrine Manager instance so we can set some settings
		$manager = Doctrine_Manager::getInstance();
		
		//set models to be autoloaded and not included (Doctrine_Core::MODEL_LOADING_AGGRESSIVE)
		$manager->setAttribute(
			Doctrine::ATTR_MODEL_LOADING, 
			Doctrine::MODEL_LOADING_CONSERVATIVE);
			
		//enable ModelTable classes to be loaded automatically
		$manager->setAttribute(
			Doctrine_Core::ATTR_AUTOLOAD_TABLE_CLASSES,
			true
		);
		
		//enable validation on save()
		$manager->setAttribute(
			Doctrine_Core::ATTR_VALIDATE,
			Doctrine_Core::VALIDATE_ALL
		);
		
		//enable sql callbacks to make SoftDelete and other behaviours work transparently
		$manager->setAttribute(
			Doctrine_Core::ATTR_USE_DQL_CALLBACKS,
			true
		);
        
		//not entirely sure what this does :)
		$manager->setAttribute(
			Doctrine_Core::ATTR_AUTO_ACCESSOR_OVERRIDE,
			true
		);

        //enable automatic queries resource freeing
		$manager->setAttribute(
			Doctrine_Core::ATTR_AUTO_FREE_QUERY_OBJECTS,
			true
		);
		
		//connect to database
		$manager->openConnection($doctrineConfig['connection_string']);
		
		//set to utf8
		$manager->connection()->setCharset('utf8');
		
		return $manager;
	}
	
	protected function _initAutoload()
	{
		// Add autoloader empty namespace
		$autoLoader = Zend_Loader_Autoloader::getInstance();
		$resourceLoader = new Zend_Loader_Autoloader_Resource(array(
			'basePath' => APPLICATION_PATH,
			'namespace' => '',
			'resourceTypes' => array(
				'model' => array(
					'path' => 'models/',
					'namespace' => 'Model_'
				)
			),
		));
		// Return it so that it can be stored by the bootstrap
		return $autoLoader;
	}
}

The setup I’ve done here is based on a script I found in the past on http://dev.juokaz.com, which was maintained by Juozas Kaziukenas, one of the team members at the Doctrine project. Sadly, the blog has already been shut down, so I won’t be able to link to it anymore. Also, take note that we have another method called _initAutoload(). This basically initializes the Zend Autoloader, which will autoload all the generated models inside the models folder. This saves us the hassle of having to include these files one by one.

Next, we need to setup the Doctrine CLI script that we’ll use to auto-generate Models from the database. Go back to the thenextsocial folder and create a folder called scripts. Inside, create a file named doctrine-cli.php and put the following inside:

<?php
/**
 * Doctrine CLI script
 *
 * @author Juozas Kaziukenas (juozas@juokaz.com)
 */

define('APPLICATION_ENV', 'development');
define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../application'));

set_include_path(implode(PATH_SEPARATOR, array(
    realpath(APPLICATION_PATH . '/../library'),
    './',
    get_include_path(),
)));

require_once 'Zend/Application.php';

// Create application, bootstrap, and run
$application = new Zend_Application(
    APPLICATION_ENV,
    APPLICATION_PATH . '/configs/application.ini'
);

$application->getBootstrap()->bootstrap('doctrine');

// set aggressive loading to make sure migrations are working
Doctrine_Manager::getInstance()->setAttribute(
    Doctrine::ATTR_MODEL_LOADING,
    Doctrine_Core::MODEL_LOADING_AGGRESSIVE
);

$options = $application->getBootstrap()->getOptions();

$cli = new Doctrine_Cli($options['doctrine']);

$cli->run($_SERVER['argv']);

Go back inside to your models folder and delete the User model files we have there (if you want, you can move it to another location first, but it shouldn’t be inside the folder). Next, open up your command prompt (or terminal), cd to the scripts folder and type in the following command:

php doctrine-cli.php

You should see something like this:

Expected Doctrine CLI output

Expected Doctrine CLI output

If everything worked out, let’s start creating some models! Type in the following:

php doctrine-cli.php generate-models-db

You should now see the following output:

Generating Models using Doctrine CLI

Generating Models using Doctrine CLI

If you did, check out your models folder again and you should see some brand new User and UserSettings models that have been generated by Doctrine!

Generated Models!

Generated Models!

If you open the files, you won’t see much inside. Most of the code for the models are abstracted by the Doctrine library. By extending the Doctrine_Record class, we have available to us a lot of prebuilt methods from the library. Open IndexController.php again and replace the old test code with the following:

public function indexAction()
{
	// action body
	$this->view->current_date_and_time = date('M d, Y - H:i:s');
	
	$user = new Model_User();
	$user->email = 'new_user_2@test.local';
	$user->password = 'test';
	$user->salt = sha1(time());
	$user->date_created = date('Y-m-d H:i:s');
	$user->save();
}

Once done, go back to http://thenextsocial.local. If the page loads, check your MySQL table and you should see that a brand new User record has been inserted!

User record via Doctrine ORM

User record via Doctrine ORM

Now, let’s try some more complicated stuff — retrieving an existing User via prebuilt Doctrine methods and updating it. Update the code so it looks like this:

public function indexAction()
{
	// action body
	$this->view->current_date_and_time = date('M d, Y - H:i:s');
	
	$user = Doctrine_Core::getTable('Model_User')->findOneByEmailAndPassword('new_user_2@test.local', 'test');
	$user->password = 'new_password';
	$user->save();
}

The findOneByEmailAndPassword() method is a convenience method prebuilt by Doctrine to make it easier to select one row from the database. The great thing about it is you can mix-and-match table columns in the method. For example, you can call something like findOneByIdAndNameAndPasswordAndSalt() and it will still work!

Updating a User record via Doctrine ORM

Updating a User record via Doctrine ORM

There’s a whole lot more we can do now that we use Doctrine ORM for the application’s Model implementation. Stuff like the Doctrine Query Language (DQL), taking advantage of Model relationships and generating Models from YAML. For the remainder of the series, we’ll be using Doctrine ORM for the Model implementation of the application, so you’ll actually be learning two things in the series instead of just one! Score!


Conclusion

By now, you should be able to know the following:

  • What are “Models”
  • Connecting your Zend application to a database
  • How the application.ini works
  • The DAO Design Pattern
  • Creating Models using the ZF CLI tool
  • Where to download the Doctrine ORM and how to install it
  • Integrating the Doctrine ORM with your Zend application
  • The Bootstrap.php
  • Generating Models using the Doctrine CLI tool
  • Basic usage of Models generated with Doctrine

Now that you know how to implement the Models in an Zend Framework powered application, you have the knowledge to create dynamic websites. Try to play around with the application, create some new Controllers and Models that read, update, save and delete from the database.

In our next tutorial, we’ll learn about some often used components of the Zend Framework library, the Zend_Auth and Zend_Acl components and build TheNextSocial‘s authentication system!

Until then, stay tuned, and remember that all the code used here is available on TheNextSocial’s GitHub repository!

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • http://double.lt/en/ V.B.

    Good one ;)

  • Nuruzzaman Sheikh

    Great one!
    I was looking for something on Zend Framework. And luckily I’ve got here a complete series!
    God bless you guys :) thanks in advance for other parts.
    And by the way very well written.

    Great Work Nikko!

  • Nelson

    It seems you need an PHD to work with Zend Framework.

    • w1sh

      I see what you did there. :)

    • http://okeowoaderemi.com Okeowo aderemi

      i don’t think so, am not your typical hardcore PHP developer, yet i still manage to get my way around Zend only it takes me more time understanding since am a bit new to Design Patterns

  • Paul

    I’ve recently created a project with the ZF + Doctrine. I wish I had this tutorial to help me at the time.

    Good work :-)

  • http://www.nexuswebsol.com Muhammad Saqib

    Nice tutorial but after 2 months.. its too late man..

    • assas

      you are right !!!

    • http://nikkobautista.com Nikko Bautista
      Author

      Sorry for the delay guys, I takes a while to write these things since I need a lot of research to make sure everything works flawlessly. To add to that, I was in the middle of writing other articles for Nettuts+ as well.

      I’ll try to give more priority to the Zend Framework series from now on :)

      Thanks!

      • Muhammad Saqib

        thanks nikko.. :)

  • http://zf-boilerplate.com Michael

    Hey guys -

    if you want to skip a lot initial coding work setting up ZF + Doctrine 2 (and more) you may want to take a look at zf-boilerplate (http://zf-boilerplate.com). I am its creator and I am looking forward to your thoughts and feedback.

    Thanks!
    Michael

    • http://nikkobautista.com Nikko Bautista
      Author

      This looks like an interesting project. I’d love to see how you integrated some of the stuff into Zend Framework.

      Thanks for the link!

  • http://www.powerdev.ca Aleks

    Ha!
    Thanks man for the tutorial! Just wanted to dig into Zend Framework this week, very handy!

    Thanks

  • Enrico Martelli

    Hi, I think that in the First Step there’s an error in the code of the post/tutorial, I think that a </code> is missing.

    • http://nikkobautista.com Nikko Bautista
      Author

      Yes, I’ve seen the errors and I’m working on it :)

      Thanks!

  • NoxArt

    I assume it soon will be time for Zend 2 tutorials?

  • http://codefields.com Fran

    I really like Zend Framework, it seems complex at the beginning, but once you learn the basics, it’s easy! And it helps you to be a better programmer.

    • http://nikkobautista.com Nikko Bautista
      Author

      This is one of the bigger downsides with Zend Framework – the learning curve is REALLY high. But you’re right in saying that once you’re past the hump, it’s becomes extremely easy to use :)

    • http://okeowoaderemi.com Okeowo aderemi

      It’s quite true, almost jumped over a bridge when i started Zend especially with Zend Layout i store meaningful info on Snippely so if i need a code am looking for i’d just paste rather than the Zend Documentation.

  • http://www.jsxtech.com Jaspal Singh

    Great Tutorial.
    Thanks for sharing.

  • Matt

    YES! Part 2 is finally here!! XMAS AGAIN!

  • http://www.warpconduit.net Josh Hartman

    Great article, some problems that i ran into:

    Before executing php doctrine-cli.php generate-models-db you need to remove all the previous models, otherwise Doctrine sees the User.php already exists from the first part of the tutorial and does not modify it.

    Under the ;Doctrine settings section of application.ini you need to remove resources. from the beginning of each line, otherwise your _initDoctrine() bootstrap method will fail to grab the config and Doctrine Manager will fail to open a database connection because the dsn is not available.

    Thanks for writing this ZF article, i’m looking forward to more.

    • Marcin

      @Josh
      Thanks, resources were causing problems and once removed everything works.

    • http://fireninewebdesign.com Zach

      Thanks for the info! I was pulling my hair out trying to figure out what was causing that dsn error.

  • http://www.dreu.info Robert-Jan de Dreu

    I don’t completely agree with your analogy nor the picture of MVC you are showing. Controllers should never sit between your View and your Model. The view should get it’s data from the Model directly:
    http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller

    Also if you really hate the thought of putting direct requests into the View you should take a look at the View Model pattern. With that pattern your Views exists out of 2 files. The first is your view model with all your view logic and Model requests. The other is an template that serves the HTML.

    • Oscar

      There are differents MVC implementations. There is no universal law to state such a thing like ‘Views should pull data from the Model directly’.

      Also, Wikipedia is a reference site not a lawbook.

      Some says that View should care only about rendering the data; so there should be only trivial algorithms.
      Also many people says that Tries and Catchs (when something goes wrong) should be done in Controllers. What would happen if you request a DB record by an incorrect id? Doctrine will throw a NoResultException, and the View layer should only care about ‘rendering’, not bussiness logic.

      And again, that is just a MVC implementation.

      Regards :)

  • bazo

    why php 5.2? why zf 1? why doctrine 1? all of these things are already superseded by newer versions

    • Fran

      From my understanding:
      - ZF2 is still in beta, hence ZF1
      - ZF1 hence minimum PHP5.2
      - PHP5.2 hence “No namespaces” hence no Doctrine 2 hence Doctrine 1.2

      Also from my experience with Doctrine (2), it’s difficult to get the namespaces stuff, and the documentation for Doctrine 1 is way better than for Doctrine 2.

      I guess that when ZF2 is finally released Doctrine 2 will be the perfect partner, requiring both PHP5.3 and using both namespaces all around.

      Anyway, just a guess.

    • http://nikkobautista.com Nikko Bautista
      Author

      To add to what Fran has mentioned:

      - ZF2 JUST came out recently, and it’s something that I haven’t used yet.
      - I’ve been using ZF1 for a long time, so it’s something I feel more comfortable writing about
      - ZF1 is still usable and not about to go away anytime soon
      - Doctrine 1.2 is easier to integrate with ZF1 than Doctrine 2 is.

      • Haroon

        Please have a tutorial of integrating Doctrine 2 + with ZF..
        Thanks

  • Marcin

    Thanks Nikko, nice tutorial, can’t wait to see next part.

  • http://www.spletnirazvijalec.si/ spletni razvijalec

    Great article

    This will help a lot of people in there projects

    Hope next part will come soon.

  • Paul M

    What a complete waste of time with a tutorial.

    From the author: “This is one of the bigger downsides with Zend Framework – the learning curve is REALLY high. But you’re right in saying that once you’re past the hump, it’s becomes extremely easy to use :)”

    Wrong, just plain wrong. The curve is high and stays that way. The only time the curve is initally high and then drops like a stone is when you use another framework like codeigniter.

    There are ZERO reasons to develop in zend now. There are really no positives for it, just negatives.

    • Fran

      Easy fix: don’t use it.

    • http://kaelhankins.com KC

      Such an incredibly useful comment. Care to go into (any) detail?

  • http://www.skypointmarketing.com Oskarbravo

    I’m really pleased to see this series being continued! The Codeigniter From Scratch series was so extensive and detailed that I still refer back to it today when working on my projects. ZF is undoubtedly the most daunting PHP Framework and I’ve yet to see a series of tutorials that makes it accessible to the intermediate developer. So far I’m liking your approach and explanations.

    Keep up the good work!

  • Okeowo aderemi

    Just yesterday on LinkedIn was asking for part 2 of this awesome Zend from scratch sadly got Exams tomorrow so might just save the page. nice one

  • Fran

    Thanks for keeping the work on the tutorials. They are great and they really help to understand many concepts. Zend is difficult at the beginning (I am in the beginning…) but it’s really really powerful.

    All in all, great job!

  • Barry

    Hi,

    Nice tutorial, however the application.ini instructions are wrong they need to be as follows:

    ;Doctrine settings
    doctrine.connection_string = “mysql://[USERNAME]:[PASSWORD]@[DBHOST]/[DBNAME]”
    doctrine.models_path = APPLICATION_PATH “/models”
    doctrine.generate_models_options.pearStyle = true
    doctrine.generate_models_options.generateTableClasses = true
    doctrine.generate_models_options.generateBaseClasses = true
    doctrine.generate_models_options.classPrefix = “Model_”
    doctrine.generate_models_options.baseClassPrefix = “Base_”
    doctrine.generate_models_options.baseClassesDirectory =
    doctrine.generate_models_options.classPrefixFiles = false
    doctrine.generate_models_options.generateAccessors = false

    • Michael

      Thanks for your comment. I’ve spent the last hour banging my head off a wall wondering why I couldn’t get anything to work.

      Thanks again!

    • http://www.elimcmakin.com Eli McMakin

      Yep, you don’t want to get a ‘Could not parse dsn’ error in the CLI.

  • Fadel

    thank you for your article,

    but i have got this error : could not parse dsn
    my config is :

    resources.doctrine.connection_string = “mysql://root:pass@localhost/thenextsocial”

    thank’s

    • http://nikkobautista.com Nikko Bautista
      Author

      Hey Fadel,

      Make sure you’re VirtualHost has AllowOverride on so that it can check the .htaccess file. That’s usually the culprit :)

    • Barry

      All of the references to

      resources.doctrine.connection_string

      need to be changed to

      doctrine.connection_string

      eg:

      ;Doctrine settings
      doctrine.connection_string = “mysql://[USERNAME]:[PASSWORD]@[DBHOST]/[DBNAME]”
      doctrine.models_path = APPLICATION_PATH “/models”
      doctrine.generate_models_options.pearStyle = true
      doctrine.generate_models_options.generateTableClasses = true
      doctrine.generate_models_options.generateBaseClasses = true
      doctrine.generate_models_options.classPrefix = “Model_”
      doctrine.generate_models_options.baseClassPrefix = “Base_”
      doctrine.generate_models_options.baseClassesDirectory =
      doctrine.generate_models_options.classPrefixFiles = false
      doctrine.generate_models_options.generateAccessors = false

      • JM

        It works! Thanks barry

  • http://ceaiatiberiu.com Tiby

    wow. Nice one. Thank you so much. it is very helpful for me.

  • http://www.php-pt.com/ Tiago

    When using Doctrine ORM, in the example you forgot to encrypt the user password.

    Keep the good work.

  • Gabe

    Is there a reason you used Doctrine 1.2 for the tutorial? To avoid problems with php 5.2 vs 5.3?

    Great work, very clear and concise!

  • Anees

    Good Tutorial..
    Thanks for sharing

  • Aryan

    Thanks for it, but I’d love video tutorials!!

  • http://blogdict.net/ Alex

    Realy good article, I used CodeIgniter for 2 years, now I want to change ZF, your post is a good start.

    Thanks!

  • http://none andrem

    Thanks a lot, but I stop with error “An error occurred

    Application error” before doing the doctrine ORM part. Can you advise me what to debug problem?

    • http://nikkobautista.com Nikko Bautista
      Author

      Hey andrem,

      Make sure you’re VirtualHost has AllowOverride on so that it can check the .htaccess file. That’s usually the culprit :)

      • andrem

        Here is my virtual hosts record
        VirtualHost 127.0.0.15:80>
        ServerAdmin amutylo@gmail.com

        DocumentRoot “C:/www/projects/thenextsocial.lc/public/”

        Options Indexes FollowSymlinks MultiViews
        RewriteEngine On
        AllowOverride All
        Order allow,deny
        Allow from all

        DirectoryIndex index.php index.html
        ServerName thenextsocial.lc
        ServerAlias http://www.thenextsocial.lc
        ErrorLog “C:/www/projects/thenextsocial.lc/public/logs/error.log”
        CustomLog “C:/www/projects/thenextsocial.lc/public/logs/access.log” common

        but I’m still having same problem again. Anything else to check?

      • Slo

        I have the same issue: “An error occurred – Application error”.
        Directory is set to AllowOverride All but it ain’t helping.

        Is there a way to see more details about the error?

        Thanks

      • Derek

        Same for me too, using MAMP PRO on Lion and allow overrides are set to all. I’m presuming it’s something obvious to someone who has an inkling what’s going on in Zend, but unfortunately I haven’t a clue.

  • mohamad

    thanks a lot,

    excuse me sir
    refer to page
    http://survivethedeepend.com/zendframeworkbook/en/1.0/introduction
    paragraph 8 , 9
    zend framework is a glue framework
    isn’t it ?

  • nikolai

    Zend Framework 2 already in secon beta http://devzone.zend.com/1935/zend-framework-200beta1-released/
    Doctrine 2 as far I remember avaliable 1 year or more http://www.doctrine-project.org/projects/orm

    So why write code which is already out of date?

    • Alchemication

      Agree, nice that somebody took the ZF/Doctrine subject for tutorial,

      but seriously – authors of Doctrine 2 are clearly stating that Doctrine 1 had many issues that have been fixed in D2, so why not use the new stable version? There are hundreds of tutorials out there on how to integrate D2 into ZF projects,

      I just simply love working with Doctrine 2 and ZF, love it, love it, love it ;)

  • Boogiestrat

    Thanks for the clear and human explanations.

    I’ve been trying to figure all that out in one chunk for the last few weeks (ZF-DAO-Doctrine). I was about to forget the idea of using Doctrine when I found your tutorial. Now I think it’s a keeper for all my projects from now on!

    Once you get pass the step when you say to yourself “Ahhh! I got it…”, the rest of the learning task is so easy.

  • Shahriar

    Thanks
    Nice tutorial

  • Alchemication

    Hello All,

    I think that you all loose a lot of time by integrating Doctrine 1 into your projects…
    Just look at any of the Doctrine 2 videos out there and you will see what the developers of Doctrine say about their first release…

    Link to D2 integration:
    http://www.zendcasts.com/unit-testing-doctrine-2-entities/2011/02/

    Good luck!!

  • LD

    Following your tutorial, when I get to the step to run the docrine-cli.php script, I get this error:

    PHP Fatal error: Class ‘Doctrine_Manager’ not found in ….Bootstrap.php on line 17

    Line 17 is this one:

    $manager = Doctrine_Manager::getInstance();

    Any ideas how to correct this error? Thanks in advance.

  • Anshuman Bhagat

    Nice Tutorial!

  • http://roes-wibowo.com Roes Wibowo

    Anyone know ORM application for active record database what is support for PHP 5.2.1? Almost hosting, and my hosting provider use PHP 5.2.1, so Doctrine and phpactiverecord.org not work in my hosting. :(

  • Radu

    I got to the part when we insert one user into the database and I get the error
    “Message: No adapter found for Application_Model_DbTable_User”

    I googled it and if I got it to work by including

    $db = Zend_Db::factory(‘Pdo_Mysql’, array(
    ‘host’ => ‘localhost’,
    ‘username’ => ‘root’,
    ‘password’ => ”,
    ‘dbname’ => ‘zen’
    ));
    Zend_Db_Table::setDefaultAdapter($db);

    int the init method of the indexcontroller

    I don’t think I’m supposed to include this in all my controllers, so, how do I fix this?

    • Radu

      Update:
      I found out that if I include that code snippet into the bootstrap.php file it works application wide.

      Still a question remains, why include the database details into the application.ini at all?

    • Lee Xiang Wei

      you need to add this line into you .htaccess file inside public folder

      SetEnv APPLICATION_ENV development

  • Peter John

    Thanks for being so clear. The zend quick guide is not as quick & clean as your.
    Codeigniter is very well documented!

  • http://www.aerypton.be Aerypton

    Following your tutorial, when I get to the step to run the docrine-cli.php script, I get this error:

    PHP Fatal error: Uncaught exception ‘Doctrine_Manager_Exception’ with message ‘Could not parse dsn’ in /var/www/vectorworks/library/Doctrine/Manager.php:419
    Stack trace:
    #0 /var/www/vectorworks/library/Doctrine/Manager.php(434): Doctrine_Manager->_buildDsnPartsArray(NULL)
    #1 /var/www/vectorworks/library/Doctrine/Manager.php(304): Doctrine_Manager->parseDsn(NULL)
    #2 /var/www/vectorworks/application/Bootstrap.php(198): Doctrine_Manager->openConnection(NULL)
    #3 /var/www/vectorworks/library/Zend/Application/Bootstrap/BootstrapAbstract.php(666): Bootstrap->_initDoctrine()
    #4 /var/www/vectorworks/library/Zend/Application/Bootstrap/BootstrapAbstract.php(626): Zend_Application_Bootstrap_BootstrapAbstract->_executeResource(‘doctrine’)
    #5 /var/www/vectorworks/library/Zend/Application/Bootstrap/BootstrapAbstract.php(583): Zend_Application_Bootstrap_BootstrapAbstract->_bootstrap(‘doctrine’)
    #6 /var/www/vectorworks/scripts/doctrine-cli.php(25): Zend_Application_Bootstrap_BootstrapAbstract->bootstrap(‘doctrine’)
    #7 {main}
    thrown in /var/www/vectorworks/library/Doctrine/Manager.php on line 419

    Fatal error: Uncaught exception ‘Doctrine_Manager_Exception’ with message ‘Could not parse dsn’ in /var/www/vectorworks/library/Doctrine/Manager.php:419
    Stack trace:
    #0 /var/www/vectorworks/library/Doctrine/Manager.php(434): Doctrine_Manager->_buildDsnPartsArray(NULL)
    #1 /var/www/vectorworks/library/Doctrine/Manager.php(304): Doctrine_Manager->parseDsn(NULL)
    #2 /var/www/vectorworks/application/Bootstrap.php(198): Doctrine_Manager->openConnection(NULL)
    #3 /var/www/vectorworks/library/Zend/Application/Bootstrap/BootstrapAbstract.php(666): Bootstrap->_initDoctrine()
    #4 /var/www/vectorworks/library/Zend/Application/Bootstrap/BootstrapAbstract.php(626): Zend_Application_Bootstrap_BootstrapAbstract->_executeResource(‘doctrine’)
    #5 /var/www/vectorworks/library/Zend/Application/Bootstrap/BootstrapAbstract.php(583): Zend_Application_Bootstrap_BootstrapAbstract->_bootstrap(‘doctrine’)
    #6 /var/www/vectorworks/scripts/doctrine-cli.php(25): Zend_Application_Bootstrap_BootstrapAbstract->bootstrap(‘doctrine’)
    #7 {main}
    thrown in /var/www/vectorworks/library/Doctrine/Manager.php on line 419

    How can this be solved?

    • http://www.aerypton.be Aerypton

      The bootstrap doesn’t seem to load the configs out of the application.ini

      • http://www.aerypton.be Aerypton

        Barry helped me with his comment above on “January 17, 2012 at 10:13 am”
        Thx!!

    • http://www.mikevrind.nl MikeVrind

      How did you solve this? I’m having the same problem.

  • Amr Soliman

    Thanks man … very helpful tuts … go ahead with this advance

    hope not to late as this time
    Regards

  • acrobat

    any news on part 3 of this collection of tutorials?

  • Mike

    Little markup mistake in the beginning of step 1.

  • Haroon

    Hi!
    Awesome tutorial.. The only thing i wana say is why you didn’t use Doctrine 2+ . If the latest version is available the y you are using the old one. I am waiting for implementation of the latest Doctrine with zend framework your tutorial.

    Thanks.
    Haroon Khan

  • John

    Gahhgh! I was slacking in the beginning and used the zf create project – command. Now I need to redo everything :o

    But still a very good tutorial. And I can’t wait for next tutorial. When can we expect it?

    Thank you! And keep up your good job!

  • Shah

    How i Implement ZF 1 with Doctrine 2.

    when the CLI command:

    /var/www/thenextsocial$ zf create db-table User userCreating a DbTable at /var/www/thenextsocial
    /application/models/DbTable/User.php

    Updating project profile ‘/var/www/thenextsocial/.zfproject.xml’

    After this i get /var/www/thenextsocial/aplication/models/DbTable/User.php in the following code

    <?php

    class Model_DbTable_User extends Zend_Db_Table_Abstract // Here Application_ Missing
    {

    protected $_name = 'user';

    }

    Why not I get the Application_ in Class Name

  • Andy

    Looks like you have html in certain code sections that were meant to be in the content area instead.

  • Om

    Nikko, are you still there?
    it looks you have abandoned the blog, we are waiting for more but instead you are quite silent.

  • Russ

    I can’t make the Doctrine work XD