Tuesday, November 17, 2009

Doctrine 2 and Zend Framework first date

This morning I have tried for the first time to use Doctrine 2 in a Zend Framework application. I used the latest release, 2.0.0 alpha3, for this experiment.
The chosen application is my recently born project NakedPhp, a port of the Naked Objects Java framework which generates the user interface and let the end user manipulate domain objects directly.
During this first run, I have not set up an application resource yet and I have just hardcoded a few configurations to bootstrap correctly Doctrine. I will publish a resource class (conforming to the Zend_Application_Resource_Resource interface) soon when I have it ready.

Doctrine\ORM\EntityManager is the Facade class which act as a portal towards the functionality of Doctrine 2, and it is the homologue of Hibernate EntityManager. Our code should interact mainly with this class.
Though, I have isolated the EntityManager behind an interface since I do not want infrastructure code to slip in NakedPhp for now. The code will obviously depend on Doctrine but it is good practice to have an interface I can mock out easily, as I don't need all the methods of the EntityManager and this way I just hide everything is not mandatory instead of introducing coupling to it.

Doctrine 2 is released in three packages: Common, Database Abstraction Layer and ORM. Instead of downloading three different packages I just grab them from the subversion repository:
svn export http://svn.doctrine-project.org/tags/2.0.0-ALPHA3/lib/
and move the Doctrine/ and vendor/ folders in my library/ directory along with Zend/. The vendor folder contains a small annotation parser.
It can be useful also to export other resources:
svn export http://svn.doctrine-project.org/tags/2.0.0-ALPHA3/bin/
svn export http://svn.doctrine-project.org/tags/2.0.0-ALPHA3/sandbox/
The bin/ folder contains the doctrine.php and doctrine command-line scripts (same thing), while the sandbox provides a working example of Doctrine 2.

Doctrine 2 prescribes that model classes (entities) and proxies should be autoloaded, so after moving doctrine.php in application/ I deleted the reference to Doctrine autoloader and added:
require_once __DIR__ . '/../application/bootstrap.php';
which is my bootstrap file, where:
  • the library/ folder is added to the include_path
  • the autoloader is set up to load Zend/ classes
  • a Zend_Loader_Autoloader_Resource sets up autoloading for my model classes.
  • my autoloader is set up to take care of \Doctrine and \NakedPhp namespaces.
In the future, I will add proxies autoloading setup to this file. Since you probably don't have your own autoloader for namespaced classes, you can simply use IsolatedClassLoader from Doctrine\Common.

It's time to code a cli-config.php file to use with doctrine.php; this file should define two variables (it is well-documented in the sandbox example). My final result is:
$classLoader = new \Doctrine\Common\IsolatedClassLoader('Proxies');
$classLoader->setBasePath(__DIR__ . '/../application/');

$config = new \Doctrine\ORM\Configuration();
$config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache);
$config->setProxyDir(__DIR__ . '/Proxies');

$connectionOptions = array(
    'driver' => 'pdo_sqlite',
    'path' => '/var/www/nakedphp/sqlite/database.sqlite'

// These are required named variables (names can't change!)
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config);

$globalArguments = array(
    'class-dir' => __DIR__ . '/../application/models'
Which is practically the cli-config.php file grabbed from the sandbox, but slightly edited:
  • there were two instances of Doctrine\Common\IsolatedClassLoader, one for the entities and one for the proxies. I deleted the first one since entities autoloading is already taken care for in the bootstrap.
  • I haven't used proxies for now, but the configuration is mandatory. The default namespace and folder are enough.
  • I changed the path to the sqlite database. Sqlite was the fastest choice to get the application up and running, but remember that both the sqlite database file and its directory must be writable by apache and php.
  • I changed also the argument class-dir to specify my entities folder.
Before starting to use the Doctrine 2 command-line interface, you will have to define annotations on your model classes. For instance, @Column and @OneToOne annotations. Since I have developed without even caring about the database until now, I had to add also an $_id private property. :)
I also submitted a patch to improve the errors generated by the schema tool in case of incorrect field names referenced on relations, which is what happened to me today.

Now, it's time to generate your schema:
php bin/doctrine schema-tool --re-create --config=bin/cli-config.php
If cli-config.php is in the directory where you issue this command, you can leave out the --config option.
Maybe after playing a bit with Doctrine 2, you will want to see what was inserted in the database:
php bin/doctrine run-sql --sql="SELECT * FROM Example_Model_Place" --config=bin/cli-config.php 
To obtain an EntityManager reference in a controller, you can set up a dumb resource that includes cli-config.php and return $em. I used a factory which was already available and add a method for retrieving the instance.

So I now have an hacked working instance of Doctrine 2 in my project. The next step will be writing an application resource to allow configuration to be specified following the standard, in application.ini. I will publish this resource in the next days.

The image at the top is the NakedPhp example application screen that says saving was successful. I implemented the storage of my persistence-agnostic in-memory object graph in less than an hour.


Tester_Bester said...

I have tried to run application on windows machine, but it does not work without modification, because you have equal file names for example bootstrap.php and Bootstrap.php that on win machine are the same.


Giorgio said...

Uh, I haven't used windows for a long time. Currently I'm on hiatus with NakedPhp and similar projects since I'm writing my thesis, but I will pay attention to that.