Zend Framework and Doctrine integration – autoloading of doctrine models

I’m reinvestigating Doctrine and Zend Framework for a new project.  I’d dismissed them last year as not meeting my needs, but am giving it another go this year.  I have to say I’ve got mixed impressions at best, as there seems to be little in the way of documentation with real use cases.

I would have expected there to be some explicit reference to how to set this up, but the best I can find are general examples where the Doctrine models directories are appended to the include_path in a ZF index file.  Shouldn’t there be a way to explicitly have the Doctrine subsystem react to requests for models as well?

I was expecting the Doctrine::autoload to look for the classes on the ‘models path’ set via Doctrine::setModelsDirectory() call, but it doesn’t.

Hrm… after more investigation, it seems there’s a ‘modelsAutoload’ method on the Doctrine_core which will automatically look at the models_path set via setModelsDirectory().  Almost what I need.  Except…(!)

There’s no support for multiple directories.  The standard Doctrine generation process creates a ‘BaseFoo’ object in a ‘generated’ directory below where the standard ‘Foo’ file is written.  The ‘Foo’ file subclasses the BaseFoo, but there’s no support to have it autoloaded.

In my ‘_initDoctrine()’ in Bootstrap, I’ve got

$autoloader = Zend_Loader_Autoloader::getInstance();
$autoloader->pushAutoloader(array('Doctrine','modelsAutoload'));

I know what to do here! I thought to myself. I’ll subclass Doctrine’s Core (which is what’s referenced by the ‘Doctrine’ above) and have that be the autoloader to use.  I’ll modify the modelsAutoload() method to deal with an array of $_modelsDirectory entries, and attempt to load from each of them.  Except…(!)

$_modelsDirectory is *private*.  I can’t modify it in a subclass, nor is there a get() method on the Core class to access it.  There’s a *set* method (setModelsDirectory()) but no get!  So, I’m rather forced to go through modifying the Doctrine Core to get the behaviour I want, in the simplest form.  I’ve been trying to followup on some other postings about getting autoload to work with Doctrine-generated models, but they seem incomplete (to me anyway) or suggest to modify how Doctrine generates its class names (doesn’t seem simple or straightforward to me at all).  Having Doctrine core be able to deal with an array of paths to check for models would be the simplest, especially given that Doctrine *forces* this issue on you by generating classes in different directories by default.

Crux of the code is:

Doctrine/Core.php line 1142

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if(is_array(self::$_modelsDirectory)) {
 foreach(self::$_modelsDirectory as $dir) {
  $class = $dir . DIRECTORY_SEPARATOR . str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
  if (file_exists($class)) {
   require $class;
   return true;
  }
 }
} else {
 $class = self::$_modelsDirectory . DIRECTORY_SEPARATOR . str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
 if (file_exists($class)) {
  require $class;
  return true;
 }
}

I don’t see Doctrine code on github – if it’s there I’ll fork it and create my own patched version for a pull.

So now in my code, I can say

Doctrine::setModelsDirectory(array("/app/doctrine/models/","/app/doctrine/models/generated/"));

FWIW, I can’t find the equivalent code section in Doctrine 2.0, though I suspect this might still be a design issue in 2.0 (would love to be proved wrong on that).

How have *you* dealt with autoloading of Doctrine-generated classes in ZF (or indeed, in general)?


I'm currently working on a book for web freelancers, covering everything you need to know to get started or just get better. Want to stay updated? Sign up for my mailing list to get updates when the book is ready to be released!

Web Developer Freelancing Handbook

Share and Enjoy:
  • del.icio.us
  • DZone
  • Facebook
  • Reddit
  • StumbleUpon
  • Digg
  • Simpy
  • Technorati

