views:

65

answers:

2

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.)

+2  A: 

Even I don't know what you are trying to do, one of the best options to avoid "Cannot redeclare" is 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 kind 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 simple: "No, you cannot".

narcisradu
That sounds like a great help fixing the autoloader itself - not quite what I'm asking but maybe what I have to settle for. Thank you for now. :)
pinkgothic
The answer to your question is "No, you cannot" but I just wanted to be constructive :)
narcisradu
Yus. It's much appreciated! (P.S. sorry for the late response, I was on vacation for two weeks.)
pinkgothic
+1  A: 

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> ... }

Kendall Hopkins
*"If you have >1 class with the same name your probably doing it wrong."* Totally agreed. But sometimes libraries are on a system in a double fashion - e.g. a PEAR pack may be in the system php folder, or it might be in a folder beneath the main application folder. And you may nonetheless have to include both in your includepath for the sake of other libraries/frameworks/etc. (And trying to figure out just where the conflict is happening is incredibly taxing, the error message is not as helpful as it could be.)
pinkgothic
Re: Your 'hack': My tool has no authority over the broken code. I happen to have access to the files we're running it over, but again, I do want to release it to the public, so that's coincidental. I'd just like the tool to be able to catch the scenario to be more robust.
pinkgothic
I know by now the answer is indeed 'No', there is no way to do this. Ergo I'm just going to have to live with the issue :) But thank you very much for responding.
pinkgothic
@pinkgothic You could probably regex, or `token_get_all` to search for Class name clashes before `require` ing the file, but that boarder line insanity. It still doesn't get around the issue of having to load both classes at once (which is impossible in PHP). You might be able to use `namespace` to segment the class space, but honestly I don't know enough about `namespace` s to know if it's possible or not.
Kendall Hopkins
I don't *need* to load the classes, though; being able to load one of the two is perfectly sufficient, the other can fail. It's solely a question of reducing the amount of times the script uses a heuristic rather than Reflection. The lack of class-loading necessity is kind of why I'm asking this odd question in the first place.
pinkgothic
Since I do a `token_get_all()` anyway, I might just try to see if I can tie that around the autoloader somehow (with the prerequisite that I *cannot change the autoloader*, since that will be user-supplied)... but I suspect I can't. Still, since the answer to my main question's 'no', it's a case of 'can't hurt to try'. :)
pinkgothic