E_NOTICE ?== E_DEBUG,避免使用更复杂的error_handler isset() 和 @

E_NOTICE ?== E_DEBUG, avoiding isset() and @ with more sophisticated error_handler

提问人:mario 提问时间:11/12/2010 最后编辑:Communitymario 更新时间:4/12/2015 访问量:1482

问:

有哪些更好的方法可以避免应用程序逻辑中出现过多的问题,并在需要时保留查看调试消息 (E_NOTICE) 的能力?isset()

首先推定:E_NOTICE不是错误,而是用词不当,实际上应该E_DEBUG。然而,虽然这对于未设置的变量是正确的(PHP仍然是一种脚本语言),但一些文件系统函数等也会抛出它们。因此,最好在E_NOTICEs的情况下进行开发

然而,并非所有的调试通知都是有用的,这就是为什么在整个应用程序逻辑中引入 isset() 和 @ 是一个常见(不幸的)PHP 习语。isset/empty 当然有很多有效的用例,但总的来说,它似乎是语法盐,实际上会阻碍调试。

这就是为什么我目前使用error_reporting书签和哑开/关开关的原因:

// javascript:(function(){document.cookie=(document.cookie.match(/error_reporting=1/)?'error_reporting=0':'error_reporting=1')})()

if (($_SERVER["REMOTE_ADDR"] == "127.0.0.1")
    and $_COOKIE["error_reporting"])
{
    error_reporting(E_ALL|E_STRICT);
}
else {/* less */}

但是,这仍然给我留下了一个问题,即一旦启用,就会有太多的通知需要搜索。作为解决方法,我可以使用@错误抑制运算符。与 isset() 不同,它不会完全终止调试选项,因为自定义错误处理程序仍然可以接收抑制的E_NOTICEs。因此,将预期的调试通知与潜在问题分开可能会有所帮助。

