处理 php 页面上的错误的最佳方法?

Posted

技术标签:

【中文标题】处理 php 页面上的错误的最佳方法?【英文标题】:Best way to handle errors on a php page? 【发布时间】:2012-08-07 07:26:34 【问题描述】:

现在我的页面看起来像这样:

if($_GET['something'] == 'somevalue')

    $output .= 'somecode';

    // make a DB query, fetch a row
    //...
    $row = $stmt->Fetch(PDO::ASSOC);

    if($row != null)
    
        $output .= 'morecode';

        if(somethingIsOK())
        
            $output .= 'yet more page output';
        
        else
        
            $error = 'something is most definitely not OK.';
        
    
    else
    
        $error = 'the row does not exist.';
    

else

    $error = 'something is not a valid value';


if($error == '') // no error

    //display $output on page

else // an error

    // display whatever error occurred on the page

我做事的方式是可行的,但是对于可能显而易见的事情来说它非常麻烦和乏味:假设我在代码中间的某个地方调用了一个函数,或者想要检查变量的值,或者验证一个数据库查询返回了一个有效的结果,如果它失败了我想输出一个错误?我将不得不制作另一个 if/else 块并将所有代码移动到新的 if 块中。这似乎不是一种聪明的做事方式。

我一直在阅读有关 try/catch 的内容,并一直在考虑将我的所有代码放在 try 语句中,然后让代码在没有任何 if/else 块的情况下按顺序运行,如果出现问题,则抛出异常。根据我的阅读,这将停止执行并使其直接跳转到 catch 块(就像失败的 if 语句将转到 else 块一样),然后我可以在那里输出错误消息。但这是可接受的或标准的做法吗?

在构建和输出 html 页面的 php 应用程序中,处理错误(致命与否)的最佳方法是什么?我不想死在一个空白屏幕上,因为这对用户非常不友好,而是想在页面正文中输出一条消息,仍然允许页眉和页脚显示。

感谢您的建议!

【问题讨论】:

我自己使用exit($error),例如:if($some_error == TRUE)exit('Error.');希望这不是一个坏习惯。 @AzizAG - 不错的主意,但我的页面上有一个包含页眉和页脚的包装器,如果出现错误,我希望它以令人愉悦的图形方式显示。 (即,我不想死在带有错误消息的空白白页上)。 @Nate 我撒谎了,我从不直接使用exit。我有一个名为exitApp($error) 的函数:(1)显示标题(2)回显$error(3)显示页脚(4)最后exit应用程序。 @AzizAG 这就是未捕获异常的作用,因此您可能只是切换到异常。请参阅我的回答,它向您展示了如何启用 PDO 来引发异常以及如何将所有 PHP 错误转换为异常(PHP 有专门用于此目的的 ErrorException 类)。 【参考方案1】:

有很多方法可以解决这个问题,坦率地说,没有一种方法本质上是“正确的”。

您必须自己决定哪种方法对您来说更“舒适”——它始终取决于您的偏好(尽管您应该避免某些技术,并且有充分的理由)。

这在很大程度上取决于您如何拆分逻辑,但是我倾向于将所有可以返回非致命错误的代码包含在函数中,并使用所述函数的返回值来指示存在错误。

对于fatal errors,我倾向于使用异常(带有try-catch 块)。

现在要明确一点:

非致命错误是您可以恢复的错误 - 这意味着即使出现问题,仍然有一些代码可以执行并生成一些有价值的输出。例如,如果您想使用NTP 协议获取当前时间,但服务器没有响应,您可以决定使用本地time 函数,仍然向用户显示一些有价值的数据。 致命错误是您无法恢复的错误 - 这意味着发生了非常糟糕的事情,您唯一能做的就是告诉您的用户该页面无法正常工作被要求做。例如,如果您从数据库中获取一些数据并得到 SQL Exception - 没有有价值的数据可以显示,您只能将此通知用户。

非致命错误(使用函数返回)

使用函数返回来处理非致命问题的一个很好的例子是一个函数试图在页面上显示某些文件的内容当这不是页面的主要目标时(例如,您将有一个在每一页上显示从文本文件中获取的徽章的功能 - 我知道这有点牵强,但请耐心等待)。

function getBadge($file)
    $f = fopen($file,'r');
    if(!$f)
        return null;
    
    .. do some processing ..
    return $badges;


$badges = getBadges('badges.txt');
if(!$badges)
    echo "Cannot display badges.";
 else 
    echo $badges;

.. carry on doing whatever page should be doing ..

其实fopen这个函数本身就是一个例子——it will return。

成功时返回文件指针资源,错误时返回 FALSE。


致命错误(使用异常 - try-catch)

当您有一些代码需要执行,因为它正是用户想要的(例如从数据库中读取所有新闻并将它们显示给用户),您可以使用异常。让我们举一个简单的例子——一个用户访问了他的个人资料并想查看他收到的所有消息(我们现在假设它们以纯文本形式存储)。你可能有这样的功能:

