3

This is my first question, so please excuse me for any mistake I make.

The issue:

Let's say I'm using a PHP CMS like WordPress, Drupal, etc.

I want to write a plugin / module to output virtual xml files, e.g. http://example.com/page.xml and to do that I use a function called send_headers (or similar) in which I send something like Content-Type: text/xml; charset=UTF-8 to visitors' browsers.

If I have PHP errors (or other things) such as undefined index somewhere in the codes, and they are outputted before I send the content-type header above, the visitors will be greeted by the cryptic Content Encoding Error error, or in some cases the header already sent error.

Of course I can clear all the bugs in my codes as well as other unexpectedly outputted things, but those CMSs have a lot of hooks, and a lot of plugins can be developed by other developers, over which I do not have any control. PHP errors and unexpected outputs can be everywhere.

My approaches:

  1. Disable error reporting using error_reporting function and / or ini_set('display_errors', 0). This approach works but with some limitations:
    • If error_reporting has already set by internal functions of those CMSs and other plugins with bugs are loaded before my plugin, the PHP errors are still shown.
    • ini_set also has the same limitation, and what's worse is that it might not be enabled.
    • If the headers have already been sent by unexpected outputs, those two functions can't help.
  2. Clean all outputs and headers using ob_end_clean() before sending any new headers in send_headers(), e.g.:

    $ob_level = @ob_get_level();
    if ($ob_level)
    while ($ob_level > 0)
    {
        $ob_level -= 1;
        @ob_end_clean();
    }
    

    This approach does have the same drawbacks, but if I add something like ob_start() to a boot file for those CMS (which can be a config file), ob_end_clean can pretty much clean up every thing that tries to mess up the virtual xml file's headers. The real issue with this method is that it might cause other unexpected behaviours.

    For example, on a server that I'm testing my plugin on (Apache/2.2.16 Ubuntu, PHP 5.3.5), ob_end_clean seems to affect even headers sent after it is called:

    $ob_level = @ob_get_level();
    if ($ob_level)
    while ($ob_level > 0)
    {
        $ob_level -= 1;
        @ob_end_clean();
    }
    // When I view the response headers, no Content-Type is set, 
    // other headers set by header() function are discarded as well. 
    // Headers set by the server are not affected however.
    header('Content-Type: text/xml; charset=UTF-8');
    

The question:

Is there a better way to build virtual xml files using PHP without worrying about PHP errors and sent headers mangling those files' headers?

Thanks for any help.

EDIT 1 - in reply to Hakre's answer:

So best would be to only install the output buffer if the request is actually for your XML. You've been a bit unspecified about which plugin this is going to be and which platform (wordpress, drupal, ...). Implementing a output buffer often needs to take care to not destruct the order of output buffers a platform and it's add-ons are already using. Naturally any plugin that wants to make use of output buffers tries to reach the top level. So will you. So it's not always easy to find the right working here.

Okay let's consider this for a WordPress plugin. I plan to put an ob_start in the file that WordPress uses to load my plugin, and since most plugins use the init action hook to initialize, I think that's the place where we can place a top-level output buffering for our plugin. What do you think?

You're already checking the output buffer level in your code. I have no clue why you put @ in front of the functions, if you're expecting those functions not to work you must check that on it's own, and not continue just as-is. So I would first of all drop the error suppression operator.

Actually those ob functions might emit a warning or similar things, and even if they don't work there's a huge chance that there's nothing to clear so my plugin can still work normally. But if they emit any output, I will just effectively mess up my own headers.

For example, I've done one plugin for wordpress that's taking care for themes that do output upon activation: Theme Napkin.

Much like WordPress's warning for unexpected outputs from plugins upon activation, right? But how about plugins that emit errors in the front end?

Both the 'friendly message' and 'output buffering' approaches should work well, but the output buffering method, which I have mentioned in my question, does cause unexpected behaviours (it seems to discard all headers set by PHP on some specific server configurations). So maybe it's best to just output a friendly message to the users, which makes it harder for them to blame something that is not actually the culprit :-).

Khang Minh
  • 33
  • 1
  • 5
  • Always get your code warning free. But what you're looking for is to disable [display_errors](http://php.net/manual/en/errorfunc.configuration.php), so warnings do no output. For the great goal you try for: Forget it. In an environment you do not fully control, you can't gain full control. That easy it is. Don't fix what is not broken. – hakre Jul 14 '11 at 14:08
  • @hakre Thank you for the advice. 'Always get your code warning free', I always try me best to do so. There are many users out there who have this kind of content encoding error, and they will most of the time think that the plugin / module is broken, which is actually untrue. I would not try to fix something if it's not broken, of course, but if there's a way to make things better, I still want to try. – Khang Minh Jul 14 '11 at 14:58
  • I can perfectly understand you. The main problem is just that if things are broken too much, there is not much you can do about. I've experienced this a hundred times in the wordpress world. To be customer friendly, this needs fixes in other add-ons and therefore getting in contact to those people who have problems in their plugins and get those fixed first. It might not sound overall practical but this can turn out very well and quick in the end. And this can be so much better than dangling with problems you normally can't properly solve on your end/code. – hakre Jul 14 '11 at 16:33
  • Which server are you using where the `header` method call does not work? As long as no headers have been sent (check with `send_headers` that method should always work). - Just Ref: http://core.trac.wordpress.org/ticket/12089 – hakre Jul 15 '11 at 09:20
  • @hakre thanks for the link. The test server is: Apache/2.2.16 Ubuntu, PHP 5.3.5. Also, if I have something like `ob_start('ob_gzhandler');` that hooked to the init action, any call to `ob_end_clean()` causes a content encoding error. – Khang Minh Jul 15 '11 at 15:37

