A first look at Doctrine 2.1

July 21st, 2011 | Tags: , ,

About one and a half year ago I remember that I had a look at Doctrine 1. It was pretty easy to install and use. The functions where pretty intuitive and it looked very cool. Even when i wanted to implement it to the Zend Framework.

I hadn’t been using Doctrine for a while and decided to pick it up two weeks ago, as we wanted to see if we can implement it for our CMS at our office. So I setup a clean installation of the zend framework (1.11.9) and tried tried to implement Doctrine.

The main goal was to see if we can reverse engineer existing databases and then start doing some queries.

Finding 1:
It was quite hard to get it going. I was confronted to numerous terminologies which I didn’t understand. What are Proxies, why do we need them? What are annotations? What is the entityManager for?  And so on.

Finding 2:
It was hard to get directions to get the Doctrine classes working with the Zend Framework. We had a hard time getting all autoloaders functioning correct. It also plays a role that Doctrine 2.1 uses Namespaces and the Zend Framework doesn’t yet.

protected function _initDoctrine()
    {
        // Get the configuration options from the application.ini file
        $options = $this->getOption('doctrine');               

        // Get the Zend Autoloader
        $zendAutoloader = Zend_Loader_Autoloader::getInstance();

        // Autoload the doctrine objects
        $autoloader = array(new \Doctrine\Common\ClassLoader('Doctrine'), 'loadClass');
        $zendAutoloader->pushAutoloader($autoloader, 'Doctrine');

        // Autoload the models
        $autoloader = array(new \Doctrine\Common\ClassLoader('Entities', $options['entitiesPath']), 'loadClass');
        $zendAutoloader->pushAutoloader($autoloader, 'Entities');

        // Now configure doctrine
        if ('development' == APPLICATION_ENV) {
            $cacheClass = isset($options['cacheClass']) ? $options['cacheClass'] : 'Doctrine\Common\Cache\ArrayCache';
        } else {
            $cacheClass = isset($options['cacheClass']) ? $options['cacheClass'] : 'Doctrine\Common\Cache\ApcCache';
        }
        $cache = new $cacheClass();

        // Setup the Doctrine configuration
        $config = new Configuration();
        $config -> setMetadataCacheImpl($cache);
        $config -> setMetadataDriverImpl(Doctrine\ORM\Mapping\Driver\AnnotationDriver::create(array($options['entitiesPath'])));
        $config -> setQueryCacheImpl($cache);
        $config -> setProxyDir($options['proxiesPath']);
        $config -> setProxyNamespace('Proxies');
        $config -> setEntityNamespaces(array('Entities'));
        $config->setAutoGenerateProxyClasses(true);
        $em = EntityManager::create(
            $options['connection'],
            $config
        );

        // Register the default ORM annotations for Doctrine. This has all the commonly
        // used annotations for the entities.
        AnnotationRegistry::registerFile('Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php');

        // Once we have the EntityManager ready, add it to the registry
        Zend_Registry::set('em', $em);

        // end
        return $em;
    }

I created a resource in my bootstrap file in order to setup, load and configure Doctrine.
Then in my index controller I created a function to generate my entities. When generating my entities based on an existing mysql database i had the following problems

  • All tables need to have a primary key, without a primary key Doctrine 2 will not generate the entities.
  • We had some tables with geometry fields. Doctrine 2 cannot handle these field types yet. I had to change them to doubles or bynaries

Here is the code I used to generate my Entities.

 
public function generateFromDb()
    {
        try {
            $this->_em->getConfiguration()->setMetadataDriverImpl(
                new \Doctrine\ORM\Mapping\Driver\DatabaseDriver(
                    $this->_em->getConnection()->getSchemaManager()
                )
            );
            $cme = new Doctrine\ORM\Tools\Export\ClassMetadataExporter;
            $cme = new \Doctrine\ORM\Tools\Export\ClassMetadataExporter();
            $cmf = new Doctrine\ORM\Tools\DisconnectedClassMetadataFactory;
            $cmf>setEntityManager($this->_em);

            $generator = new \Doctrine\ORM\Tools\EntityGenerator();
            $generator->setUpdateEntityIfExists(true);	// only update if class already exists
            $generator->setGenerateStubMethods(true);
            $generator->setGenerateAnnotations(true);
            $result = $generator->generate($metadata, APPLICATION_PATH . '/models/');

        }
        catch(Exception $e) {
            Zend_Debug::dump($e->getMessage());
        }
    }

After I generated my entities I wasn’t yet able to start my queries. This because I had errors that it couldn’t find my classes `Entities\User`.  Further investigation learned that each Entity class was missing the line `namespace Entities;`. I thought it was just a setting to get this working. But I ended up debugging the `\Doctrine\ORM\Tools\EntityGenerator();`.

