Integrating Zend Framework with Doctrine ORM

Yet another how-to for Zend Framework. You can find a lot of integration how-tos around the web with googling and I maybe the just one of them. I’m trying to find the wheel again. This one is like my subversion how-tos, a self reminder first. So let’s cut the crap and get down to the business.

First of all Doctrine is an ORM framework for PHP. Recently Zend has declared that they had given up on Zend_Entity and decided to stick with Doctrine and contribute to improve it. So the integration will be a lot easier later then now. First go and download Doctrine 1.1 the current stable release. You can be an alpha-beta guy. However I don’t suggest you use the beta versions for production environment. This how-to does not change a lot for 1.1 and 1.2 however there can be a significant differences with 2.0. You are warned!

I assume you are already familiar with how you bootstrap Zend Framewok, if you aren’t you can read it here. Assume that I’m continuing that post and I will use the codes from there.

Now let’s open our application.ini file:

[production]
phpSettings.display_startup_errors = 0
phpSettings.display_errors = 0
includePaths.library = APPLICATION_PATH "/../library"
includePaths.model = APPLICATION_PATH "/models"
bootstrap.path = APPLICATION_PATH "/Bootstrap.php"
bootstrap.class = "Bootstrap"
resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"
resources.db.adapter = "pdo_mysql"
resources.db.params.host = "localhost"
resources.db.params.username = "root"
resources.db.params.password = "the_really_secret_password"
resources.db.params.dbname = "dbname"
resources.db.isDefaultTableAdapter = true

resources.layout.layout = "myLayout"
resources.layout.layoutPath[] = APPLICATION_PATH "/layouts/scripts"
resources.layout.layoutPath[] = APPLICATION_PATH "/layouts/generics"

