Using Doctrine 2 with symfony 1.x [Part 1]

Background

It’s been a while since Jonathan announced the availability of Doctrine 2 for Symfony 1.x (wouldn’t recommend trying it with anything less than 1.3) and a a few things have changed since then, so here’s a refresher.

We are at a crossroads now with Symfony 2 looming on the horizon, and many developers may wish to wait until that is more stable (later this year) to make the move to Symfony 2 and Doctrine 2 simultaneously. This is not such a bad idea, however Doctrine 2 is already at a level where you may wish to consider using it in your projects, you won’t be disappointed if you do!

It will take me a while to explain why Doctrine 2 is better than Doctrine 1, and more importantly why you should start using – but you can just take my word for it and take a look at a couple of Jonathan’s presentations to back up my claim. (I Recommend Doctrine 2 – not the same old PHP ORM)

Getting started

Check out the plugin and set up the database as described in Jon’s blog, but don’t configure the schema just yet 😉

We’re going to go all out with the “Doctrine 2” way of doing things, so we’ll be using annotations, not yaml or xml – although you can look up that syntax if you prefer. (A lot of the stuff below won’t work though – you can’t have multiple yaml files for example yet).

In your project configuration class, you will have access to some methods:

public function configureDoctrineConnection(\Doctrine\ORM\Configuration $config) {}
 
public function configureEntityManager(\Doctrine\ORM\EntityManager $em) {}

In the first of these, you need to add all the things to configure Doctrine before the entity manager can be created. Once Doctrine has created the entity manager, you can use the second method if you need to, to further configure the entity manager. (Registering a Doctrine listener for example).

So, in your configureDoctrineConnection() method, you’ll want to do something like this:

// Decide where you want the proxy classes to be stored, and which namespace they should use
$config->setProxyDir(sfConfig::get("sf_cache_dir")."/Proxies");
$config->setProxyNamespace("Proxies");
 
// You may want to make this environment specific for performance reasons
$config->setAutoGenerateProxyClasses(false);
$config->setSqlLogger(null);
 
// This will get things working, but later you will want to use APC
// or another "real" cache for production
$cache = new \Doctrine\Common\Cache\ArrayCache;
$reader = new \Doctrine\Common\Annotations\AnnotationReader($cache);
$config->setMetadataCacheImpl($cache);
$config->setQueryCacheImpl($cache);
 
// Tell Doctrine where to find your entities (you may have more than one location)
// This is mostly required for cli tasks that iterate over all of your entities
$paths = array(sfConfig::get("sf_lib_dir") . "/Entities"); // Populate this with all the locations of your entities
$reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\');
$annotation = new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($reader, $paths);
$config->setMetadataDriverImpl($annotation);
 
// Register all the classes that Doctrine needs to autoload
$classLoader = new \Doctrine\Common\ClassLoader('Entities\doctrine');
$classLoader->setIncludePath(sfConfig::get('sf_lib_dir'));
$classLoader->register();
 
$classLoader = new \Doctrine\Common\ClassLoader("Proxies");
$classLoader->setIncludePath($cachedir);
$classLoader->register();
 
$classLoader = new \Doctrine\Common\ClassLoader("Another\Namespace");
$classLoader->setIncludePath(sfConfig::get('sf_lib_dir'));
$classLoader->register();

Ok, that was a lot of stuff – but Doctrine 2 is more verbose – meaning less magic, but more explicit code. Some of the above calls may not be necessary – I need to do further testing, and also I’d like to get a lot of these things “standardised” in the plugin, so you can skip a lot of this if you follow a “default” path. The problem with the original release of the plugin was that it was not possible to have multiple class dirs, which makes it impossible to have plugins (for example) that also contain Entities. With the approach above, you explicitly add as many classes as you like to the annotation driver and the autoloader, and your plugin configuration classes can do the same.

Active Entity

The plugin comes bundled with Active Entity – this means that by extending ActiveEntity from our model classes (Entities) we can use our classes in much the same way as in Doctrine 1. Things like the following become available:

$user->toArray();
$user->save();
\Entities\User::find(1);
$user["name"];

ActiveEntity was written and included with good intentions, because for Doctrine 2 to continue to be compatible with symony in the same way as Doctrine 1, a bit of magic needed to be re-introduced. BUT – for anyone that has been excited by Doctrine 2, one of the major breakthroughs is the non-intrusive model, and ActiveEntity kills that (along with a kitten):

//With ActiveEntity:
class \Entities\User extends  \DoctrineExtensions\ActiveEntity {}
 
//Without ActiveEntity:
class \Entities\User extends-or-does-not-extend  \Whatever\The\Hell\You\Like {}

So I strongly recommend that you Don’t extend ActiveEntity – and gradually the plugin will be “fixed” so that is never necessary. Currently basic object forms and some widgets are working fine without it, but I haven’t tested it with admin generators or anything like that yet so feedback is appreciated.

If you follow my advice, you have to do everything the “Doctrine 2 way”. That means taking control of your own code! Write your own getters and setters, extend your own classes (if you want to) – be verbose, write OOP code, etc!

Ongoing development

Doctrine 2 is still in Alpha at the moment and is constantly changing. Since we are using it actively in a project at the moment, we normally spot the changes pretty quickly and update the plugin to keep up, however we are not using all the aspects of the plugin (like generators, all the form widgets, etc) so from time to time something might completely fail when you svn up.