Bug?

What happens behind the scenes is that Doctrine creates an metadata array with objects of each database table. Each object has a `name` property which is by default set to have the name of the table. Doctrine scans this name to find a `\\` if it finds the two shlashes it will create a namespace else it will skip and not create a namespace. Huh?? Yes that’s also what I thought.

The easiest solution to solve this was to loop through the objects and and change the name property to something like: `Entities\\User`. When i did this, I was able to create entities having the namespace declaration. And now I am able to start working with queries.

 
  public function generateFromDb()
    {
        try {
            $this->_em->getConfiguration()->setMetadataDriverImpl(
                new \Doctrine\ORM\Mapping\Driver\DatabaseDriver(
                    $this->_em->getConnection()->getSchemaManager()
                )
            );
            $cme = new Doctrine\ORM\Tools\Export\ClassMetadataExporter;
            $cme = new \Doctrine\ORM\Tools\Export\ClassMetadataExporter();
            $cmf = new Doctrine\ORM\Tools\DisconnectedClassMetadataFactory;
            $cmf->setEntityManager($this->_em);
            $metadata = $cmf->getAllMetadata();
            foreach($metadata as $k => $v){
                $bla = $v->name;
                $v->name  = 'Entities\\'. $bla;
            }

            $generator = new \Doctrine\ORM\Tools\EntityGenerator();
            $generator->setUpdateEntityIfExists(true);	// only update if class already exists
            $generator->setGenerateStubMethods(true);
            $generator->setGenerateAnnotations(true);
            $result = $generator->generate($metadata, APPLICATION_PATH . '/models/');

        }
        catch(Exception $e) {
            Zend_Debug::dump($e->getMessage());
        }
    }

}

I think this is a bug, maybe someone in the community has a different solution to this. I would be glad to hear about it.

First thoughts on Doctrine 2.1

The only thing what keeps me going with doctrine 2.1 at the moment is that Doctrine ‘s query’s are easier and faster. The problem is that I haven’t seen much of this yet as it took me a lot of time to get the basic configuration up and running. I also have a lot of question’s which i hope to become clear in the next few days as I continue to work on my little Zend Framework 1.11.x and Doctrine integration 2 project.

There are already a lot of resources available on the Internet. I have looked at various configurations, like for example the `bisna` project from Guilhere Blanco.  But I keep saying that it’s really difficult and has a steep learning curve. Doctrine 1.2 was really simple. Doctrine 2.x is a lot more difficult to get into.

  • Pingback: Jigal Sanders ‘Blog: Ein erster Blick auf Doctrine 2,1 | PHP Boutique

  • http://codeutopia.net/ Jani Hartikainen

    It seems like a rather common issue that people have a hard time with Doctrine 2 when they start. The docs are rather techno-babbley in that they are hard to follow when you start.

    However, I think Doctrine 2 is a really, really big productivity boost and I hope you stick with it enough to understand it better :)

    By the way, you can use the Doctrine 2 CLI tool to generate the database and stuff like that so you won’t need to implement that yourself like you did now.

  • Pingback: Jigal Sanders’ Blog: A first look at Doctrine 2.1 | Scripting4You Blog

  • Pingback: Jigal Sanders’ Blog: A first look at Doctrine 2.1

  • Iain Potter

    Could you define a custom type mapping for your geometry field issue?

    • admin

      I changed the fields manually in the database. I know there is a way to do some type mapping. But I didn’t test it yet.

  • Iain Potter

    Nice article BTW :)

    • admin

      Thank you.

  • iH8

    Why didn’t you add the configuration you put in application.ini? I was looking around for an easy tutorial for integrating Doctrine into ZF but like you said most are too advanced for newbees (also looked over Bisna) and the rest is incomplete like yours. :(

    • admin

      This post wasn’t meant as a tutorial. But I don’t mind to show you wat is in my application.ini:

      doctrine.connection.driver = pdo_mysql
      doctrine.connection.host = localhost
      doctrine.connection.dbname = testdb
      doctrine.connection.user = root
      doctrine.connection.password =
      doctrine.proxyNamespace = Proxies
      doctrine.autoGenerateProxyClasses = 1
      doctrine.entitiesPath = APPLICATION_PATH “/models”
      doctrine.proxiesPath = APPLICATION_PATH “/models”

  • http://codeutopia.net/ Jani Hartikainen

    Oh forgot to mention… You *can* map geometry fields with Doctrine 2. You just need to implement the mapping types yourself.

    I wrote about that exact topic in my blog http://codeutopia.net/blog/2011/02/19/using-spatial-data-in-doctrine-2/

  • Elison

    Good article!
    This help me with ZF + Doctrine 2.

    Thanks.

« »