Zend Framework 1.10.5 and Doctrine 2.0 integration

As not all of us are willing to let go ZF for Symfony, the native environment for Doctrine, here is a clean way of integrating Doctrine 2 into your ZF projects.

As the integration method chosen was to create an application resource, you may enable both new projects and existing ones to use Doctrine 2 without changing a single line of bootstrap code.

Added bonus – memcache integration and a custom Doctrine 2 eAccelerator caching class which I strongly advice against using, at least on Windows :)

Requirements:


Initial setup:
Get ZF and Doctrine 2 and unpack them in the dedicated folders, in the library project folder (make sure you only have library/Zend, not library/Zend/Zend).
Move the folder Symfony from the Doctrine folder onto the one from library, overwriting it (I just thought it would be nice not to have multi layered library folders).
Leave out the tests, bins and other files from both ZF and Doctrine.
Copy the needed files from the attached source folder (library/Keops and the root doctrine files).

You will notice that in the attached project you will have some doctrine related files in the root of your project: these are files created based on the Doctrine 2 sandbox sample. They will allow you to use the very cool Doctrine command line (doctrine.bat) and help you propagate changes from your entities to the db.
The  database connection information is hard coded in doctrine-cli-config.php too, besides application.ini – when I first integrated ZF and Doctrine 1 I have also updated the CLI tool to read its settings from application.ini; now I guess I was a bit lazy. Anyway, please feel free to do that.

Here is the code of the resource class, located in library/Keops/Application/Resource:

class Keops_Application_Resource_Doctrine2
      extends Zend_Application_Resource_ResourceAbstract
{
    public function init()
    {
        $bootstrapOptions = $this->getBootstrap()->getOptions();

        $options = $this->getOptions();
        $memcache = null;

        $doctrineConfig = new \Doctrine\ORM\Configuration();
        if (!empty($options['options']['metadataCache'])) {
            $metaCache = new $options['options']['metadataCache']();
            if ($metaCache instanceof
                    \Doctrine\Common\Cache\MemcacheCache) {
                $memcache = new Memcache();
                $memcache->connect('localhost', 11211);
                $metaCache->setMemcache($memcache);
            }
            $doctrineConfig->setMetadataCacheImpl($metaCache);
        }
        if (!empty($options['options']['queryCache'])) {
            $queryCache = new $options['options']['queryCache']();
            if ($queryCache instanceof
                    \Doctrine\Common\Cache\MemcacheCache) {
                if (is_null($memcache)) {
                    $memcache = new Memcache();
                    $memcache->connect('localhost', 11211);
                }
                $queryCache->setMemcache($memcache);
            }
            $doctrineConfig->setQueryCacheImpl($queryCache);
        }

        $driverImpl =
            $doctrineConfig->newDefaultAnnotationDriver(
                array($options['paths']['entities']));
        $doctrineConfig->setMetadataDriverImpl($driverImpl);
        //$doctrineConfig->setEntityNamespaces(
        //    $options['entitiesNamespaces']);

        $doctrineConfig->setProxyDir($options['paths']['proxies']);
        $doctrineConfig->setProxyNamespace(
            $options['options']['proxiesNamespace']);

        $this->getBootstrap()->em =
            \Doctrine\ORM\EntityManager::create(
                $options['connections']['doctrine'],
                $doctrineConfig);
        return $this->getBootstrap()->em;
    }
}

As you may have probably noticed, the resource is configured to run on a memcache caching system. For testing, you may replace that with the included ArrayCache. For production you should definitely use a caching mechanism though. I don’t want to turn this into neither a ZF or a Doctrine tutorial, so let’s leave the subject.

All you need to do to make this work now is to add some settings to application.ini, enabling the new resource; add these to your [production] section:

