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



这是我的第一个问题,请原谅我犯的任何错误。

问题:

假设我使用的是PHP CMS,如WordPress、Drupal等。

我想编写一个插件/模块来输出虚拟xml文件,例如http://example.com/page.xml,为此,我使用了一个名为send_headers(或类似函数)的函数,在该函数中,我向访问者的浏览器发送类似Content-Type: text/xml; charset=UTF-8的内容。

如果我有PHP错误(或其他事情),比如代码中的某个地方有未定义的索引,并且在我发送上面的内容类型标头之前输出了这些错误,那么访问者将收到神秘的Content Encoding Error错误,或者在某些情况下是header already sent错误。

当然,我可以清除代码中的所有错误以及其他意外输出的东西,但这些CMS有很多钩子,很多插件可以由其他开发人员开发,我对此没有任何控制权。PHP错误和意外输出随处可见。

我的方法:

  1. 使用error_reporting函数和/或ini_set('display_errors', 0)禁用错误报告。这种方法有效,但有一些局限性:
    • 如果这些CMS的内部函数已经设置了error_reporting,并且在我的插件之前加载了其他有错误的插件,那么PHP错误仍然会显示出来
    • ini_set也有同样的限制,更糟糕的是它可能无法启用
    • 如果标头已经由意外的输出发送,那么这两个函数也无济于事
  2. send_headers()中发送任何新标头之前,使用ob_end_clean()清除所有输出和标头,例如:

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

    这种方法确实有同样的缺点,但如果我为这些CMS的启动文件(可以是配置文件)添加ob_start()之类的东西,ob_end_clean几乎可以清理所有试图弄乱虚拟xml文件头的东西。这种方法的真正问题是,它可能会导致其他意想不到的行为。

    例如,在我正在测试插件的服务器上(Apache/2.2.16 Ubuntu,PHP 5.3.5),ob_end_clean似乎甚至会影响调用后发送的头:

    $ob_level=@ob_get_level();if($ob_level)while($ob_level>0){$ob_level-=1;@ob_end_clean();}//当我查看响应标头时,没有设置内容类型,//header()函数设置的其他头也将被丢弃。//但是,服务器设置的标头不受影响。标头('内容类型:text/xml;字符集=UTF-8')

问题:

有没有一种更好的方法可以使用PHP构建虚拟xml文件,而不用担心PHP错误和发送的头文件破坏了这些文件的头文件?

谢谢你的帮助。

编辑1-回复Hakre的回答:

因此,如果请求实际上是针对XML的,那么最好只安装输出缓冲区。你有点不确定这将是哪个插件和哪个平台(wordpress,drupal,…)。实现输出缓冲区通常需要注意不要破坏平台及其加载项已经使用的输出缓冲区的顺序。自然,任何想要利用输出缓冲区的插件都会尝试达到最高级别。你也是。因此,在这里找到合适的工作并不总是那么容易。

好的,让我们考虑一下WordPress插件。我计划在WordPress用来加载我的插件的文件中放入一个ob_start,由于大多数插件都使用init操作钩子进行初始化,我认为这是我们可以为插件放置顶级输出缓冲的地方。你觉得怎么样?

您已经在检查代码中的输出缓冲区级别。我不知道为什么你把@放在函数前面,如果你希望这些函数不起作用,你必须自己检查,而不是按原样继续。所以我首先要去掉错误抑制运算符。

实际上,这些ob函数可能会发出警告或类似的东西,即使它们不起作用,也很有可能没有什么需要清除的,所以我的插件仍然可以正常工作。但如果它们发出任何输出,我只会有效地弄乱我自己的头。

例如,我为wordpress做了一个插件,负责处理激活后输出的主题:主题餐巾。

很像WordPress在激活时对插件意外输出的警告,对吧?但是在前端发出错误的插件呢?

"友好消息"one_answers"输出缓冲"方法应该都能很好地工作,但我在问题中提到的输出缓冲方法确实会导致意外行为(它似乎会丢弃PHP在某些特定服务器配置上设置的所有标头)。因此,也许最好只是向用户输出一条友好的消息,这会让他们更难责怪实际上不是罪魁祸首的东西:-)。

