ZF-8205: Zend_Loader_Autoloader_Resource Namespace Separator

Description

Add getter / setter for the namespace separator in the Zend_Loader_Autoloader_Resource class, this then makes it easy to switch to loading 5.3 namespaced resources.

This would help with Doctrine 2 integration as it loads 5.3 namespaced entities.

Comments

Hi,

I am attaching a prototype patch for this, this is only a very quick first implementation so see what you think. I may need to expand the tests etc...

Thx

Keith

Hi,

I tried to roll my own patch to Zend_Loader_Autoloader_Resource when I found this report and then applied the diff to avoid reinventing the proverbial wheel.

With the files successfully patched, loading namespaced classes is working as expected, although the resource loader doesn't seem to be able to load class files

For instance, in a module bootstrap file:


$moduleLoader = new Zend_Application_Module_Autoloader(
    array(
        'namespace' => 'Forum',
        'basePath' => __DIR__,
    )
);
$moduleLoader->setNamespaceSeparator('\\');
$moduleLoader->addResourceType('entity', 'models', 'Model');

$autoloader = $this->getInvokeArg('bootstrap')
                   ->getResource('modules')
                   ->offsetGet('forums')
                   ->getResource('Autoloader');

// tries to load \Forums_Model\Post, fail!
$autoloader->getModel('Post');

// tries to load \Forums\Model\Post, which is okay, however raises the error
// Class '\Forums\Model\Post' not found in /usr/local/lib/php/Zend/Loader/Autoloader/Resource.php
$autoloader->getEntity('Post');

We should find a way to make the two implementation to cooperate when PHP5.3 namespaced classes are requested together with classic namespaced classes.

When I was brainstorming for the patch a solution that popped in my mind was:


// Explicitly tells the loader to use PHP5.3 namespace
$moduleLoader->addResourceType('resorurceName', 'resourceFolder', '\Resource');

// Triggers the classic loader. Which results in a #epic fail#
$moduleLoader->addResourceTYpe('resourceName', 'resourceFolder', 'Resource');

The first call will load \Forums\Resource\ when the second will load Forums_Resource_.

The patch could do something like this:

In {{Zend_Loader_Autoloader_Resource#addResourceType}} * Check if the $namespace has the heading \ ** Remove the heading \ in order return to the classic $namespace ** Mark the $resource as namespaced


+ if ('\\' == substr($namespace, 0, 1)) {
+     $this->_namespacedResources = ...
+ }

In {{Zend_Loader_Autoloader_Resource#load}} * Before instantiating the the resource check if it's namespaced ** Alter the $class name to reflect the Fully Qualified Name.


+ if () {
+    $klass = '\\' . str_replace('_', '\\', $class);
+    $this->_resources[$class] = new $klass;
+ } else {
     $this->_resources[$class] = new $class;
+ }

With this trick the autoloader will autoload the file in the classical way because the class is underscored and the namespace is only "unveiled" when instantiating the resource. For instance:


$resourceLoader->setNamespace('MyApp_');
$resourceLoader->addResourceType('entities', 'folder/entities', '\Entity');
$resourceLoader->addResourceType('models', 'folder/models', 'Model');

// Will autoload MyApp/folder/entities/User.php and instantiates MyApp\Entity\User class
$resourceLoader->getEntity('User');

// Will autoload MyApp/folder/models/Post.php and instantiates MyApp_Model_Post class
$resourceLoader->getModel('Post');

What do you think about it?

This is a naïve implementation of my solution, it does compose the namespace in the correct class path and does tell namespaced resources apart although it cannot autoload the file and therefore instantiate the Fully Qualified Name class.

I had to hardcode a call to {{Zend_Loader_Autoloader_Resource#autoload($class)}} to load the required file and then instantiate the FQN class.

I attached the file Resource.diff, it contains all the changes I made to the {{Zend_Loader_Autoloader_Resource}} class, the test is in the previous {{ResourceTest.diff}} file. The ResourceTest has just one more test method I'm no Test-Driven so if you find this solution appropriate I suggest you to write another bunch of tests, just to be sure that the patch works with a wide range of use-cases.

Cya

As I said in my previous comments, my patch doesn't seem to be able to autoload classes when instantiated directly, for example:


// Autoload correcly the class contained in application/modules/forums/models/Post.php
$myPost = new \Forums\Model\Post();

ended in Fatal Error: class \Forums\Model\Post not found ... while loading it with the load($resource, $class) worked.

I hacked the Zend_Loader_Autoloader and found out that applying the following patch made the Autoloader autoload my classes flawlessly.


---   library/Zend/Loader/Autoloader.php    2010-01-06 03:05:09.000000000 +0100
+++   /usr/lib/php/Zend/Loader/Autoloader.php   2010-03-09 20:35:44.000000000 +0100
@@ -115,6 +115,10 @@
     {
         $self = self::getInstance();
 
+   if (false !== strpos($class, '\\')) {
+       $class = str_replace('\\', '_', $class);
+   }
+
         foreach ($self->getClassAutoloaders($class) as $autoloader) {
             if ($autoloader instanceof Zend_Loader_Autoloader_Interface) {
                 if ($autoloader->autoload($class)) {

I wouldn't rely on my previous patch to {{Zend/Loader/Autoloader/Resource.php}} as it was just a proof of concept to test resource loading.

I'd like to hear your opinions and if I'm heading the right way.

Bye

I'm pretty sure there is no need to "hack" Zend_Loader_Autoloader.

Just use your custom autoloader. Also don't forget about resource autoloader for accessing classes with "Application_Model" into directory "applicatiom/models".


class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{

    protected function _initAutoload()
    {
        $applicationAutoloader = new Zend_Application_Module_Autoloader(array(
            'namespace' => 'Application_',
            'basePath'  => dirname(__FILE__),
        ));

        $autoloader = Zend_Loader_Autoloader::getInstance();
        $autoloader->pushAutoloader(array('Bootstrap', 'modelsAutoload'), 'Application\\Model\\');
    }

    static public function modelsAutoload($className)
    {
        Zend_Loader_Autoloader::autoload(str_replace('\\', '_', $className));
    }

}