pluginPaths.Keops_Application_Resource = APPLICATION_PATH "/../library/Keops/Application/Resource"
autoloaderNamespaces[] = "Doctrine"
autoloaderNamespaces[] = "Keops"
autoloaderNamespaces[] = "Entities"
resources.doctrine2.options.metadataCache = "Doctrine\Common\Cache\MemcacheCache"
resources.doctrine2.options.queryCache = "Doctrine\Common\Cache\MemcacheCache"
resources.doctrine2.options.proxiesNamespace = "Proxies"
resources.doctrine2.paths.entities = APPLICATION_PATH "/doctrine/entities"
resources.doctrine2.paths.proxies = APPLICATION_PATH "/doctrine/proxies"
resources.doctrine2.connections.doctrine.driver = "pdo_mysql"
resources.doctrine2.connections.doctrine.dbname = "eplaza"
resources.doctrine2.connections.doctrine.user = "root"
resources.doctrine2.connections.doctrine.password = "root"

As you can see, you can customize connection details, multiple database connections (just add more entries to resources.doctrine2.connections), separate caching for queries and metadata (the memcache PHP client will actually be shared) and the path to the entities and auto generated proxies. If you intend to change any of the paths, make sure the CLI tool is updated accordingly.

You are set up! All you need to do now is edit your entities from application/doctrine/entities like the one below:

namespace Entities;

/**
 * @Entity
 */
class Store
{
    /**
     * @Id @Column(type="integer", name="store_id")
     * @GeneratedValue
     */
    protected $id;

    /**
     * @Column(length=32, nullable=TRUE)
     */
    protected $ext_code;

    /**
     * @Column(length=64)
     */
    protected $name;

    /**
     * @Column(length=8)
     */
    protected $status;

    /**
     * @Column(type="datetime")
     */
    protected $create_ts;

    public function getId()
    {
        return $this->id;
    }

    public function getName()
    {
        return $this->name;
    }

    public function setName($name)
    {
        $this->name = $name;
    }

    public function getExtCode() {
        return $this->ext_code;
    }

    public function setExtCode($ext_code) {
        $this->ext_code = $ext_code;
    }

    public function getStatus() {
        return $this->status;
    }

    public function setStatus($status) {
        $this->status = $status;
    }

    public function getCreateTs() {
        return $this->create_ts;
    }

    public function setCreateTs($create_ts) {
        $this->create_ts = $create_ts;
    }

}

Update the db mappings (this will update the db and create proxies in application/doctrine/proxies; don’t remember if you need to create the database before the first run):

doctrine-update.bat

And use your new Doctrine 2 ORM:

class IndexController extends Zend_Rest_Controller
{

    public function indexAction()
    {
        $em = $this->getInvokeArg('bootstrap')->em;

        $store = new \Entities\Store();
        $store->setName('Paris');
        $store->setStatus('A');
        $store->setCreateTs(
            new DateTime(Zend_Date::now()->get(Zend_Date::ISO_8601)));
        $em->persist($store);
        $em->flush();

        echo $this->_helper->json(
            array('success' => true, 'newRecordId' => $store->getId()));
    }
}

In the root folder you will find also a doctrine-regen.bat file – this one will drop the schema and recreate all tables, so make sure you don’t have any data in the database that you will cry after. Usually you will use doctrine-update.bat, which shouldn’t delete anything.

I’ve had some issues putting it all together, especially with the whole Zend autoloader – Doctrine namespaces thing but it just seemed like a good direction to point my next projects to. Hope some of you will find this useful.

Peace!

This entry was posted in PHP Articles and tagged , , , . Bookmark the permalink.