{ 19 comments to read ... please submit one more! }

  1. Completely off topic but: the concept of returning true after a “require” is kinda pointless. A failed require will die right there. It might better (and faster) to return include $class;

  2. yes, it is off topic, but also not my code specifically. Yes, I could have refactored it out, but I was just wrapping existing code in an array loop structure.

  3. On Topic: you can probably use ZF to load the doctrine classes and skip any doctrine autoloading functionality.

    $defaultAuto = new Zend_Loader_Autoloader_Resource(array (‘basePath’=>APPLICATION_PATH.’/default’, ‘namespace’=>’Default’));
    $defaultAuto->addResourceTypes(array(
    ‘dbtable’ => array(
    ‘namespace’ => ‘Model_DbTable’,
    ‘path’ => ‘models/DbTable’,
    ),
    ‘form’ => array(
    ‘namespace’ => ‘Form’,
    ‘path’ => ‘forms’,
    ),
    ‘model’ => array(
    ‘namespace’ => ‘Models’,
    ‘path’ => ‘models’,
    ),
    ‘Doctrine’ => array(
    ‘namespace’ => ‘Orm’,
    ‘path’ => ‘doctrine/’,
    ),
    ));

    This would require that all your doctrine generated files start with Orm_ or something.

  4. You should probably have a look at Doctrines Documentation for how you can “manipulate” the names, prefixes and directories of the generated classes.
    You can change a lot including PEAR-styled naming.

    bottom of:
    http://www.doctrine-project.org/documentation/manual/1_2/en/yaml-schema-files

  5. I still come back to 2 points.

    1. If Doctrine will, by default, generate something which requires *two* distinct paths be available to the ‘autoloader’ system, why does the core autoloader not support multiple (or at least 2) paths to be set?

    2. Why have a setter on a private property, but not a getter? If the core had a getter, or the $_modelsDirectory property was protected, I could have subclassed and made this work much easier than meddling with the Core.php file.

  6. Noooo! Don’t load Doctrine with Zend Autoloader: http://framework.zend.com/issues/browse/ZF-8629

    You can get segfaults when ZF tries to load Doctrine’s Hydrator classes.

  7. Bryan Zarzuela

    I second robo47′s thought. Just use PEAR naming conventions on your models and you’re all set. That is a more elegant approach.

  8. Hi,

    I’ve put together a post a while back about this topic. You can find it at
    http://blog.elinkmedia.net.au/2009/12/03/zf-doctrine-and-unit-tests/

    The source code’s also available on github
    http://github.com/marsbomber/zf-with-doctrine

    Hope you find it’s useful

  9. Hi, if the biggest issue is generating of base classes you could simply pass ‘generateBaseClasses’ => false on your model generation function.

  10. Thank you all for your feedback and links.

  11. Here is the code for my _initDoctrine and _initAppAutoload bootstrap functions:

    http://pastie.org/855122

    If you use those two together, you should get what you are looking for.

  12. Doctrine is quite a mess when it comes to that. Nonetheless I’m using ZFs autoloader. An you are going to patch doctrine after every update…
    http://groups.google.com/group/doctrine-dev/browse_thread/thread/c68304587c29ce33
    http://www.doctrine-project.org/jira/browse/DC-382

  13. I am also using Doctrine in my Zend Framework Application. I have an Application_Resource to deal with Doctrine Manager and a CLI script for loading yaml schema files, sql, data… I put them there:

    // based on http://ruben.savanne.be/articles/integrating-zend-framework-and-doctrine
    class Cvb_Application_Resource_Doctrine extends Zend_Application_Resource_ResourceAbstract
    {
    protected $_doctrine;
    public function init()
    {
    return $this->getDoctrine();
    }
    public function getDoctrine()
    {
    if (null === $this->_doctrine) {
    $this->getBootstrap()->getApplication()->getAutoloader()
    ->pushAutoloader(array(‘Doctrine_Core’, ‘autoload’));
    $options = $this->getOptions();
    $manager = Doctrine_Manager::getInstance();
    $manager->setAttribute(Doctrine_Core::ATTR_AUTO_ACCESSOR_OVERRIDE, true);
    $manager->setAttribute(
    Doctrine_Core::ATTR_MODEL_LOADING,
    $options['model_autoloading']
    );
    // Escape reserved words with quotes
    $manager->setAttribute(Doctrine_Core::ATTR_QUOTE_IDENTIFIER, true);

    $db = $this->getBootstrap()->bootstrap(‘db’)->db;
    $conn = Doctrine_Manager::connection($db->getConnection());
    $conn->setAttribute(Doctrine_Core::ATTR_USE_NATIVE_ENUM, true);
    $conn->setCharset(‘utf8′);
    //$conn->setOption(‘username’, null);
    //$conn->setOption(‘password’, null);

    $this->_doctrine = $manager;
    $this->_connection = $conn;
    }
    return $this->_doctrine;
    }
    }

    The CLI script (abreviated), creates models for main application and modules
    // Initialize and retrieve Resources
    $bootstrap = $application->getBootstrap();
    $manager = $bootstrap->bootstrap(‘doctrine’)->getResource(‘doctrine’);
    if ($module = $getopt->getOption(‘m’)) {
    $front = $bootstrap->bootstrap(‘frontcontroller’)->getResource(‘frontcontroller’);
    $modules = $front->getControllerDirectory();
    $namespace = ucfirst($module);
    $path = $front->getModuleDirectory($module);
    } else {
    $namespace = $application->getOption(‘appnamespace’);
    $path = APPLICATION_PATH;
    }

    //$config = $bootstrap()->getResource(‘doctrine’)->getOptions();
    $config = array(‘data_fixtures_path’ => $path . “/configs/data/fixtures”,
    ‘models_path’ => $path . “/models”,
    ‘migrations_path’ => $path . “/configs/migrations”,
    ‘sql_path’ => $path . “/configs/data/sql”,
    ‘yaml_schema_path’ => $path . “/configs/schema.yml”,
    ‘generate_models_options’ => array(
    ‘pearStyle’ => true,
    ‘generateTableClasses’ => false,
    ‘generateBaseClasses’ => true,
    ‘baseClassPrefix’ => “Base_”,
    ‘baseClassesDirectory’ => false,
    ‘classPrefixFiles’ => false,
    ‘classPrefix’ => $namespace . “_Model_”));

    // Fix for loading data successfully
    $manager->setAttribute(
    Doctrine_Core::ATTR_MODEL_LOADING,
    Doctrine_Core::MODEL_LOADING_AGGRESSIVE);

    $cli = new Doctrine_Cli($config);
    $cli->run($getopt->toArray());

    I hope this would be of any help

  14. This frustrated me as well. I opted out to set these options when generating models:

    ‘generate_models_options’ => array(
    ‘pearStyle’ => true,
    ‘generateTableClasses’ => true,
    ‘baseClassPrefix’ => ‘Base_’,
    ‘baseClassesDirectory’ => ‘base’,)
    This way model base classes are generated in base subdirectory and autoloading works like a charm :)

  15. Oops, baseClassesDirectory should be empty.

  16. Have you seen the following link? http://devzone.zend.com/article/3411-Integrating-Zend-Framework-and-Doctrine

    I tried to use it about a year or so ago to first integrate the two. Of course there have been a few changes but maybe it could help.

  17. Hadn’t seen that one – thanks.

{ 2 Pingbacks/Trackbacks }

  1. Michael Kimsal’s Blog: Zend Framework and Doctrine integration - autoloading of doctrine models | Webs Developer
  1. Zend Framework News » Blog Archive » Autoloading für Doctrine Model in einer Zend Framework Anwendung

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> <pre lang="" line="" escaped="">



0.5718469619751