We are also trying to slowly, and safely (for BC) remove the need for ActiveEntity – but we’re being careful with this because we don’t want to break existing projects that are using it.

Report bugs in the usual way using the symfony bug tracker (if it’s plugin related) or on Doctrine Jira (if you know it is a Doctrine issue). When reporting plugin bugs, please register the bug under the sfDoctrine component and add the keyword sfDoctrine2Plugin as I use a filtered query to keep an eye on Doctrine 2 related issues.

Coming in part 2…

Will either be based on feedback/suggestions (if any) or I’ll move on to some real world examples and best practices when it comes to integrating symfony and Doctrine 2.

9 Comments so far

  1. Brandon Turner on March 24th, 2010

    Russ,
    Thanks for starting this series! I can’t wait for the next article.

    One quick question: Are you using the (exact) code you posted? I ask because of the IsolatedClassLoader on Entities\doctrine and the example \Entities\User class you posted. That combination of autoloader and class name doesn’t work for me and I’m curious how you are naming your entities.

    I’m also interested in your opinions about naming conventions in a Doctrine 2 with Symfony 1.4 world. Does the lib/model directory get replaced with lib/Entities? Are entities in the Entities\doctrine namespace, a Model namespace, or some custom namespace per project?

  2. Russ on March 24th, 2010

    Hi Brandon,
    Yes, I am using pretty much the code that is posted above, however I have just changed a couple of things because the base path should be just that (a base path) and the actual path is then derived from the namespace.

    The \Entities\doctrine example is the namespace for our repository classes – you could use something else there.

    \Entities\User::find(1) will only work if you have a User class in your \Entities directory, which is in the namespace \Entities\User and extends ActiveEntity.

    As for naming conventions, I’m not sure anyone has got that far yet – but I personally like lib/Entities although lib/model/Entities could also fit I guess. It is complicated by the fact that the symfony autoloader does not understand namespaces, so we have to rely on the Doctrine one, which in turn relies on matching namespaces to a directory structure.

  3. Brandon Turner on March 24th, 2010

    In your post you mention doing things the “Doctrine 2 way”. Do you think the Doctrine 1 way of doing things (write YAML files, auto-generate base classes, modify child classes if necessary) will be abandoned for writing model classes from scratch?

    I’ve been playing with Doctrine 2 and the sfDoctrine2Plugin. Support for generating classes via YAML (or XML) files is there. Doctrine 2 provides a few things not yet exposed in the Symfony plugin (generate stub methods, etc). But neither the Symfony plugin, nor Doctrine, builds parent and child classes (BaseUser, User) anymore.

    Writing entity classes with annotations is so easy in Doctrine 2 that I could see why this may be preferred. But I can also imagine a lot of people (myself included) enjoy quickly writing YAML and letting Doctrine auto generate all the boilerplate code.

    I’m wondering your thoughts on what the “default” should/will be. If YAML is supported, do you think it should structure the code into one auto-generated class and one user-modifiable class like Doctrine 1?

  4. Russ on March 27th, 2010

    It’s a bit early to say whether those processes will be abandoned in time, however for now at least yaml and xml are both fully supported in D2, so I think the idea is to genuinely give people a choice.

    The only Doctrine classes that are “built” now are the proxy classes that are used for resolving relations, and these extend your own classes so they are not intrusive. The fact that there are no “base” classes is what makes Doctrine 2 so appealing 🙂

    Whether you use yaml/xml/annotations the structure of your classes will be the same – the only difference is that with xml/yaml your model definition is in an external file, whereas with annotations it is in the docblocks in the classes themselves. I don’t think D2 will ever support any kind of “base” class, so for now at least it doesn’t really matter which format you use with Doctrine itself.

    Of course if you want to follow this blog then annotations is a good choice, but you should be able to transfer anything I write here to xml/yaml easily enough!

  5. Nathan on April 5th, 2010

    This has helped so far, but I’m running into walls. Anyone been able to get the doctrine 2 plugin to generate forms with symfony 1.x? It doesn’t seem to be generating any of the form classes or filters for my model…

  6. Russ on April 5th, 2010

    I’ll be honest the only forms I’ve used so far have been manually created, since it’s pretty easy to do so I haven’t tested this.

    Off on holiday for a bit now – will take a look when I get back! If you have any ideas in the meantime please drop me a line. It could be something in one of plugin task classes that hasn’t been updated in line with the recent Doctrine 2 updates, or it could simply be something that was never implemented in the plugin.

  7. Nathan on April 7th, 2010

    This is probably silly, but I annotated my classes, then switched to using the regular doctrine plugin to generate the schema yml file, built the forms/filters, then switched back to the doctrine2 plugin. I would create them from scratch, but I’m new to both symfony AND doctrine, so this is easier for now.

  8. Nathan on April 25th, 2010

    Is part 2 still coming? I’ve managed to work past some initial hurdles, but I doubt I would have continued without seeing this article. I’d love to see more examples!

  9. Brandon Turner on May 17th, 2010

    @Nathan — With regards to forms not generating, see these two bugs:

    http://trac.symfony-project.org/ticket/8658
    http://trac.symfony-project.org/ticket/8659

Leave a reply