doujubeng2942 2011-07-14 14:03
浏览 71
已采纳

使用PHP构建虚拟xml文件时,删除错误并避免标头已发送错误

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

  • 写回答

2条回答 默认 最新

  • dongliang1941 2011-07-14 17:33
    关注

    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.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥15 2020长安杯与连接网探
  • ¥15 关于#matlab#的问题:在模糊控制器中选出线路信息,在simulink中根据线路信息生成速度时间目标曲线(初速度为20m/s,15秒后减为0的速度时间图像)我想问线路信息是什么
  • ¥15 banner广告展示设置多少时间不怎么会消耗用户价值
  • ¥16 mybatis的代理对象无法通过@Autowired装填
  • ¥15 可见光定位matlab仿真
  • ¥15 arduino 四自由度机械臂
  • ¥15 wordpress 产品图片 GIF 没法显示
  • ¥15 求三国群英传pl国战时间的修改方法
  • ¥15 matlab代码代写,需写出详细代码,代价私
  • ¥15 ROS系统搭建请教(跨境电商用途)