;This one means inherit from the production values.
;So the testing env inherits the values from the production env
; every value defined in the production is also defined in the testing yet
; if value is defined in both, value defined in testing env is used.
[testing : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1

[development : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1

As you notice we had used Zend_Db for our database connection and models. However we do not want anymore to hard code how our models work and want something that can be autogenerated. So we intend to use Doctrine for this. Our configuration file must remove the db resources and must add things related to the Doctrine and conf file becomes somethine like this:

[production]
phpSettings.display_startup_errors = 0
phpSettings.display_errors = 0
includePaths.library = APPLICATION_PATH "/../library"
includePaths.model = APPLICATION_PATH "/models"
bootstrap.path = APPLICATION_PATH "/Bootstrap.php"
bootstrap.class = "Bootstrap"
resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"

;Doctrine related settings. You must change the dsn for every envrionment
doctrine.dsn = "mysql://my_mysql_username:my_secret_mysql_password@myslq_host_usually_localhost/my_database"
doctrine.module_directories[] = APPLICATION_PATH "/models/doctrine/generated"
doctrine.module_directories[] = APPLICATION_PATH "/models/doctrine/"
doctrine.attr.ATTR_MODEL_LOADING = Doctrine::MODEL_LOADING_CONSERVATIVE
doctrine.attr.ATTR_AUTO_ACCESSOR_OVERRIDE = 1
doctrine.attr.ATTR_AUTOLOAD_TABLE_CLASSES = 1
doctrine.attr.ATTR_USE_NATIVE_ENUM = 1

resources.layout.layout = "myLayout"
resources.layout.layoutPath[] = APPLICATION_PATH "/layouts/scripts"
resources.layout.layoutPath[] = APPLICATION_PATH "/layouts/generics"

;This one means inherit from the production values.
;So the testing env inherits the values from the production env
; every value defined in the production is also defined in the testing yet
; if value is defined in both, value defined in testing env is used.
[testing : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1

[development : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1

So now that we have something else to bootstrap our bootstrapping class should have an additional function:

class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
    //Some older init functions are here...
    protected function _initDoctrine()
    {
        //Load the autoloader
        Zend_Loader_Autoloader::getInstance()->registerNamespace('Doctrine')->pushAutoloader(array('Doctrine', 'autoload'));

        $manager = Doctrine_Manager::getInstance();
        foreach ($this->_options['doctrine']['attr'] as $key => $val) {
            $manager->setAttribute(constant("Doctrine::$key"), $val);
        }

        $conn = Doctrine_Manager::connection($this->_options['doctrine']['dsn'], 'doctrine');

        Doctrine::loadModels($this->_options["doctrine"]["module_directories"]);
    }
}

So it’s pretty straightforward. However it requires some explanation. As you know lazyloading is better if you care about your performance. Zend Framework is not like this however and it generally has a lot of static loading and this causes a lot of overhead and slows down your script a bit. Same is valid for Doctrine too. However you can define a conservative (lazy) loading option in it. That’s what we are doing. In the first line we are adding the Doctrine’s autoloader function to Zend’s autoloading class. Once you push the autoloader, this autoloader will be called on every autloading call which was not in the Zend’s namespace (remember the namespace issue is solved via class names).

You can also change the pushAutoloader() method to something like this: pushAutoloader(array(‘Doctrine’, ‘autoload’), ‘Doctrine’); . Even it looks cool, it will break the autoloading of doctrine models because generally models are not in the Doctrine namespace (because their class name does not start with “Doctrine_”) and Doctrine’s autoloader will not be called for them which will break up the autoloading process completely. However by not doing so, our doctrine autoloader, tries to autoload everything. This takes more time but not as much as loading them all.

For setting options, I know the eval() is a bit dirty right there. However you cannot deny the flexibility it gives us here. Usage of constant() gives me a flexible way to handle the attribute assignment. I can define a value for every attribute of Doctrine in application config and nothing is hard coded. So if I want to change the loading method from conservative to aggressive, it’s requires me to change the configuration and not the code.

Later we define and open a connection to our database server. Doctrine takes a dsn (legacy from MDB2 perhaps) and connects to it. Whenever you want to reach the connection object all you have to do is to call Doctrine_Manager::connection() and you can execute sqls via that. You can also define other connections too and you name them with the second parameter. When calling just give the string identifier to the connection().

And lastly the most critical part of the integration. I’m defining where my model classes are existing. These directories can be arranged from the conf file, so this gives me a bit more flexibility as I can distribute my models in different folders on the development environment however I can put them together in a single file and only one folder might be enough. Doctrine makes this “compilation” with its generator scripts. Separated files are cool however using “require” too many times can cause a CPU problem. So compiling them is a better option. However this may cause of loading all the models to the memory too as they are in one file and all of them will be loaded once the files is included. You can solve this problem by “bundling” models. Let’s say you have a User model, Every user can have one or more emails and one or more addresses. So Now you have 3 database tables: users, emails and addresses. You know that if you want to create a User model you will also need the Email and Address models. So bundle them together and save time from “require” commands. Well I don’t think you will ever need this kind of optimization, check other options like Memcached (to cache database queries) or Zend Server (for caching your php scripts, there is a community edition too).

Let’s get back to the topic. Notice that there are two directories defined in the configuration file. One is the app/models/doctrine and second is app/models/doctrine/generated/. Doctrine is a smart companion and it can auto generate php classes from database tables or yaml schemas. And it can also create sqls or yamls from php classes. But how? Let’s create a generator script for that. Create a file on /doctrine_generator.php (on the same level as app/ or lib/):

require 'public/index.php'; //For bootstrapping purposes, you may have to disable $application->run() and only use $application->bootstrap()
Doctrine::generateYamlFromDb("app/configs/", array("doctrine"), array("generateTableClasses" => true));
Doctrine::generateModelsFromDb("app/models/doctrine/", array("doctrine"), array("generateTableClasses" => true));

This will create a yaml schema on the configs folder and lots of files under the app/models/doctrine folder. It didn’t? Yes, it didn’t and you have taken a fatal error showing that Doctrine class is not found. But we were autoloading it! Sadly, we weren’t and we never did. Remember that we had defined a class which was autoloading doctrine classes. You haven’t included that one. So open up your public/index.php file and add these lines before including Zend/Application.php file. I’m assuming Doctrine library is already added to your include path:

//Include the Doctrine ORM library
require_once 'Doctrine.php';

So you ran it again and it ran smoothly. When you keep running this script, the changes you have made to you model classes will not be affected because there is a two level inheritance for every model class. Let’s say you have a table named user. Generator script first creates a BaseUser class which defines the table fields and it extends from Doctrine_Record (yes yes ActiveRecord pattern). Later it creates a User class which extends from BaseUser. Everything you do is done on the User class and if the User class file exists, generator will not touch it. So you can change every bit of your models. Base files will be under the generated folder. That’s why we add two directories.

Another thing you can notice is there is also a UserTable class. What is it for? It’s the collection and it’s the main class which you can make a query from database. You can use dql or just the find() method which will return the object with that id.

So far we have done nothing more than Zend_Db_Table and Zend_Db_Table_Row duo. It was nearly the same wasn’t it? If this is all why should I ever have to bother to migrate or to learn something new. We had already done something important, the auto generation of models. Yes I know that Zend_Db_Table does the same too, however it does it on the run time! Every time it creates an instance of the model, it sends a “Describe” query to the database which is not handy for a production environment. Of course, you can use caching mechanisms to prevent it, but why to waste time cache something which can only change when you upgrade to a new version.

But the real magic of Doctrine comes with the relations. As this is an ORM framework, everything you define on the database is in your code! This includes the relations between your database tables. That’s where Doctrine comes in handy. In Zend_Db_Table you have to define your relations and your dependent tables and columns. But Doctrine finds them and map them to your models. However for this magic to work properly, you should not forget to define the foreign keys between tables. In MySQL, you have to use the InnoDB engine to create foreign keys. When you define it in your MySQL you are ready to rock’n’roll!

Now that Doctrine is integrated with our Zend Framework, let’s create a controller which will list our users and we will be able to modify them. I won’t get into the details of it as you most probably know how to do it. There are two things you should notice in these scripts, first the controller and usage of UserTable and User classes. Second, how the form and User class is associated.

Let’s go with the user manager controller. Create a file named UserManagerController.php:

public class UserManagerController extends Zend_Controller_Action
    public function indexAction()
    {
        $userData = Doctrine::getTable("Users");
        $this->view->users = $userData->findAll()->toArray(); //On the view there is a foreach loop which shows everything related to the user.
    }

    public function addAction()
    {
        //Check if the attributes that passed are valid.
        //Do not forget about FIEO!!!!
        $user = new Users();
        $user->hydrate($_POST['user']); //Attention here!
        try {
            $user->save(); //Throws an Exception in case of failure
            $this->view->message = "Success";
        } catch (Exception $e) {
            $this->view->message = "Failure";
        }
        $this->_forward("index");
    }

    public function editAction()
    {
        //Make some validations about existing user id, if you don't you can end up adding more users with this usage.
        //Do something like this to write less code but never do "exactly like this"
        $this->_forward("add");
    }

    public function deleteAction()
    {
        $user = new Users();
        $user->hydrate($_POST['user']);
        $user->delete();
        $this->_forward("index");
    }
}

Your form must be something like:

Notice that I have defined html input elements as an array. PHP parses this usage to an array, and it really simplifies the assignment process.

So what about validation. Of course you can do that manually, however wouldn’t be nice if you had some built-in functions as you already define what the attributes are in the database to the doctrine. The best way of course would be to use a Zend_Form component. By this the form creation and validations are handled, however for lazy guys, Doctrine has a validation attribute too!

Change the doctrine configuration to this:

;Doctrine related settings. You must change the dsn for every envrionment
doctrine.dsn = "mysql://my_mysql_username:my_secret_mysql_password@myslq_host_usually_localhost/my_database"
doctrine.module_directories[] = APPLICATION_PATH "/models/doctrine/generated"
doctrine.module_directories[] = APPLICATION_PATH "/models/doctrine/"
doctrine.attr.ATTR_MODEL_LOADING = Doctrine::MODEL_LOADING_CONSERVATIVE
doctrine.attr.ATTR_AUTO_ACCESSOR_OVERRIDE = 1
doctrine.attr.ATTR_AUTOLOAD_TABLE_CLASSES = 1
doctrine.attr.ATTR_USE_NATIVE_ENUM = 1
;I added this line.
doctrine.attr.ATTR_VALIDATE = Doctrine::VALIDATE_ALL

Now we are checking everything and our controller’s add action evolves to this:

    public function addAction()
    {
        //Check if the attributes that passed are valid.
        //Do not forget about FIEO!!!!
        $user = new Users();
        $user->hydrate($_POST['user']); //Attention here!
        if (!$user->isValid()) {
            $this->view->message = "Error";
            $this->_forward("index");
        }
        try {
            $user->save(); //Throws an Exception in case of failure
            $this->view->message = "Success";
        } catch (Exception $e) {
            $this->view->message = "Failure";
        }
        $this->_forward("index");
    }

For detailed information about Doctrine validators check here. However do not trust Doctrine too much too, because you may want to define some other validations related to your business logic and trusting on Doctrine may cause a lot of headache. Before trusting on isValid() make sure you understand where and how it’s useful.

This was it for now. i may write additional posts about Doctrine and Zend Framework in the near future. Until now, check these blogs for additional informations:

http://maxgarrick.com/effective-development-using-doctrine-orm/
http://codeutopia.net/blog/2009/01/17/optimizing-zend-framework-and-doctrine-applications/
http://weierophinney.net/matthew/archives/220-Autoloading-Doctrine-and-Doctrine-entities-from-Zend-Framework.html
http://www.doctrine-project.org/documentation/manual/2_0/en/introduction
http://blog.astrumfutura.com/archives/380-Zend-Framework-Page-Caching-Part-1-Building-A-Better-Page-Cache.html
http://www.doctrine-project.org/documentation/manual/1_1/en/introduction

Update: I have noticed that save() does not return anything (returns void if you care). Instead in case of a failure it throws an exception. So you should put that in a try catch statement. If an exception is caught you have to show the error page. I have updated the code examples here.

Update: I have updated the code on my github with a simple module example with how to bootstrap it etc.