PHP 错误类型与级别
PHP 脚本在执行过程中可能会遇到各种各样的问题,从不会中断脚本执行的轻微警告,到导致脚本突然终止的严重致命错误。了解这些不同的错误类型,对于诊断问题和构建健壮的应用程序来说是基本功。PHP 将错误划分为几个不同的类型,每种类型都代表了问题的不同严重程度和来源。
1. PHP 错误类型
PHP 主要根据错误的严重程度以及它们是否允许脚本继续运行来进行分类。这些类型在 PHP 中由预定义的常量表示,这些常量对于配置错误报告和处理机制至关重要。
1.1 通知 (Notices)
通知(Notices)是严重程度最低的错误类型。它们表示在开发和测试期间经常发现的轻微问题,不会中断脚本的执行。通知通常指出在某些边缘情况下可能导致意外行为的潜在问题,或者仅仅提示某些地方可以采用更好的处理方式。
特征:
- 脚本继续执行。
- 通常与访问未定义的变量或数组索引有关。
- 最佳实践是解决所有通知(尽管它们是非致命的),因为它们可能会暴露出底层的逻辑缺陷。
示例 1:未定义的变量
访问一个尚未声明或赋值的变量会触发一个 E_NOTICE。
<?php
// 这个脚本打算输出一句问候,但是 'name' 并没有被定义。
echo "Hello, " . $name . "!";
// 输出将会是: Notice: Undefined variable $name in /path/to/script.php on line X
// Hello, !
?>在这个例子中,$name 在没有事先声明的情况下被使用了。PHP 会继续执行,将 $name 视为空字符串,但会发出一个通知来提醒开发者。
示例 2:未定义的数组索引
尝试使用不存在的索引来访问数组元素也会生成一个通知。
<?php
$data = ['item1' => 'Value A', 'item2' => 'Value B'];
// 访问一个存在的键
echo $data['item1'] . "\n"; // 输出: Value A
// 访问一个不存在的键
echo $data['item3'] . "\n";
// 输出将会是: Notice: Undefined index: item3 in /path/to/script.php on line X
//
?>这里访问了 $data['item3'],但 item3 并不是 $data 数组中的键。脚本继续运行,但通知表明在逻辑或数据处理上可能存在失误。
1.2 警告 (Warnings)
警告(Warnings)比通知更严重,但和通知一样,不会停止脚本的运行。警告表明存在比通知更严重的问题,通常暗示需要引起注意,以确保脚本按预期运行。虽然脚本会继续,但后续的输出或操作可能是不正确的。
特征:
- 脚本继续执行。
- 通常与包含不存在的文件,或调用函数时传递了错误数量的参数有关。
- 应该始终解决警告,因为它们会导致难以察觉的 Bug 和不可预测的行为。
示例 1:包含一个不存在的文件
使用 include() 或 require()(我们将在后续章节详细讨论)加载一个不存在的文件会触发一个 E_WARNING(对于 include 而言)。
<?php
// 尝试包含一个不存在的文件
include 'non_existent_file.php';
// 输出将会是: Warning: include(non_existent_file.php): Failed to open stream: No such file or directory in /path/to/script.php on line X
// Warning: include(): Failed opening 'non_existent_file.php' for inclusion (include_path='.:/usr/share/php') in /path/to/script.php on line X
echo "脚本在警告后继续执行。\n";
// 输出: 脚本在警告后继续执行。
?>脚本尝试包含 non_existent_file.php,失败后发出了两个警告,但随后继续执行了 echo 语句。
示例 2:无效的函数参数
如果提供了无效的参数,一些内置函数可能会发出警告(不过根据严重程度,许多函数也会抛出异常或致命错误)。
<?php
// 使用 strlen() 函数处理一个数组(它本期望接收一个字符串)
$myArray = [1, 2, 3];
echo strlen($myArray);
// 输出将会是: Warning: strlen() expects parameter 1 to be string, array given in /path/to/script.php on line X
//
echo "脚本完成。\n";
// 输出: 脚本完成。
?>这里,strlen() 期望得到一个字符串,但传入了一个数组。PHP 发出一个警告,该函数可能返回 null 或 false,但脚本继续执行到了下一行。
1.3 解析错误 / 语法错误 (Parse Errors / Syntax Errors)
解析错误(也称为语法错误)是开发过程中遇到的一类最常见且最致命的错误。当 PHP 解析器在脚本中检测到无效的语法时,就会发生这种错误。这些错误会阻止脚本的编译和执行。
特征:
- 脚本根本不会开始执行。解析器无法理解代码。
- 始终是致命的。
- 通常由缺少分号、未闭合的括号、大括号或使用了错误的关键字引起。
- PHP 会提供一条清晰的消息,指出首次检测到语法错误的文件和行号。
示例 1:缺少分号
最常见的解析错误之一是在语句末尾忘记加分号。
<?php
echo "Hello, World!" // 这里缺少分号
echo "这行代码永远不会被执行。";
// 输出将会是: Parse error: syntax error, unexpected 'echo' (T_ECHO) in /path/to/script.php on line X
?>由于缺少分号,脚本在第一个 echo 语句处立即终止,第二个 echo 永远不会被处理。错误消息表明解析器在 echo 关键字之前期待一个分号。
示例 2:未闭合的大括号
未闭合的大括号或括号是引起解析错误的另一个常见原因。
<?php
if (true) {
echo "在 if 块内部。";
// 这里缺少闭合的右大括号 }
echo "这在 if 块外部。";
// 输出将会是: Parse error: syntax error, unexpected end of file in /path/to/script.php on line X
?>PHP 期望为 if 语句找到一个闭合的右大括号。当它到达文件末尾却没有找到时,就会报告一个“意外的文件结尾 (unexpected end of file)”解析错误。
1.4 致命错误 (Fatal Errors)
致命错误是导致脚本立即终止的严重错误。与警告和通知不同,致命错误意味着脚本无法继续执行。当 PHP 遇到无法恢复的问题时(例如尝试实例化一个不存在的类或调用一个未定义的函数),通常会发生此类错误。
特征:
- 脚本执行立即停止。
- 表明存在极其严重的问题,使得进一步的执行变得不可能或毫无意义。
- 通常与内存问题、未定义的函数/类,或者对不存在的文件调用 require() 有关。
示例 1:调用未定义的函数
尝试调用一个尚未定义的函数将导致致命错误。
<?php
// 尝试调用一个不存在的函数 'my_non_existent_function'
my_non_existent_function();
// 输出将会是: Fatal error: Uncaught Error: Call to undefined function my_non_existent_function() in /path/to/script.php on line X
echo "这行代码永远不会被执行。";
?>尝试调用函数时,脚本立即停止,echo 语句永远不会执行。
示例 2:实例化不存在的类
尝试从一个尚未定义的类创建对象也会导致致命错误。
<?php
// 尝试从一个不存在的类 'NonExistentClass' 创建对象
$obj = new NonExistentClass();
// 输出将会是: Fatal error: Uncaught Error: Class "NonExistentClass" not found in /path/to/script.php on line X
echo "这行代码永远不会被执行。";
?>示例 3:使用 require() 引入不存在的文件
虽然 include() 对于不存在的文件只发出警告,但 require() 会引发致命错误。
<?php
// 使用 require() 加载一个不存在的文件
require 'another_non_existent_file.php';
// 输出将会是: Fatal error: require(): Failed opening required 'another_non_existent_file.php' (include_path='.:/usr/share/php') in /path/to/script.php on line X
echo "脚本不会执行到这里。\n";
?>require 语句对于脚本的执行至关重要。如果找不到该文件,它被视为不可恢复的错误,导致脚本终止。
1.5 可捕获的致命错误 (Recoverable Fatal Errors)
PHP 5 引入了可捕获的致命错误,这类错误通常是致命的,但如果定义了自定义错误处理程序,它们可以被捕获并处理。这允许对某些类型的致命错误(特别是与类型声明相关的错误)执行更优雅的关闭或日志记录机制。
特征:
- 脚本执行通常会停止。
- 如果注册了自定义错误处理程序,它可以尝试恢复。
- 通常与向强制执行严格类型声明的函数传递无效参数有关。
示例:参数类型声明不匹配
<?php declare(strict_types=1);
function sum(int $a, int $b): int {
return $a + $b;
}
// 这通常会导致一个 TypeError(一种可捕获的致命错误)
echo sum(5, 'hello');
// 输出将会是: Fatal error: Uncaught TypeError: sum(): Argument #2 ($b) must be of type int, string given, called in /path/to/script.php on line X and defined in /path/to/script.php:3
?>如果没有自定义错误处理程序,此脚本将以 TypeError 终止。我们将在此后的课程中探索自定义错误处理程序。
1.6 弃用错误 (Deprecated Errors)
弃用错误表明代码中使用的某个特性、函数或常量已经过时,并且可能会在 PHP 的未来版本中被移除。它们通常不是致命错误,允许脚本继续运行。
特征:
- 脚本继续执行。
- 提醒开发者更新他们的代码,使用更新的、受支持的替代方案。
示例:使用已弃用的函数(假设场景)
<?php
// 假设函数 'old_style_connect' 现在已被弃用
function old_style_connect($host) {
// 这里是已弃用的功能
return "Connected to " . $host . " using old method.";
}
echo old_style_connect("localhost");
// 输出可能是: Deprecated: Function old_style_connect() is deprecated in /path/to/script.php on line X
// Connected to localhost using old method.
?>2. 错误级别常量
PHP 定义了几个代表不同错误级别的常量。这些常量与 error_reporting() 和 ini_set() 等函数一起使用,以配置 PHP 应该报告哪些类型的错误。
E_ERROR: 致命的运行时错误。表示无法恢复的错误。E_WARNING: 运行时警告。非致命错误。E_PARSE: 编译时解析错误。语法错误。E_NOTICE: 运行时通知。非关键问题。E_CORE_ERROR: PHP 初始启动期间发生的致命错误。E_CORE_WARNING: PHP 初始启动期间发生的警告。E_COMPILE_ERROR: 致命的编译时错误。E_COMPILE_WARNING: 编译时警告。E_USER_ERROR: 用户生成的错误消息(通过 trigger_error() 触发)。E_USER_WARNING: 用户生成的警告消息。E_USER_NOTICE: 用户生成的通知消息。E_STRICT: 运行时通知,旨在提高代码的互操作性和向前兼容性。E_RECOVERABLE_ERROR: 可捕获的致命错误。E_DEPRECATED: 针对已弃用特性的运行时通知。E_USER_DEPRECATED: 用户生成的弃用消息。E_ALL: 所有的错误和警告(在现代 PHP 中,它包含了所有类型)。
这些常量可以使用位运算符(例如 E_ALL & ~E_NOTICE)组合使用,以精确指定应该报告哪些类型的错误。
3. 综合实战演练
让我们通过一个有意产生不同类型错误的脚本来巩固我们的理解,并观察它们的行为。
<?php
// --- 第 1 部分:通知 (Notice) ---
// 目的:演示访问未定义变量时的 E_NOTICE。
echo "--- 第 1 部分:通知示例 ---\n";
$favoriteFood = "pizza";
// 假设我们本想用 $favoriteFood,但是拼写错误写成了 $favouriteFood
echo "我最喜欢的食物是: " . $favouriteFood . "\n\n"; // 这将触发一个 E_NOTICE
// --- 第 2 部分:警告 (Warning) ---
// 目的:演示尝试包含不存在文件时的 E_WARNING。
echo "--- 第 2 部分:警告示例 ---\n";
// 文件 'config.php' 在此目录中不存在。
// 使用 include() 会发出警告,但允许脚本继续。
include 'non_existent_config.php';
echo "这行代码在 'include' 警告之后执行。\n\n";
// --- 第 3 部分:可捕获的致命错误 (TypeError) ---
// 目的:演示因严格类型不匹配导致的 TypeError。
echo "--- 第 3 部分:可捕获的致命错误 (TypeError) 示例 ---\n";
function addNumbers(int $num1, int $num2): int {
return $num1 + $num2;
}
try {
// 第二个参数传入字符串而不是整数
echo "10 和 '5' 的和是: " . addNumbers(10, '5') . "\n";
} catch (TypeError $e) {
// 这个 catch 块捕获了 TypeError
echo "捕获到一个错误: " . $e->getMessage() . "\n";
}
echo "这行代码会执行,因为 TypeError 被捕获了。\n\n";
// --- 第 4 部分:解析错误 (Syntax Error) ---
// 目的:故意引发语法错误以演示脚本终止。
// 注意:取消注释此部分将阻止脚本运行!
/*
echo "--- 第 4 部分:解析错误示例 ---\n";
if (true) {
echo "在块内部。";
// 这里缺少闭合大括号 }
echo "由于解析错误,这行永远不会被到达。";
*/
// --- 第 5 部分:致命错误 (Fatal Error) ---
// 目的:演示不可恢复的致命错误。
echo "--- 第 5 部分:致命错误(未定义函数)示例 ---\n";
// 调用一个根本不存在的函数。
nonExistentFunctionCall();
echo "由于致命错误,这行代码【永远不会】被执行。\n";
?>