ZF-7228: Zend_Loader will include a file multiple times (no include_once)

Description

In ZF-2923 (SVN 12769), the include_once call on line #83 was changed to include. This created some cases where a single file could be included multiple times and cause a 'Cannot redeclare class' fatal error.


// Register the ZF autoload function
require_once 'Zend/Loader/AutoLoader.php';
$loader = Zend_Loader_Autoloader::getInstance();
$loader->setFallbackAutoloader(true);
$loader->suppressNotFoundWarnings();

Zend_Locale::$compatibilityMode = false;
$localeExists = class_exists('Locale') // Should equal false, but includes Zend/Locale.php

Comments

Sorry, I just remembered to check the trunk and it looks like it has already been updated there. When will that be moved into a tag?

Ok, double sorry. I'm not used to Fisheye so I was looking at an old version. The latest trunk does not have include_once.

First, are you absolutely sure about the error and the fix?

Zend_Loader::loadClass() does a class_exists() check before it ever attempts to load a class via include(). We switched from include_once() to include() as (a) the aforementioned class_exists() check typically short circuits the call in the first place, and (b) it provides some performance gain over its _once() cousin (which must do a stat() call internally to check against its internal path cache).

Second, the case that you present indicates that your include_path is set incorrectly. if class_exists('Locale') is including Zend_Locale, that means that you have library/Zend/ on your include_path -- instead of just library/ -- which is what should be on the path.

Finally, I'd recommend against using the autoloader as a fallback autoloader. One of the key reasons it was developed was to provide a namespaced autoloader -- which helps prevent the very issues you're running against. Always, always, always prefix your classes with your vendor/personal class prefix -- it prevents naming collisions, and assists the autoloader in preventing false positive lookups.

To your first points, I can understand that you want to get the performance benefits of dropping include_once to just include, but it does add the possibility in some edge cases for including a file multiple times. The only reason we've found it is because of some practices which are probably more lazy than anything on our part. But I'll get into that after addressing the include_path.

A standard PHP include path looks something like '.:{PEAR libraries}'. Since there is a '.' in the include path, an attempt to load a class of name 'Locale' will include the Zend/Locale.php file because the Loader.php file also sits in that Zend directory. That can be solved by shifting your include_path to ensure the real Locale file gets included before the Zend locale or by removing the '.'. But that is only a fix if there is a Locale.php file elsewhere that you are trying to include.

In this particular case we are running class_exists with the string 'Locale', which doesn't exist. If class_exists returns false, we prepend a prefix and run the check again. The second check finds the proper class and we continue running. This is why the class_exists check doesn't protect us from the include call.


* This calls the autoloader with 'Zend_Locale', which includes the Zend/Locale.php file

  • This call the auto loader with 'Locale', which should return false but causes a fatal error
  • The class_exists check before the include call returns false because no file has been included that defines the class Locale
  • The include call sees the '.' in the include path and includes Zend/Locale.php because the Loader.php file is in the Zend directory
  • At that point the fatal error is thrown saying that Zend_Locale cannot be redeclared

If you are interested in keeping the benefits of the include call over include_once, could I suggest adding a setting to Zend_Loader that determines if it should use include or include_once?

I'm with Jeff Mace. I just got the same problem, we're looping over 2 different namespaces to autoload a class depending on the first namespace that it finds it in. With include_once it works perfectly, but with the current 1.9.2 and 1.9.3PL1 it just give me a blank error page without error.

Note that I only have this problem running it on Zend Platform Enterprise Edition with code acceleration and such options enabled, locally on a WAMPserver it does not occur. Also note that the Zend_Loader::loadFile() function has an option to use include or include_one, but it's not used withing the loadClass() function when you do not give it any directories.

I think all includes should be an include_once/require_once, no matter what.

Addition to my comment above:

Zend_Loader @line: 82&83:


self::_securityCheck($file);
include $file;

can be replaced by the following line:


self::loadFile($file, null, true);

I have just the same problem, Our class Auth was included as Zend_Auth and it caused problem "Cannot redeclare class". I spent 4 hours to figure out the reason. Please repair these bug. Thanks

Fixed in trunk, to release with 1.10.0.