Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP autoloading: Preventing 'cannot redeclare <class>' in all constellations?

Question

Is there a way I can make PHP ignore re-declarations of classes rather than barf up a FATAL ERROR? Or at least throw an exception? (I could easily catch it then and proceed (as well a log the attempted autoloading).)

I'm guessing no and a fatal error is a fatal error - after all, in ninety-nine out of a hundred cases, that's reasonably sensible behaviour - and I'll probably just have to fix instances of it being triggered on a case-by-case basis. But maybe someone smarter than me has this figured out.


If you're asking yourself "Why on earth would you want to do that?", read on.

Background

I'm working on a tool that uses Reflection to aggregate specific information about used functions and classes. One of the script's arguments is an optional bootstrap file to make Reflection more reliable with autoloading (less ReflectionExceptions that end up caught and triggering a fallback heuristic, because classes are unknown in a specific file).

Now, the bootstrapping loads the autoloader(s) fine, and the script runs as intended, moves through several hundred files without a complaint, until I hit a snag:

PHP Fatal error: Cannot redeclare class PHPUnit_Framework_Constraint in /usr/share/php/PHPUnit/Framework/Constraint.php on line 62

I have two issues:

One, I have no idea what is triggering this. I've adjusted the bootstrap that is used, but am only alternating between 'Cannot redeclare' and 'Could not open file', depending on the include path used. There is no middle ground, i.e. no point where no error occurs. Still, I'm still investigating. (This issue is not what the question is about, though.)

Two, more importantly, and leading to the subject of this question, I need a way to catch it. I've tried writing a custom error handler, but it doesn't seem to want to work for Fatal errors (somewhat sensibly, one might argue).

I intend to release this tool into the open source world at some point, and this strikes me as a quite unacceptable behaviour. I have a fallback heuristic for classes that don't exist - I'd rather they're declared once too seldom than once too often, but without over-using the heuristic, either, which is to say, I do want to offer the capability of using a bootstrapper. Without breaking the script. Ever. Even if it's the worst autoloader in the history of autoloaders.

(To stress: I don't want help with my autoloaders. That is not what this question is about.)

like image 381
pinkgothic Avatar asked Jul 14 '10 12:07

pinkgothic


3 Answers

I don't know if you are still interested in answers, but I encountered possibly the same issue you had when mixing autoloading and reflection. Here is my hypothesis as to why autoloading fails:

spl_autoload_register makes a distinction between classes it auto-loaded with a fully specified name-spaced class identifier, and classes that where loaded from inside a namespace with class identifiers that don't include the name-space. As far as I can tell, this is a bug.

My solution: I test before I create the reflection class instance, if the class identifier is fully name-spaced. If it is not, I don't use reflection. Since you don't mind some classes not loading, this might also be a solution to your problem.

like image 61
DudeOnRock Avatar answered Nov 07 '22 04:11

DudeOnRock


One of the best options to avoid Cannot redeclare is the class_exists function. You may use it in the autoloader to prevent class redeclaration. With class_exists you don't have to catch the error, you just prevent it.

There are actually two kinds of fatal errors: catchable and not catchable. Class redeclaration doesn't fire up an E_RECOVERABLE_ERROR (a catchable one) and you can't handle it.

So, the answer to your question is: "You cannot."

like image 45
Narcis Radu Avatar answered Nov 07 '22 04:11

Narcis Radu


Autoload will never automatically try to load a class that is already loaded. If you have >1 class with the same name your probably doing it wrong.

If your parsing "unsafe" code, you might want to search the file for the class name before you try to load it, but this should only be used as a last resort as it's a huge waste of CPU and probably just hiding valid bugs.

If you have a require structure in place AND an autoload system you could possibly be including a file once in autoload and then again in require. You can hack a fix by wrapping the class with if( class_exists( <class_name_string> ) { ... <class declaration in here> ... }

like image 2
Kendall Hopkins Avatar answered Nov 07 '22 02:11

Kendall Hopkins