2 Answers2

1

There is not much you can do about in the situation. But I won't say never. Other plugin developers have managed that as well. But in the end of the day: It's worth to get things properly fixed even if it's not your code that is causing this.

So it's advisable that your plugin, in case that needed pre-conditions are not met gives a useful error description why things don't work. I would do this first as a safe spot:

if (header_sent($file, $line))
{
  $message = sprintf('Premature output is preventing the XML Plugin from working properly. Output has started in %s on line %d.', $file, $line);
  echo '<div style="border: 1em solid red; background: #fff; color: #f00; margin:2em; padding: 1em;">', htmlspecialchars($message), '</div>';
  trigger_error($message);
  die();
}

This will ensure that users are getting a message that speaks to them and gives you the ability to better argue. Additionally it specifically names why things are not working as expected. Naturally the message is just something I quickly typed here, but this is the place where you can communicate with the user upfront, your stakes.

However even if you do so, it's not useful in all situations. But there is not much the "right-thing" you can do always. So this is the most basic suggestion I can give.

What can be done else? Some tricks:

Shortcut the request

If you know which request belongs to your plugin, establish a rewrite with what the plugin offers and take care of the output on your own. This prevents the full framework to load and prevents getting output by other plugins / add-ons. Most often this is the simplest you can do and it gives back full control of the output.

Output Buffering

Output buffering can prevent headers being sent. And you can use it to discard (drop, throw away) already done output. However this comes with a price.

The best thing I can imagine is to install an output buffer at the top level. As output buffers are stack-able this ensures that you have full control. Question is: Is it possible to install an output buffer at top level? And another question is: What happens with the output buffer if the request is not resulting in your XML?

While establishing a top level output buffer, it's useful to know what the default output buffer is. Normally PHP itself is configured to have an output buffer already running (see as well what is output buffering?)for performance reasons as it lowers I/O usage. So it's worth to keep the default level number to not destroy that level.

So best would be to only install the output buffer if the request is actually for your XML. You've been a bit unspecified about which plugin this is going to be and which platform (wordpress, drupal, ...). Implementing a output buffer often needs to take care to not destruct the order of output buffers a platform and it's add-ons are already using. Naturally any plugin that wants to make use of output buffers tries to reach the top level. So will you. So it's not always easy to find the right working here.

You're already checking the output buffer level in your code. I have no clue why you put @ in front of the functions, if you're expecting those functions not to work you must check that on it's own, and not continue just as-is. So I would first of all drop the error suppression operator.

Next to that keep in mind that you're destroying other plugins output buffers. That's not behaving friendly. I can understand that you need to go full power here, but keep in mind that you actually might introduce more problems that you solve with that. Other plugin authors might complain just like you're doing. So why do you want to make the same mistake as others? That's merely the price you pay with output buffers.

# installing at the toplevel
$my_default_level = ob_get_level(); # learn about already set output buffers
$my_has_buffer = ob_start(); # my output buffer, with flagging

# burning down (somewhere after)
if ($my_has_buffer)
{
  $c = ob_get_level() - $my_default_level;
  if ($c <= 0)
  {
    # someone else already cleared my buffer.
  }
  else
  {
    while($c--)
    {
      ob_end_clean();
    }
  }
}

You can encapsulate such in a class that automatically handles this on instantiation and which can keep the flags / variables in it's instance. However the concrete implementation depends much on the system you use and for which request. For example, I've done one plugin for wordpress that's taking care for themes that do output upon activation: Theme Napkin. This is not totally comparable because it works on a very specific request and in that request only on a very specific operation. However it does work with output buffering and successfully prevents sending headers too early.

Community
  • 1
  • 1
hakre
  • 193,403
  • 52
  • 435
  • 836
  • What a great answer, thank you! Too bad I don't have enough reputation to vote this one up. Please see the **Edit 1** section in my question for a reply. – Khang Minh Jul 15 '11 at 06:23
0

Two solutions or ways to go I would say. First you can test with headers_sent() if you are save to sent the headers. If not you can create a user friendlier error message. Second, if you have a request for a different doc type you should catch that in your index.php or bootstrap process and divert to avoid the whole CMS overload. I understand you want to provide this as a plugin for others to us in their Wordpress et al installations. I don't think a plugin is the right way to go. These frameworks are usually trimmed for a special purpose (Wordpress = Blog = HTML) and changing that behavior needs to be addressed early whereas plugins are hooked onto the band wagon rather late in the process. Changing the type is not just something hooked onto the blog.

Adrian World
  • 3,118
  • 2
  • 16
  • 25