function getMessages($user)
    $messages = array();
    $f = fopen("messages_$user.txt","r");
    if(!$f)
        throw new Exception("Could not read messages!");
    
    ... do some processing ...
    return $messages;

并像这样使用它:

try
    ..do some stuff..
    $messages = getMessages($_SESSION['user'])); //assuming you store username in $_SESSION
    foreach($messages as $msg)
        echo $msg."<br/>";
    
 catch(Exception $e)
    echo "Sorry, there was an error: ".$e->getMessage();

如果您有一个可以执行所有其他代码的“***”脚本,现在这可能会派上用场。这意味着,例如,在您的 index.php 中,您只需:

try
    .. execute some code, perform some functions ..
 catch(Exception $e)
    echo "Sorry, there was an error: ".$e->getMessage();

不要过度使用异常!

无论你做什么,都不要使用异常来检查你可以从中恢复的东西。阅读on another question(完全归功于Anton Gogolev,以获得对此以及其他回答者的非常好的解释)了解为什么会这样。

进一步阅读

现在,要学习如何处理错误,最好的方法莫过于尝试几件事,看看什么对你有好处。您可能会发现以下内容很有用:

W3School on PHP Exception handling Short tutorial on error handling(类似于我的函数返回方法) Extensive tutorial on PHP error handling - 包括使用 trigger_error() 函数,我没有提到它,因为我没有使用它,也不太了解它,但显然它真的很有用。 这是一本特别好的读物。

希望这会有所帮助:)

【讨论】:

顺便说一句,我猜你的意思是badges.txt 不是bages.txt @ShaquinTrifonoff:是的,确实是一个错字:) 感谢您的编辑! :) 同意大部分,除了警告过度使用异常。例如,Java 在任何地方都使用它们。 Python 使用了一个 StopIteration 异常,这应该会严重激怒反异常人群。 :) @Pestilence 我喜欢 Java 的原因之一。遗憾的是,我不认为 PHP 被设计为在非异常情况下使用异常,或者作为正常的流控制。尝试调试使用异常作为正常流控制的长 PHP 代码——这很痛苦。性能也会受到影响。 @norfavrell 你真的以较慢的 PHP 性能 作为避免异常的理由吗? :) 哈哈。我同意调试很痛苦,但我很少调试,因为我是 PHPUnit 的忠实粉丝。我认为 OP 不是在谈论正常的控制流程,只是在谈论错误。尽管如此,即使是异常也会出错:JDBC:曾经为快速阅读编写过正确的三重嵌套 try-catch-finally 构造吗??【参考方案2】:

PHP 有一个内置类ErrorException,用于将PHP errors 转换为异常,如果不处理,自然会停止执行。

异常改进了错误处理机制(try catch)和更好的调试信息(堆栈跟踪)。

将其包含在执行路径的最顶部(配置,或所有代码中首先包含的内容):

 set_error_handler(function($nNumber, $strMessage, $strFilePath, $nLineNumber)
      throw new \ErrorException($strMessage, 0, $nNumber, $strFilePath, $nLineNumber);
 , /*E_ALL*/ -1);

虽然PDO支持抛出异常,但默认是关闭的,需要开启:

 $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);

如果使用 mysql,您还希望出现未设置必填字段的错误以及默认情况下原谅的许多其他错误/警告:

 $pdo->exec("SET sql_mode = 'STRICT_ALL_TABLES'");

可以像在许多其他编程语言中一样使用try catch finally 处理异常:

try

    echo $iAmAnUndefinedVariable;

catch(\Throwable $exception)

    /*[...]*/

验证内容时,只需抛出异常:throw new Exception("Missing URL variable userId!");

如果 PHP 有朝一日彻底摆脱旧的 error reporting 事物并默认抛出异常(弃用 error_reporting() 并更改默认值),那就太好了。

【讨论】:

【参考方案3】:

这更加优雅和可读。

try


    if($_GET['something'] != 'somevalue') 
    
        throw new Exception ('something is not a valid value');
    


    $output .= 'somecode';

    // make a DB query, fetch a row
    //...
    $row = $stmt->Fetch(PDO::ASSOC);

    if($row == null)
    
        throw new Exception ('the row does not exist.');
    


    $output .= 'morecode';


    if(somethingIsOK())
    
        $output .= 'yet more page output';
    
    else
    
        throw new Exception ('something is most definitely not OK.');
    


    echo $output;


catch (Exception $e)

    echo $e->getMessage();

【讨论】:

这是我提出问题时的设想,但我不确定是否正确使用 try/catch。【参考方案4】:

使用try-catch 是您可以使用的最干净的解决方案之一。

我做了一个例子,当错误发生时仍然显示页眉和页脚,使用你的代码转换为try-catch格式:

PHP:

<?php
try 
    $output = array();
    if($_GET['something'] != 'somevalue') throw new Exception('something does not have a valid value.');
    $output[] = 'Some Code';
    $row = mt_rand(0, 10) < 5 ? null : mt_rand(0, 100);
    if($row === null) throw new Exception('The row does not exist.');
    $output[] = $row;
    if(!somethingIsOK()) throw new Exception('Something is most definitely not OK.');
    $output[] = 'Yet more page output';
 catch(Exception $e) 
    $output[] = 'Error: ' . $e->getMessage(); // To show output and error
    $output = array('Error: ' . $e->getMessage()); // To only show error

function somethingIsOK() 
    return mt_rand(0, 10) < 5;

?>

HTML:

<!DOCTYPE HTML>
<html lang="en-US">
<head>
    <meta charset="UTF-8" />
    <title>PHP Error test</title>
    <style type="text/css">
body 
    background: #eee;
    text-align: center

#content 
    padding: 60px

#header 
    padding: 30px;
    background: #fff

#footer 
    padding: 10px;
    background: #ddd

    </style>
</head>
<body>
    <div id="header">Header</div>
    <div id="content">
<?php echo implode('<br />', $output); ?>

    </div>
    <div id="footer">Footer</div>
</body>
</html>

参考资料:

PHP: Exceptions - Manual PHP: CATCH - Manual

【讨论】:

【参考方案5】:

查询的PDO错误异常处理,实际上所有代码都应该运行:

try



catch




finally


这样做的原因是,当您可以在冗长的脚本中大致查明发生错误的位置时,它会使调试变得更加容易

更多信息在这里:http://php.net/manual/en/language.exceptions.php

【讨论】:

我相信 finally 目前仅在 RFC 领域,实际上并不可用。 它供将来使用。对 OP 了解未来的潜力很有好处。不过好点。【参考方案6】:

创建错误处理程序 (set_error_handler) 并在其中抛出异常。 它将有助于不支持异常的函数。

【讨论】:

【参考方案7】:

使用错误处理函数正确处理 PHP 错误和警告。 (见例子here)

在 PHP 中处理错误的最佳方法是, 您可以通过在 php 文件顶部添加此行来停止所有错误报告 -

error_reporting(0);

//OR

error_reporting('E_ALL');

// Predefined Constant

使用函数在 PHP 中处理错误:

debug_backtrace — 生成回溯 debug_print_backtrace — 打印回溯 error_clear_last — 清除最近的错误 error_get_last — 获取最后发生的错误 error_log — 向定义的错误处理发送错误消息 例行公事 error_reporting — 设置报告哪些 PHP 错误 restore_error_handler — 恢复之前的错误处理函数 restore_exception_handler — 恢复之前定义的异常 处理函数 set_error_handler — 设置用户定义的错误处理函数 set_exception_handler — 设置用户定义的异常处理程序 功能 trigger_error — 生成用户级错误/警告/通知消息 user_error — trigger_error 的别名

上面列出的所有函数都用于 PHP 中的错误处理。

【讨论】:

【参考方案8】:

如果您正在寻找一种看起来很漂亮并且可以工作的代码结构 - 您可以使用我一直使用的 whitelist 方法。例如 - 验证 $_GET 变量:

$error = false;

if(!isset($_GET['var'])) 

    $error = 'Please enter var\'s value';

elseif(empty($_GET['var'])) 

    $error = 'Var shouldn\'t be empty';

elseif(!ctype_alnum($_GET['var'])) 

    $error = 'Var should be alphanumeric';


//if we have no errors -> proceed to db part
if(!$error) 

    //inserting var into database table

所以,就是这样,只有 2 个 if/elseif 块,没有嵌套

【讨论】:

我没有投反对票,但 Nate 正在寻找没有 if 语句的东西,因此如果要添加功能,代码需要添加的代码量最少。 这就是 if/else 块的用途 - 用于检查变量和值 确实如此,但这不是处理错误的有效方法。此外,这基本上是 @Nate 已经拥有的。 @ShaquinTrifonoff ,@Nate 使用嵌套的 if/else 块,这不利于错误检查,这就是我提出这种方法的原因;)干杯 @Nate 不想要 if/else 块。如果您使用if/else,随着项目变得越来越复杂,不可避免地需要嵌套块。因此,需要更清洁的解决方案。请参阅接受的答案和我的答案。 更干净

以上是关于处理 php 页面上的错误的最佳方法?的主要内容,如果未能解决你的问题,请参考以下文章

为多个主机上的 PHP 站点处理会话的最佳方法是啥? [关闭]

PHP代码错误导致PHP返回页面空白

在没有数据库或用户名的情况下使用 php 密码保护文件夹/页面的最佳方法是啥

加载应该出现在每个页面上的内容的最佳方式

在 Lumen 中创建自定义错误页面

改善用户体验的404页面最佳实践