21 Responses to Zend Framework 1.10.5 and Doctrine 2.0 integration

  1. samuele says:

    can u post the base package whit zf doctrine complete?

    • If you mean a full project including the libraries, I didn’t want to go into the whole licensing territory. Anyway, the full package would be quite large.
      It’s easy to setup though – I left the folder names, in library, as they should appear after copying the libraries.
      I’ll try to post a library folder tree snaphot:

      ├───library
      │   ├───Doctrine
      │   │   ├───Common
      │   │   ├───DBAL
      │   │   └───ORM
      │   ├───Keops
      │   │   ├───Application
      │   │   │   └───Resource
      │   │   └───Doctrine
      │   │       └───Common
      │   │           └───Cache
      │   ├───Symfony
      │   │   └───Components
      │   │       └───Console
      │   └───Zend
      │       ├───Acl
      │       ├───Amf
      │       ├───...
      
  2. SnOoPy says:

    Thanks ! And what about the integration of ZFDebug in this config ?

  3. Galford says:

    Nice article.
    Did you try use YAML in doctrine?

    I successfull used YAML in tools/sandbox configuration but when I tried with your integration with Zend I’ve got errors.

    Any idea?

    Regards.

  4. Galford says:

    First I’m using doctrine 2.0BETA3

    I created directory in ./application/doctrine/yaml and put there yaml files

    After that I changed doctrine-cli-config.php to load yaml driver $driverImpl like it was shown in documentation:
    $driverImpl = new YamlDriver(array(__DIR__ . ‘/application/doctrine/yaml’));

    When I’m trying to create database (doctrine.bat) I have this error
    Fatal error: Class ‘YamlDriver’ not found in D:\Workspace\htdocs\doctrine2\doctrine-cli-config.php on line 22

    When I try this way
    $driverImpl = $config->newDefaultAnnotationDriver(array(__DIR__ . ‘/application/doctrine/yaml’));

    I’ve got this ‘No Metadata Classes to process.’ and no database is created.

    Earlier I was testing the just doctrine 2.0BETA3 with sandbox. With this lines after require_once works great
    use Doctrine\ORM\EntityManager,
    Doctrine\ORM\Configuration,
    Doctrine\ORM\Mapping\Driver\YamlDriver;
    but with your code I have another errors :(

    Thanks for help.

    Regards.

    • Galford says:

      Ok, I made some research over Internet and saw that what I was trying to do will never works :)

      Is it required to use command line tools to generate from yaml entities and than database etc.?

      Regards.

  5. Colin says:

    Ok I’ve tried to determine the answer but for the life of me I can’t use the Entity Manager to query the database. Could you provide some examples of retrieving data from the db?

  6. Galford says:

    Did you try to work this with oracle db?

  7. Galford says:

    I’m trying to use xml and Entities class but I have problem during creating database with orm:schema-tool:create

    xml files are in /application/doctrine/xml

    Do you know how to configure cli-config.php to see xml and Entities folder?

    Regards

    • Galford says:

      I forgot to add. In sandbox its working fine.

      • Galford says:

        Ok i figure it out :) Sorry for bothering ;)

        Need to change in config
        $classLoader = new \Doctrine\Common\ClassLoader('Entities', __DIR__ . '/application/doctrine');
        $classLoader->register();

        and metadata driver should look like this
        $driverImpl = new \Doctrine\ORM\Mapping\Driver\XmlDriver(array(__DIR__ . '/application/doctrine/xml'));
        $config->setMetadataDriverImpl($driverImpl);

        regards

  8. Kimmo says:

    Running this on Apache + windows results in the following:

    Warning: include_once(Entities\Store.php) [function.include-once]: failed to open stream: No such file or directory in C:\Program Files (x86)\Apache Software Foundation\Apache2.2\htdocs\demoproject\library\Zend\Loader.php on line 146

    Is there some tweaking with paths that you need to do when running this on Windows?

  9. Hodonou says:

    Great!
    It’s what I need for since a month. I’ll try it and let you know.
    Best regard.

  10. Songo says:

    Hi
    Is there a way to integrate Doctrine 2 and Zend with an existing database ?!
    I mean I have the database up and running and need Doctrine to read my database schema and create the models for it.

  11. I believe you could use the generate:entities command from the doctrine command line utility

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>