Prevent wp_debug from polluting ajax within plugin

“How can I prevent unexpected output from reaching the browser/interfering with my AJAX response?”

The Problem

Something in your installation is generating “unexpected output,” that is, it’s creating content or data which should not be there if WordPress is running smoothly. This could imply a misconfigured server environment, a faulty WordPress installation, a bad database connection, an old plugin, a theme attempting to display a content when it shouldn’t, or problems in your own code – the point is any error, content, or notice generated for any reason is popping up where none should be.

The most stable WordPress environments tend to be those which generate no extraneous output or messages, even when WP_DEBUG is enabled; so the best possible course of action is to examine that unexpected output, determine what it’s source is, and addressing the issues such that the only output is a proper, clean response. Ideally, a production installation should generate no errors, warnings, or notices at all.

Over the course of development however, it can be beneficial to momentarily ignore unexpected output coming from sources you are not currently addressing, or that are perhaps beyond your control (in the case of the source being another team member’s responsibility, for instance, or a theme you must work with, but has yet to receive an expected update). And should non-critical errors arise in production, you certainly don’t want them interfering with the end-user’s experience; the best course of action would be to log them to file such that you may address them later.

The fact remains: the only true solution is to locate the individual problems creating unexpected output and solve them one-by-one. Everything else should be regarded as a temporary “hack.”

The Quick Fix

To answer your question in short, ob_clean() will indeed fail if there either is no buffer, or the buffer is empty. If you still receive unexpected output after a successful call to ob_clean(), the implication would be that your code is creating output, somehow – in the code provided, perhaps the call to exit(json_encode($return_json));, or maybe even the failed call to ob_clean() itself. Adding a check to only clean a pre-existing non-empty buffer may be the solution you’re looking for (on a side note, place the header('Content-Type: application/json'); call immediately before you output the response to prevent ob_clean() from sweeping away the header in the case of nested buffers, or any header()-related errors):

if( ob_get_length() )
  ob_clean();

header('Content-Type: application/json');
echo json_encode( $return_json );
exit;

If you still receive unexpected output at this point, json_encode() is likely choking up trying to process $return_json – double check the variable’s contents and json_encode()‘s requirements.

If the problem persists, forcing an output buffer in combination to the above may do the trick. This is more of a speculative hack than a legitimate solution, though, and would best be avoided. Try one of the following:

  • Change the value of output buffering in your php.ini configuration file (location dependendant on server environment) and restart your webserver: output_buffering on
  • Attempt to enable output buffering at the script-level. Add the following line to wp-config.php: ini_set( 'output_buffering', 'on' );
    • Failing that, a simple call to ob_start() in the same file may yield results, but comes with a risk of interfering with more complex environments and installations.
  • If PHP is running as an Apache module, you may be able to enable output buffering from the .htaccess file in the root of your WordPress installation:

<IfModule mod_php5.c>
php_value output_buffering On
</IfModule>

If none of the above works, I’m afraid I’m out of ideas!


A More Robust Hack

I’ve taken something of a complex approach to solving this potential problem while working on my own plugins, taking into account the possibility that other code beyond my control is generating unexpected output as well as my own – I must admit it’s not been thoroughly tested, but so far has served me well. There are no doubt simpler solutions, but I intend to provide ideas for a more useful approach than that suggested in the codex. I’m fairly certain my own implementation has room for a lot of improvement, as well.

PHP controls output with a stack of nested output buffers, and depending on which server and PHP extensions are installed and enabled (as well as the function of the PHP script itself), additional buffers may be sitting on top of that of PHP’s default one. Or there may not even be an output buffer (which may be the case if the PHP ini directive output_buffering is disabled and the script itself hasn’t created one).

I should note that several PHP extensions use buffers to do things like URL rewriting and output compression – my solution below should be used for troubleshooting and development purposes only, as will almost certainly prevent these mechanisms from functioning properly on your AJAX responses. Usually this effect is negligible, but it can create issues in more complex environments and sites. The hack described in the Codex runs a similar risk, although lesser as it does not completely obliterate buffers as I do.

So, when I begin debugging an AJAX handler, I first dump the contents of any nested buffers and explicitly discard each buffer from the stack, then use the uppermost buffer (or create one if it does not exist) to capture any messages generated over the course of handling the AJAX request (as well as any contained in a pre-existing top-level buffer):

if( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
  $bufferContents = array();

  // Capture nested buffer contents and discard them
  while( 1 < ob_get_level() )
    $bufferContents[] = ob_get_clean();

  // Ensure that a top-level buffer is available to capture any unexpected output
  if( ! ob_get_level() )
    ob_start();
}

The $bufferContents array can then be written to a debug log, or recorded elsewhere for reference, if desired.

After handling the request and immediately before dispatching a response, I capture the contents of my new top-level buffer, which now contains any unexpected output relevant to my AJAX handler script:

// If the output buffer contains data, get rid of it to prevent mucking up the JSON response
if( 0 < ( $bufferLength = ob_get_length() ) ) {
  $bufferContents = ob_end_clean();
}

header( "Content-Type: application/json" );
echo $ajaxResponse;
exit;

Once more, the $bufferContents variable likely contains useful information and can be handled as desired.

More specifically, I like to respond to AJAX requests with a JSON object – when debugging is enabled I append the buffer’s contents to the object such that I may inspect it in Chrome’s inspector console, as well as saving it to a file regardless of whether or not debugging is enabled. For example:

$ajaxResponse  = array(
  'status' => $status || 'error',
  'data'   => $response
);

// If the output buffer contains data, get rid of it to prevent mucking up the JSON response
if( 0 < ( $bufferLength = ob_get_length() ) ) {
  $bufferContents = ob_end_clean();

  // If debugging is enabled, pass any unexpected output to the client in the form of an additional 'phpBuffer' property
  if( WP_DEBUG ) {
    $ajaxResponse[ 'phpBuffer' ] => $bufferContents;
  }

  // Take note of the situation in the log files
  if( WP_DEBUG_LOG ) {
    $bufferLogFile  = plugin_dir_path( __FILE__ ) . 'debug.buffer.log';
    $bufferContents = date('m/d/Y h:i:s a', time()) . ':' . chr(10) . $bufferContents . chr(10) . chr(10);

    error_log( $bufferLength . ' characters of unexpected output were generated while processing an AJAX request in "' . plugin_dir_path( __FILE__ ) . __FILE__ . '". They have been recorded in "' . $bufferLogFile . '".';

    // Save the buffer contents to file.
    file_put_contents( $bufferLogFile, $bufferContents, FILE_APPEND );
  }
}

header( "Content-Type: application/json" );
echo json_encode( $ajaxResponse );
exit;