Updated: Comment #0
Problem/Motivation
Currently, we attempt to treat E_RECOVERABLE_ERROR as a fatal. However, what we do with it is set the page title to "Error" (with a function we're trying to eliminate), create a Response object, return it... and then ignore it entirely. That means the program continues with no message other than the page title mysteriously being set to "Error". This is doubleplusungood.
The most common source of that error is a class name mismatch in a type hint, usually caused by a bad/missing "use" statement. That makes finding a common typo way harder than it should be.
Proposed resolution
Convert E_RECOVERABLE_ERROR to an ErrorException object and throw that. We already have an exception handling pipeline with ExceptionController, so it will get handled the same as any other exception and the message actually displayed. However, it also lets the calling code catch the exception and handle it more intelligently if it is so inclined. Currently it cannot do that.
Remaining tasks
Decide if this is still relevant.
We really ought to clean up the entirety of errors.inc, but that's for another time.
User interface changes
None.
API changes
None.
| Comment | File | Size | Author |
|---|---|---|---|
| #24 | convert-2165589-19.patch | 3.84 KB | vlklavanya |
| #18 | convert-2165589-18.patch | 5.53 KB | hitesh-jain |
| #7 | 2165589-error-handling.patch | 6.22 KB | Crell |
Comments
Comment #1
Crell commentedAnd patch.
Comment #2
neclimdulClearly that's a valid problem. Seems like we're treating RECOVERABLE terribly. Granted its pretty akward to handle generically anyways. I'd have to look at this closer but my gut doesn't like the solution though. RECOVERABLE is its own brokenish thing and it doesn't seem like exceptions matches it correctly.
NOTE: The RFC this was based on didn't pass either. https://wiki.php.net/rfc/engine_exceptions.
Comment #3
Crell commented"Errors you can handle intelligently in context to fail gracefully", in PHP, mean exceptions. Personally I think we should be treating type mismatches as fatal, which this does. (The current code pretends to, but lies.)
Comment #4
neclimdulOk well looking at the patch I've got these observations.
This log_error references E_RECOVERABLE but it will never be reached because we're throwing an exception now.
This subtly changes PHP's behavior too because it stops code execution. Granted, code relying on being able to run after a E_RECOVERABLE happens is probably trouble but it was one of the reasons for blocking the RFC and it really is hard to expect how external libraries work.
Comment #5
neclimdulThe request is already logged with verbose so I don't think this is needed.
Comment #6
Crell commented#4.2: It changes PHP's behavior to what the code we already had was attempting to do; vis, it stops Drupal execution and shows an error page. Using an exception simply means that it actually happens now.
Comment #7
Crell commentedAttached patch addresses #4.1 and #5.
Comment #8
neclimdulBah, while I'm still not a fan, it seems you're correct. This is actually a regression in functionality.
http://drupalcode.org/project/drupal.git/blob/refs/heads/7.x:/includes/e...
Comment #9
dave reid@neclimdul: Except it's not a regression. http://drupalcode.org/project/drupal.git/blob/refs/heads/7.x:/includes/e...
Comment #10
neclimdulYeah, that's exactly the calling code triggering the exit that stops code flow.
This passes $fatal as
truecausing the code I linked to trigger exiting and stopping code flow.Comment #11
sunThanks for creating this issue! — I ran into this scenario a couple of times myself already (especially in the installer), but didn't take the time to figure out what's going on/wrong :)
I'm a bit confused: The error/exception handler handles an E_RECOVERABLE_ERROR and throws an ErrorException with code E_RECOVERABLE_ERROR — doesn't that result in infinite recursion?
I guess PHP has some built-in magic for this use-case?
Perhaps more interesting: Aren't we losing the (back)trace information, or is that retained as well?
Lastly, the example usage on http://php.net/manual/en/class.errorexception.php passes the $error_level value as the third parameter instead of the second, which appears to comply with ErrorException::__construct():
The example passes 0 as $code, and $error_level as $severity.
Actually, I wonder whether this is still necessary in D8...?
DRUPAL_BOOTSTRAP_FULL ought to be a thing of the past, no? ;)
I'd be eager to simply remove it and see what happens, but happy to defer that to a separate issue ;)
It looks like the $fatal argument is obsolete with this change?
I have a clear preference (Drupal's) and I normally wouldn't comment on this in favor of making progress, but within a single patch, it would be great to decide on one style? ;-)
Comment #12
Crell commented1) No recursion. The PHP error handler doesn't touch exceptions; there's an entirely separate callback for that. Although... if that calls _drupal_log_error(), too, that could be a problem. We'd need to generate an error pre-kernel for that to be the case, though (since once we're in the kernel exceptions will get caught and never get to the global exception handler), so I don't know how to write a test module for that.
Although... we already have a try-catch block in index.php, so at least when using index.php *the global exception handler never gets called at all*. Oh Drupal...
2) DRUPAL_BOOTSTRAP_FULL isn't gone yet, AFAIK. I wasn't sure if that's still necessary or not. I just moved it around. I didn't test to see what happens if we remove it. (I'd be more than happy to see that global-state-aware code die if possible.)
3) Nope. _drupal_log_error() is, weirdly, called from one or two other places as well. I didn't want to get into a full refactor of our entire error handling here, although we're over-due for that, certainly.
4) The first is just me missing the enter key while typing. :-) Will fix in the next draft if someone doesn't beat me to it.
Comment #13
damien tournoud commentedAll this is really messed up, and in dire need of clean-up.
Let just bite the bullet and transform all errors into exceptions, and remove all
errors.inccompletely.Comment #14
Crell commentedMy knee-jerk response is to agree, Damien, but that means every E_NOTICE becomes a probably-site-killing error. I've done that for my own projects before, but may not fly as well in Drupal. Also, some PHP native functions throw E_NOTICE or E_WARNING (rightly or wrongly), which means those become site-killing exceptions.
Comment #15
Crell commentedRelated, someone just brought this question up on FIG: https://groups.google.com/forum/#!msg/php-fig/T4mtQc6qyaE/q0Wq1leM_KAJ
Comment #16
sunThat's more or less what I've done in #1247666-5: Replace @function calls with try/catch ErrorException blocks, so if we want to go that direction, then I think this issue should be marked as duplicate.
Comment #17
jhedstromComment #18
hitesh-jain commentedRerolled the patch to latest Release.
Comment #20
Crell commentedNote that PHP 7 will do this automatically; or rather, E_RECOVERABLE_ERROR now throws an Error (which is not an exception but a sibling of it).
Comment #21
Crell commentedThat's definitely wrong. We should get a 500 error on E_RECOVERABLE_ERRORs, assuming they propagate up uncaught that far.
Comment #24
vlklavanya commentedCode has been re rolled and committed as patch referring #18 patch
Comment #25
siva_epari commentedQueuing for review
Comment #27
Aki Tendo commentedhttps://wiki.php.net/rfc/engine_exceptions_for_php7
PHP 7 now throws specific exceptions for these errors. Perhaps it would be best to write a handler for these that throws the same exception as PHP 7 is going to throw in the PHP 5.x environment, allowing further error handling and logging to work in the same manner. For the assert tools patch I already wrote code to convert runtime assertions in this manner, but it would make sense to expand the scope of this out as much as possible.
As far as I can tell all E_RECOVERABLE_ERRORs are now Type Exceptions. So perhaps that is the route to take?
Comment #28
Crell commentedMany E_RECOVERABLE_ERRORs are now type exceptions, but not all. That's just the most common. Also, many of the newly thrown things are Errors, not Exceptions, so we'd have to shim Throwable, too.
Comment #29
Aki Tendo commentedIn PHP 5.x though, was E_RECOVERABLE_ERROR used for anything but the type hints? I've been looking for awhile and haven't found an example.
Comment #41
quietone commentedThis was a bugsmash triage issue of the day. It was discussed by lendude, longwave and DanielVeza.
They agreed this issue is a task. And longwave questioned if this is still valid, read comments #27
The question to determine if this is still valid.
Comment #42
mfbYes, I would say this can be closed as outdated. This issue and all comments up thru #29 date from before php 7.0.0 was released, and by now php has converted almost all cases of E_RECOVERABLE_ERROR to throwing a Throwable - which is caught by Drupal\Core\EventSubscriber\ExceptionLoggingSubscriber or, in cases where that event subscriber is not in play, by _drupal_exception_handler().
I tested one of the few remaining E_RECOVERABLE_ERROR still in existence with this code:
$var = (bool) gmp_mul(1, 1);The error is logged ("Recoverable fatal error: Object of class GMP could not be converted to bool") and the browser gets a 500 page reading "The website encountered an unexpected error. Please try again later."
The error handler could convert this to an ErrorException, allowing the calling code to catch it, but unless someone has a good reason to do so, it seems safer to wait for php itself to figure out if/how to convert it. Since I don't see any bug/task left to solve here, and this issue was inactive for so long, I'll go ahead and close, but feel free to reopen if I missed something.