然而,这同样不能令人满意。因此问题来了。有没有人使用或知道更复杂的PHP错误处理程序。我正在想象一些事情:

  • 输出未经过滤的错误/警告/通知(使用 CSS 绝对定位?
  • 和 AJAX-whatnot 允许客户端检查和抑制
  • 但还可以保存预期和“批准通知或警告的过滤列表。

当然,某些框架必须已经有这样的用户错误处理程序。

  • 基本上我对警告/通知管理感兴趣。
  • 完全E_NOTICE压制是真的不可取的。
  • E_NOTICES通缉。只是少了。默认情况下,突出显示我可能关心的那些,而不是预期的。
  • 如果我在没有 ?order= 参数的情况下运行,则会出现预期的 NOTICE。由于意料之中,我不需要多次通知。
  • 但是,在完全调试模式下,我确实希望通过存在(或更有趣的是不存在)所述调试通知来查看未定义变量的存在。-> 这就是我认为他们的目的。避免 isset 会带来语言隐式 print 语句。
  • 还要注意,这是关于普通PHP表单处理语义的用例,而不是必须严格性的应用领域。

哦,天哪,有人请帮忙重写这个。冗长的解释失败。

PHP 错误报告

评论

12赞 ircmaxell 11/12/2010
就我个人而言,我觉得你的假设是错误的。我的立场是,它们是由未定义的数组索引或变量用法抛出的。如果变量未定义,则访问该变量是错误的。是的,很多人只是关闭通知,从不使用 .这是错的吗?哈哈但是你确实会丢失很多有效的未定义变量错误(例如变量拼写错误)......因此,虽然它不是“错误的”,但我认为不使用它(并开发无通知的代码)是懒惰的,也不是一个很好的做法(甚至接近于“坏做法”)......issetisset
0赞 mario 11/12/2010
@ircmaxell 我的看法是,这真的取决于。从逻辑上讲,NULL 或 undef 变量可能会非常例外地咬你的屁股。对于大多数PHP代码,处理表单输入等,接受PHP的动态语义似乎并不那么牵强,因为未定义的变量不是错误。同样,这要视情况而定。不惜一切代价避免被淘汰。此外,我确实希望保留通知,基本上是 NULL 断言。只是少了。
2赞 ircmaxell 11/12/2010
这很公平。但是,如果你走了那么远,你为什么还要访问原始表单输入呢?你为什么不使用一个可以为你处理验证、清理等的库呢?而不是。。。这样你就不会到处都是,你实际上使它更具可读性。虽然我同意“接受PHP动态并不牵强......”,但我会争论这样做是否好。预定义变量并检查是否存在不仅使其更具可读性,而且还阐明了您的意图(这使得调试更容易)。$foo = $_GET['bar']$foo = $request->get('bar');isset()
2赞 ircmaxell 11/12/2010
我可以看到(断言)......另外,您可以对可选变量采用默认方法:...这样一来,粗略的一瞥就很清楚您可能期望什么(而不是必须通读整个方法才能查看您使用的密钥)......再说一次,这只是我的立场......$array = $array + array('optional1' => '', 'optional2' => '');
1赞 ircmaxell 11/15/2010
@mario:我确实在生产中使用它们。但是我有一个自定义错误和异常处理程序,它记录调试信息并通过电子邮件将副本发送到我的问题跟踪系统。这样,如果发生任何错误,我会立即收到通知,以便我可以开始调试。毕竟,你怎么能调试你不知道存在的东西(并且没有像样的回溯/环境信息)。

答:

1赞 Cfreak 11/12/2010 #1

在我看来,避免的最好方法是在使用变量之前定义变量。我不喜欢它不是因为它丑陋,而是因为它促进了糟糕的编程实践。isset()isset()

至于错误处理本身,我把所有这些信息都放到了服务器日志中。我还在命令行上使用语法检查程序。默认情况下,我为用户制作了漂亮的消息。php -l

您可以查看各种框架之一,看看其中是否有任何适合您。我看过的大多数都有错误处理例程,使事情变得比 PHP 开箱即用的更容易。

编辑:@mario - 我对你的评论的回应太长了:-)。我不主张定义类型或采用某种严格的格式,如Java或C。我只是主张在使用它的上下文中声明变量。( 与将变量留空不同)。$foo = null;

我认为这在很多情况下更像是全局变量的问题,尤其是用于获取 GET 和 POST 数据的超级全局变量。我真的希望PHP能够放弃超级全局变量,转而使用用于获取输入数据的类。像这样的东西(超级简单,但嘿,你想要一些具体的东西::))

<?php
class PostData {
     private $data;

     public function __construct() {
          $this->data = $_POST;
          unset($_POST);
     }

     public function param($name, $value = null) {
          if( $value !== null ) {
               $this->data[$name] = $value;
          }

          if( isset( $this->data[$name] ) ) {
               return $this->data[$name];
          }
          return null;
      }
}
?>  

包含类,然后可以从该方法获取和设置 POST 数据。这也是将验证合并到输入数据中的好方法。作为奖励,无需检查所有内容(已经是)。param()isset()

评论

