views:

74

answers:

3

I would like to be able to discard a partially rendered page and show an error page in PHP.

I already know about set_error_handler(), but it can only trap certain types of errors. I would like to know how to show an error page when an error type which can't be trapped by set_error_handler() is raised.

Unfortunately, it seems that the following code, when run with PHP 5.3.2 on Apache 2.2, doesn't do what I would expect it to do:

<?php

// Start the output buffer
ob_start();

// Output something into the buffer.
// I only want this to be displayed if I call one of the 
// ob_flush functions or echo the buffer myself later.
echo "yep";

// Call a function I know not to exist in order to raise 
// an error which cannot be trapped by set_error_handler() 
// and would, if display_errors was On, output "Fatal 
// error: Call to undefined function fwee()..." 
function_which_does_not_exist();

// This will never be executed.
$out = ob_get_clean();

The output of the script is:

yep

Whereas I would expect it to output nothing (or spew error info and only error info if display_errors() is on).

I have confirmed using LiveHTTPHeaders that PHP 5.3.2 does send a 500 error to the browser when display_errors is off (and a 200 when it's on) using the version of apache supplied by MacPorts, but it only ever spits 200s when using PHP 5.3.1 on XAMPP.

I tried setting ErrorDocument 500 "test" in the apache configuration (confirmed to be working by doing the same for 404) but PHP never shows the custom error, even when the entire contents of the script is just header('HTTP/1.1 500 Internal Server Error');

I'm not sure what else to do to make sure a partially rendered page is replaced with a simple error.

I can also confirm that this happens in the Yii framework. If I edit the view for the "about" page in the blog demo to have a line which reads <?php echo function_which_does_not_exist() ?>, I get a partially rendered page.

A: 

I think the only right way to do this is by using correct output buffering, than you don't have to rely on specific webserver or browser behaviour.

Best you'd use a MVC framework to handle this for you. All output is buffered until all systems are go, so when an error occurs you can take another route, clear the current buffer and display some nice error message.

You can also use the ob_*() family of functions.

  • You have to call ob_start() as the very first thing in your script (well, before any output is generated)
  • Install an error_handler to fetch errors
  • When an error occured, clean the buffer and re-route your app logic to display some nice userfriendly error message
Dennis Haarbrink
The code example in my question uses output buffering. How else could I use output buffering to prevent this from happening? It should also be noted that the Yii framework also suffers from this very problem - open up the Yii blog demo, shove <?php echo fwee() ?> somewhere in one of the templates, and you'll see that you get a partial render regardless of the state of output buffering.
Shabbyrobe
See my updated answer.
Dennis Haarbrink
This answer does not take into account the content of the question. I have updated the question to attempt to make it clearer.
Shabbyrobe
It seems that what you want to do is quite impossible. When an undefined function is called a fatal error is raised, which is non recoverable, immediately halting the script's execution. There is nothing to prevent that from happening.
Dennis Haarbrink
+1  A: 

You could pass ob_start the name of a callback function, that is executed before the output is flushed on ob_get_clean().

This callback function seams to be executed even if an error occured on the page.

This way you could do something like this:

<?php
$endReached = 0;

function outpu_cb($buffer) {
  global $endReached;

  if ($endReached) return $buffer;
  else return 'Your error message';
}

// Start the output buffer
ob_start('outpu_cb');

// Output something into the buffer.
// I only want this to be displayed if I call one of the
// ob_flush functions or echo the buffer myself later.
echo "yep";

// Call a function I know not to exist in order to raise
// an error which cannot be trapped by set_error_handler()
// and would, if display_errors was On, output "Fatal
// error: Call to undefined function fwee()..."
function_which_does_not_exist();

// This will never be executed.
$endReached = 1;
echo ob_get_clean();
?>
JochenJung
This is exactly what I was looking for. Wonderful. Thank you very much. I think it's really bizarre that PHP doesn't support this kind of thing through a more intuitive means.
Shabbyrobe
A: 

If your talking about E_FATAL or other such errors yes you can catch them with a custom error handler using set_error_handler().

All you need to add is a shutdown function.

// Set the error handler
set_error_handler(array('error', 'handler'));

// Catch E_FATAL errors too!
register_shutdown_function(array('error', 'catch_fatal'));

// Set the exception handler
set_exception_handler(array('error', 'exception'));

// Manually return a new exception 
function catch_fatal()
{
    if($e=error_get_last())Error::exception(new ErrorException($e['message'],$e['type'],0,$e['file'],$e['line']));
}

Take a look at http://micromvc.com or http://kohanaphp.com/ to see how it's done.

Xeoncross
Can you please update your example so it works by itself?
Shabbyrobe
I tried to make this work for me again, but it still flushes the output buffers before running register_shutdown_function. In the following example, "squeak" is still printed. As per the question, this is not desirable. <?phpset_error_handler('error_handler');register_shutdown_function('catch_fatal');function catch_fatal(){echo \_\_FUNCTION\_\_.PHP_EOL;}function error_handler($error){echo \_\_FUNCTION\_\_.PHP_EOL;}ob_start();echo "squeak";quack();
Shabbyrobe