10

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

pinkgothic
  • 6,081
  • 3
  • 47
  • 72
  • What is the tool doing? Something like like http://github.com/theseer/Autoload? – Gordon Jul 14 '10 at 12:50
  • PHP's autoloading functionality should never have this problem, and if you include files using `include_once` you shouldn't either. What specifically are you doing? – deceze Jul 14 '10 at 12:52
  • @Gordon: The tool itself is using Reflection to get information on classes. It has nothing to do with autoloading, itself, it just makes use of it. @deceze: I initially used Zend's autoloader to load the files, then used an alternate method with 'just' SPL's autoload stack. Both methods cause the issue. I'm pretty sure I can probably fix this particular instance of fail if I spend more time with it, but that doesn't change that I'd like the script to be guarded against this in general, if at all possible. (More info to follow in a second comment.) – pinkgothic Jul 14 '10 at 13:10
  • My script does nothing other than use the Tokenizer, extract called functions and instantiated or typehinted classes, and aggregate information via Reflection (or guessing the info based on a heuristic). Which is to say it honestly doesn't need the classes to be loaded, or even loaded properly, but it would be nice to load as many of them properly as humanly possible and *not* abort just because one ends up being available in two places in the `include_path`. – pinkgothic Jul 14 '10 at 13:13
  • (That's what I suspect is happening. That's usually the cause for the 'cannot redeclare' errors in autoloading, anyway. That being said, as stated, I have yet to figure out a working `include_path` constellation; that's not to say one doesn't exist.) – pinkgothic Jul 14 '10 at 13:14
  • Just to emphasise: I'm not interested in getting help with the optional bootstrap file that caused the quoted problem in 'Background'. (Well, I do, but that has nothing to do with this question except as specific cause of a general problem.) What I want is the script that is given the bootstrap to be able to deal with failing autoloading, i.e. be robust in light of the general problem. It's quite possible that this is impossible, then I'll smile and nod and do more sensible things. :) But that's what I'm asking about. – pinkgothic Jul 14 '10 at 13:29

3 Answers3

8

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

Kenny Linsky
  • 1,726
  • 3
  • 17
  • 41
Narcis Radu
  • 2,519
  • 22
  • 33
  • 1
    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 Jul 14 '10 at 13:54
  • 1
    The answer to your question is "No, you cannot" but I just wanted to be constructive :) – Narcis Radu Jul 14 '10 at 14:16
  • Yus. It's much appreciated! (P.S. sorry for the late response, I was on vacation for two weeks.) – pinkgothic Aug 02 '10 at 09:28
3

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.

DudeOnRock
  • 3,685
  • 3
  • 27
  • 58
  • 1
    Hmm, interesting. I don't entirely remember the source code I was using and what the bootstrap issue turned out to be (though I could check former), but I do know that I wasn't using namespaces. I _believe_ my issue was that something my script was analysing used a `require()` when it should have used a `require_once()`, and that on a class that had already been loaded. But your observation is interesting and I'm sure it will help someone, so have an upvote. :) – pinkgothic Apr 15 '13 at 10:20
  • Thank you ;) i forgot to include the namespace. You comment has brought me to check it! – Hackbard Feb 13 '18 at 08:57
2

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
  • 43,213
  • 17
  • 66
  • 89
  • *"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 Aug 02 '10 at 09:24
  • 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 Aug 02 '10 at 09:26
  • 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 Aug 02 '10 at 09:27
  • @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 Aug 02 '10 at 19:19
  • 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 Aug 03 '10 at 10:29
  • 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 Aug 03 '10 at 10:31