3赞 ircmaxell 11/12/2010
你能详细说明一下我不喜欢 isset() 不是因为它丑陋,而是因为它促进了糟糕的编程实践吗? ?我很好奇它促进了什么不良做法......
0赞 Cfreak 11/12/2010
因为它鼓励人们即时声明变量。声明变量(即使是弱类型语言)可以使代码在 6 个月后返回项目时对其他人和您自己更易于维护。它曾经受到鼓励,显然是老派在这里被改装了。
1赞 mario 11/13/2010
点赞(返回 0)。最后一个音符符合这个问题。然而,它太具体了,因为我正在寻找一个线索或过滤错误处理程序的特定实现。(当然,某些框架必须有一个。——但我也不同意第一点。IMO 你不应该尝试将 Java 或 C 语义弯曲到 PHP 上。显式声明可以使代码更可靠,但PHP或Perl在大多数情况下可以很好地处理NULL或undef,因此它是多余的。E_“注意”应按字面意思理解,而不是美化为错误。(但这只是我的看法;)
1赞 ircmaxell 11/15/2010
@Cfreak:我不同意这个前提。 不鼓励人们即时声明变量。当然,它为人们提供了一种这样做的机制,但我从未见过它在这种情况下使用。我同意,即使只是从可读性的角度来看,预先声明变量也是要走的路。但我真的不认为应该因为推广不良做法而受到指责(特别是考虑到你所描述的大多数不良做法的例子,我所看到的甚至没有使用,因此会发出通知。限制使用有用的工具,因为它可能被滥用是没有意义的。isset()isset()isset
0赞 Cfreak 11/16/2010
@ircmaxwell - 我在哪里说过它应该受到限制?我说我不喜欢它,我给出了一个理由。这是我的看法。如果你愿意,可以随意不同意,但不要把话放在我嘴里。
2赞 ajreal 11/12/2010 #2

尝试 - http://www.xdebug.org/docs/stack_tracexdebug

大量检查不会伤害你,isset

事实上,它鼓励在使用之前声明变量

2赞 PazsitZ 11/15/2010 #3

我认为遵循最佳实践不是浪费时间。没错,通知不是错误,但通过正确的变量声明和验证,您的代码可以更具可读性和安全性。 但是,编写一个用户定义的错误处理程序debug_backtrace用正则表达式对 E_NOTICE(8) 进行排序并不是那么复杂。

9赞 staticsan 11/15/2010 #4

可以开发一个从不发出任何E_NOTICEs的大型 PHP 应用程序。您所要做的就是避免所有可以发出通知的情况,其中绝大多数是未初始化的变量和不存在的数组键。不幸的是,这与您避免的愿望相冲突 - 并且扩展 - 因为它们是为处理该确切问题而设计的。isset()array_key_exists()

充其量,您可以通过仔细构建框架来最大程度地减少它们的使用。这通常意味着(例如)一个输入层,该输入层被告知要期望哪些变量以及默认缺少哪些变量。这样,特定于页面的代码将始终具有要查看的值。一般来说,这是一种有价值的技术,可以应用于各种 API。但我质疑这是否应该是一个高优先级的设计目标。GET

与其他一些语言不同,PHP 区分不存在的变量和包含通常“空”值(通常)的变量。它可能是早期版本的设计工件,但它仍然存在,所以你无法真正避免它。null

评论

0赞 mario 11/15/2010
我必须投赞成票,不同意。:)并且需要更新我的问题;因为我不想回避和压制所有E_NOTICEs。事实上,我相信它们是必不可少的。我只是不喜欢一直以来的部分。 -- 当我想调试(=>打开E_NOTICEs)时,我希望被告知未设置的变量。如果到处都有 isset 装饰器,我不会得到任何装饰器。我的愿望是按预期和期望对E_NOTICEs进行分类,以便通过打开调试/e_notice模式来突出显示可能的问题。
4赞 staticsan 11/15/2010
啊。。。我可能是错的,但我想我明白你要去哪里。如果我是对的,你正在尝试使用一种在PHP中不起作用的开发技术,这就是你遇到麻烦的原因。IME,更好的方法是一直保持打开状态,并在找到通知的位置和位置修复通知。是的,这意味着要进行更具防御性的编码,是的,这意味着在依赖可能不存在的变量之前,可能会有一系列调用(但您可以检查一次并设置默认值)。换句话说,这意味着你在开发过程中总是在寻找不存在的变量。E_NOTICEisset()
0赞 enobrev 11/18/2010
@mario,php 的断言会有帮助吗?us2.php.net/manual/en/function.assert.php .否则,我完全同意staticscan(回答和评论)。
0赞 mario 11/18/2010
@enobrev这不是正确的答案。我意识到我是在反对常见的E_NOTICE清洁模因。但是,添加句法警告抑制会适得其反。isset
0赞 staticsan 11/19/2010
这是你的问题:你看到的是一种抑制语法警告的方法。虽然这是程序员经常发现它的方式,但 IMO 这不是它的用途。相反,您应该将其功能视为算法中的一个组成部分。(FWIW,在我自己的代码中,它只用在不到 1% 的行中。isset()
2赞 Sean Thayne 11/16/2010 #5

我也有类似的愿望。因此,我开始使用自定义错误处理程序。

http://php.net/manual/en/function.set-error-handler.php

然后,您可以创建自己的过滤器/机制来显示/记录错误/通知。

干杯!

6赞 István Ujj-Mészáros 11/19/2010 #6

我只使用 for 和变量,其中数据来自我的应用程序控制之外。当我没有时间编写适当的 OOP 解决方案来避免它时,我正在其他一些情况下使用它,但我相信它可以在大多数地方(如果不是所有地方)避免。例如,最好使用类而不是关联数组,这样你就不需要检查数组键的存在。isset()$_GET$_SERVER

我的建议是:

  • 避免使用 @ 运算符
  • 使用 Xdebug。首先,它打印有关每个通知/警告的易于阅读和易于注意的消息,并在异常时打印非常有用的堆栈跟踪(您可以将其配置为打印出每个方法参数和每个局部变量(和配置参数)。其次,它可以禁用带有配置值的运算符。还可以使用 Xdebug 进行分析和代码覆盖率分析。这是开发机器上的必备工具。xdebug.collect_params=4xdebug.show_local_vars=on@xdebug.scream=1
  • 对于调试,我也在使用 FirePHP,因为它可以与 Firebug 一起使用,并且能够将消息打印到 Firebug 控制台,因此它也可以用于 AJAX 调试
  • 使用自定义错误处理程序,您可以捕获和过滤任何错误和警告,并且可以将它们记录到文件中或使用 FirePHP 显示它们,或者您可以使用例如 jGrowlGritter 在网页上很好地显示它们。

我正在使用PHP手册中示例的修改版本:

<?php
//error_reporting(0);
set_error_handler("errorHandler");

function errorHandler($errno, $errstr, $errfile, $errline)
{
    echo "errorHandler()<br />\n";

    // filter out getImageSize() function with non existent files (because I'am avoiding using file_exists(), which is a costly operation)
    if ( mb_stripos($errstr, 'getimagesize') !== false )
        return true;

    // filter out filesize() function with non existent files
    if ( mb_stripos($errstr, 'filesize') !== false )
        return true;

    // consoleWriter is my class which sends the messages with FirePHP
    if (class_exists('consoleWriter'))
        consoleWriter::debug(array('errno'=>$errno, 'errstr'=>$errstr, 'errfile'=>$errfile, 'errline'=>$errline, 'trace'=>debug_backtrace()), "errorHandler");

    switch ($errno) {
    case E_USER_ERROR:
        $out .= "<b>FATAL_ERROR</b> <i>$errno</i> $errstr<br />\n";
        $out .= "Fatal error on line $errline in file $errfile";
        echo "</script>$out";   // if we were in a script tag, then the print is not visible without this
        //writeErrorLog($out);

        echo "<pre>";
        var_export(debug_backtrace());
        echo "</pre>";

        exit(1);
        break;

    case E_USER_WARNING:
        $out .= "<b>WARNING</b> <i>$errno</i> $errstr<br />\n";
        $out .= "On line $errline in file $errfile<br />\n";
        break;

    case E_USER_NOTICE:
        $out .= "<b>NOTICE</b> <i>$errno</i> $errstr<br />\n";
        $out .= "On line $errline in file $errfile<br />\n";
        break;

    default:
        $out .= "<b>Unknown</b> <i>$errno</i> $errstr<br />\n";
        $out .= "On line $errline in file $errfile<br />\n";
        break;
    }

    if (!class_exists('consoleWriter'))
        echo $out;

    //writeErrorLog($out);
    //addJGrowlMessage($out);

    // Don't execute PHP internal error handler
    return true;
}

function testNotice($a)
{
    echo $a;
}
testNotice();

另一个建议是不要在仅限 php 的文件末尾使用结束 ?> 标记,因为它可能会导致默认禁用输出缓冲的配置出现错误。headers already sent

评论

0赞 mario 11/19/2010
+1 有趣的点。虽然不完全合理,但@操作员的帖子是一个很好的概述。(但是我不同意省略,这更像是一个新手编码风格的推荐。上述错误是由草率的编码引起的,主要是在 Windows 上,并且是由于文本编辑器较弱。?>
1赞 István Ujj-Mészáros 11/19/2010
@mario 有许多关于避免使用结束标记的建议。例如,有 Zend 框架编码标准,还有一篇 Sitepoint 文章,以及其他一篇不错的文章。有时错误来自外部(我遇到过一次)。
0赞 staticsan 11/19/2010
关于不要使用太多的好总结。就我个人而言,只有在没有理由(是我最喜欢的罪魁祸首)的情况下,我才使用它,并且在不介意的代码中,我没有时间修复,但必须继续运行。@fopen()
0赞 mario 11/22/2010
赏金在这里。虽然我在几点上不同意,但就我最初的追求而言,这个答案是最不合时宜的;更重要的是,这听起来像是合理的建议。
1赞 mario 11/22/2010 #7

现在这个答案有点过时了,但我当时最初使用的是灵活的日志调度器,https://github.com/grosser/errorhandler(不完全是我想要的 IIRC,但至少比在完全和部分抑制之间交替更复杂一些。

无论如何,我同时在最常见的情况下使用 $_GET->int[“input”] 包装器。这只是一个微不足道的 ArrayAccess 包装器,隐式捕获不存在的变量,从而可以更轻松地恢复通知。(只是一个副产品。不过,主要是为了立即过滤。

对于另一个项目,我什至使用 preproccessor 宏 ,以允许根据构建参数启用/禁用或日志重定向。IFSET@($var)

2赞 Mike 4/7/2011 #8

PHP肯定是被打破了,这使得代码的可读性降低。“null”的意思是“未定义”——很简单。

当我遇到这个问题使代码不可读时,我会这样做:

/**
 * Safely index a possibly incomplete array without a php "undefined index" warning.
 * @param <type> $array
 * @param <type> $index
 * @return <type> null, or the value in the index (possibly null)
 */
function safeindex($array, $index) {
  if (!is_array($array)) return null;
  return (isset($array[$index])) ? $array[$index] : null;
}

// this might generate a warning
$configMenus = $config['menus'];  

// WTF are you talking about!!  16 punctuation marks!!!
$configMenus = (isset($config['menus']) ? $config['menus'] : null;

// First-time awkward, but readible
$configMenus = safeindex($config, 'menus');

在这里交叉发布这个答案。这对垃圾邮件检查器有帮助吗?

评论

0赞 Simon East 7/18/2011
+1 为迈克的伟大建议。我真的很喜欢它......特别是因为您可以根据具体情况使用此方法,而不是设置涵盖整个脚本部分的 error_reporting() 或 set_error_handler()。它仅适用于数组索引,但这些是我正在争取的大多数(通常从 _POST 美元到 _SESSION 美元不等)。
3赞 Tom Auger 12/19/2014 #9

好吧,如果你等待 PHP 7,你将可以访问 null coalesce 三元运算符,除了拥有现存最酷的运算符名称(我将我的下一个孩子命名为“Null Coalesce”)之外,它还允许您这样做:

$var = $some_array[$some_value] ?? "default value";

它取代了无处不在(和丑陋的)

$var = isset( $some_array[$some_value] ) ? $some_array[$some_value] : "default_value";

评论

2赞 mario 12/19/2014
(根据当前的邮件列表思考,甚至可能有一个 5.7 向后移植一些东西。[由于所有的弃用,PHP7 部署不太可能在十年内普及。