在这种情况下你无能为力。但我不会说永远。其他插件开发人员也做到了这一点。但归根结底:即使不是你的代码造成了这种情况,也值得把事情妥善修复。

因此,如果不满足所需的先决条件,建议您的插件提供有用的错误描述,说明为什么事情不起作用。我会先做这个作为一个安全的地方:

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();
}

这将确保用户收到一条与他们对话的信息,并让你能够更好地进行争论。此外,它还特别指出了事情没有按预期进行的原因。当然,消息只是我在这里快速键入的内容,但这是一个你可以提前与用户沟通的地方,你的赌注。

然而,即使你这样做,也不是在所有情况下都有用。但是,没有多少"正确的事情"你可以一直做。所以这是我能给出的最基本的建议。

还能做什么?一些技巧:

快捷方式请求

如果你知道哪个请求属于你的插件,那么用插件提供的内容进行重写,并自己处理输出。这阻止了整个框架的加载,并阻止了其他插件/加载项的输出。大多数情况下,这是你能做的最简单的事情,它可以完全控制输出。

输出缓冲

输出缓冲可以阻止发送标头。您可以使用它来丢弃(丢弃、丢弃)已经完成的输出。然而,这是有代价的。

我能想到的最好的事情就是在顶层安装一个输出缓冲区。由于输出缓冲区是可堆叠的,这确保了您拥有完全的控制权。问题是:是否可以在顶层安装输出缓冲区?另一个问题是:如果请求没有产生XML,那么输出缓冲区会发生什么?

在建立顶级输出缓冲区时,了解默认输出缓冲区是什么很有用。通常,出于性能原因,PHP本身被配置为已经运行了输出缓冲区(请参阅什么是输出缓冲区?),因为它降低了I/O使用率。因此,保留默认级别编号以避免破坏该级别是值得的。

所以,如果请求实际上是针对XML的,那么最好只安装输出缓冲区。你有点不确定这将是哪个插件和哪个平台(wordpress,drupal,…)。实现输出缓冲区通常需要注意不要破坏平台及其加载项已经使用的输出缓冲区的顺序。自然,任何想要利用输出缓冲区的插件都会尝试达到最高级别。你也是。因此,在这里找到合适的工作并不总是那么容易。

您已经在检查代码中的输出缓冲区级别。我不知道为什么要把@放在函数前面,如果你希望这些函数不起作用,你必须自己检查,而不是按原样继续。所以我首先要去掉错误抑制运算符。

除此之外,请记住,您正在破坏其他插件的输出缓冲区。这是不友好的行为。我可以理解你需要全力以赴,但请记住,你实际上可能会引入更多的问题,从而解决这些问题。其他插件作者可能会像你一样抱怨。那么,你为什么要和别人犯同样的错误呢?这只是你用输出缓冲所付出的代价。

# 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();
}
}
}

您可以将其封装在一个类中,该类在实例化时自动处理此问题,并可以将标志/变量保留在其实例中。然而,具体的实现在很大程度上取决于您使用的系统以及针对哪个请求。例如,我为wordpress做了一个插件,负责处理激活后输出的主题:主题餐巾。这是不完全可比较的,因为它适用于非常特定的请求,并且在该请求中仅适用于非常具体的操作。然而,它确实可以处理输出缓冲,并成功地防止过早发送标头。

我认为有两种解决方案或方法。首先,如果要保存以发送标头,则可以使用headers_sent()进行测试。如果没有,你可以创建一个用户友好的错误消息。其次,如果你有一个不同文档类型的请求,你应该在index.php或引导程序中捕捉到它,并转移以避免整个CMS过载。我知道你想在其他人的Wordpress等安装中为我们提供这个插件。我不认为插件是正确的做法。这些框架通常是为了一个特殊的目的(Wordpress=Blog=HTML)而进行修剪的,并且需要尽早解决改变这种行为的问题,而插件则在这个过程的后期才被绑定到band wagon上。改变类型不仅仅是博客上的事情。

最新更新