Skip to content

错误控制运算符

概述

PHP 的错误控制运算符 @ 可以抑制表达式产生的错误和警告信息。当将 @ 放置在表达式前面时,该表达式中可能产生的任何错误/警告/通知都将被忽略。

强烈不推荐

在现代 PHP 开发中,应尽量避免使用 @ 运算符。PHP 官方文档也不推荐使用它。推荐使用 try/catchisset() 等机制来正确处理错误。

基础概念

基本语法

php
// @ 放在表达式前面
$result = @file_get_contents('nonexistent.txt'); // 不显示警告

// @ 可以用于任何表达式
$value = @undefinedVariable; // 不显示 Notice

// @ 不能用于函数定义、类定义等语句
// @function foo() {} // 语法错误

@ 的工作原理

@ 运算符会临时将 error_reporting 设置为 0,表达式执行后恢复之前的设置。这意味着:

  1. 错误仍然会产生,只是不显示
  2. 如果自定义了错误处理器,仍然会被调用
  3. 如果启用了 @ 追踪(xdebug),可以看到被抑制的错误

语法与代码示例

基本使用

php
<?php

declare(strict_types=1);

// 抑制文件操作警告
$content = @file_get_contents('/path/to/nonexistent/file.txt');
if ($content === false) {
    echo "File not found, using default content\n";
    $content = 'default';
}

// 抑制未定义变量通知
@$undefinedVar;
// 不显示 Notice: Undefined variable

// 抑制数组越界
$arr = [1, 2, 3];
@$value = $arr[10]; // 不显示 Warning

// 抑制除零错误
@$result = 10 / 0; // PHP 8.0+ 中 DivisionByZeroError 仍然抛出

@ 不能捕获异常

@ 只能抑制触发错误处理器(error handler)的错误和警告。PHP 8.0+ 中许多错误被转换为异常(如 TypeErrorDivisionByZeroError),@ 无法捕获这些异常。

与 try/catch 的对比

php
<?php

declare(strict_types=1);

// 使用 @ —— 不推荐
// 错误被静默,可能掩盖真实问题
function readConfig(string $path): ?array
{
    $content = @file_get_contents($path);
    if ($content === false) {
        return null;
    }
    return json_decode($content, true);
}

// 使用 try/catch —— 推荐
// 错误被正确捕获和处理
function readConfigSafe(string $path): ?array
{
    try {
        if (!file_exists($path)) {
            return null;
        }
        $content = file_get_contents($path);
        if ($content === false) {
            return null;
        }
        return json_decode($content, true);
    } catch (\Throwable $e) {
        error_log("Config read error: " . $e->getMessage());
        return null;
    }
}

// 使用 isset 避免错误 —— 最推荐
// 在访问前检查,根本不产生错误
function getArrayValue(array $arr, string $key, mixed $default = null): mixed
{
    return array_key_exists($key, $arr) ? $arr[$key] : $default;
}

@ 的性能影响

php
<?php

declare(strict_types=1);

// @ 运算符有性能开销
// 它需要临时修改和恢复 error_reporting 设置

// 测试:大量使用 @ 的性能影响
$iterations = 100000;

// 不使用 @
$start = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
    $arr = ['key' => 'value'];
    $val = $arr['key'] ?? null;
}
$timeWithout = microtime(true) - $start;

// 使用 @
$start = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
    $arr = ['key' => 'value'];
    $val = @$arr['nonexistent'];
}
$timeWith = microtime(true) - $start;

echo "Without @: {$timeWith}\n";
echo "With @: {$timeWith}\n";
// 使用 @ 的版本明显更慢(通常慢 2-3 倍)

详细说明

PHP 8.0+ 中的行为变化

PHP 8.0 将许多错误转换为异常,@ 无法捕获这些异常:

php
<?php

declare(strict_types=1);

// PHP 8.0+:以下异常无法用 @ 抑制
try {
    @$result = 10 / 0; // DivisionByZeroError
} catch (\Throwable $e) {
    echo "Caught: " . $e->getMessage() . "\n";
}

// @ 无法抑制类型错误
function expectInt(int $value): int
{
    return $value * 2;
}

try {
    @expectInt("not an int"); // TypeError 仍然抛出
} catch (\TypeError $e) {
    echo "Caught TypeError: " . $e->getMessage() . "\n";
}

// @ 无法抑制未定义常量访问(PHP 8.0+)
// @$result = UNDEFINED_CONST; // Error

@ 与自定义错误处理器

php
<?php

declare(strict_types=1);

// 自定义错误处理器仍然会被调用
// 可以通过 error_reporting() 的返回值判断是否被 @ 抑制
set_error_handler(function (
    int $errno,
    string $errstr,
    string $errfile,
    int $errline
): bool {
    // error_reporting() 在 @ 表达式内返回 0
    if (error_reporting() === 0) {
        // 被 @ 抑制的错误,可以选择记录但不显示
        error_log("[Suppressed] {$errstr} in {$errfile}:{$errline}");
        return true; // 阻止默认错误处理
    }

    // 正常错误
    throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
});

// 使用 @ 时,错误处理器仍会被触发
// 但 error_reporting() 返回 0
@$result = $undefinedVariable;
// 错误处理器被调用,error_reporting() === 0

@ 与 xdebug

当安装了 xdebug 扩展时,即使使用了 @,xdebug 仍然可以显示被抑制的错误:

ini
; php.ini
xdebug.show_exception_trace = On
xdebug.scream = On  ; 即使使用 @ 也显示错误

调试时开启 xdebug.scream

如果你怀疑代码中 @ 掩盖了关键错误,可以在 php.ini 中设置 xdebug.scream = On 来暴露所有被抑制的错误。

实战示例

替代方案:使用 ?? 运算符

php
<?php

declare(strict_types=1);

// 不推荐:使用 @
$city = @$_GET['city'] ?: 'default';

// 推荐:使用 ?? 运算符(PHP 7.0+)
$city = $_GET['city'] ?? 'default';

// 推荐:使用 isset()
if (isset($_GET['city'])) {
    $city = $_GET['city'];
} else {
    $city = 'default';
}

替代方案:使用 nullsafe 运算符

php
<?php

declare(strict_types=1);

class User
{
    public ?Profile $profile = null;
}

class Profile
{
    public ?string $address = null;
}

$user = new User();

// 不推荐:使用 @
$address = @$user->profile->address;

// 推荐:使用 nullsafe 运算符(PHP 8.0+)
$address = $user->profile?->address;

// 推荐:使用显式检查
$address = null;
if ($user->profile !== null) {
    $address = $user->profile->address;
}

替代方案:使用 error_reporting 精确控制

php
<?php

declare(strict_types=1);

// 临时降低错误报告级别(比 @ 更透明)
$previousLevel = error_reporting(E_ERROR | E_PARSE);
$content = file_get_contents('/tmp/config.json');
error_reporting($previousLevel);

// 或者使用 set_error_handler 临时忽略特定错误
function ignoreWarning(callable $callback): mixed
{
    set_error_handler(function (int $errno, string $errstr): bool {
        return $errno === E_WARNING;
    });
    try {
        return $callback();
    } finally {
        restore_error_handler();
    }
}

$result = ignoreWarning(fn() => file_get_contents('nonexistent.txt'));

注意事项

  • @ 不捕获异常:PHP 8.0+ 大量使用异常替代错误,@ 效果有限
  • @ 有性能开销:需要临时修改和恢复 error_reporting
  • @ 掩盖真实问题:可能隐藏严重的代码错误,增加调试难度
  • @ 不阻止自定义错误处理器:如果你的代码设置了自定义错误处理器,它仍然会被调用
  • PHP 8.0+ 中许多错误变为异常@TypeErrorValueError 等无效

最佳实践

  1. 几乎永远不使用 @:它的存在是 PHP 历史遗留,现代 PHP 有更好的替代方案
  2. 使用 isset()/array_key_exists():在访问前检查,避免产生错误
  3. 使用 ?? 运算符:替代 @$arr['key'] 的典型场景
  4. 使用 try/catch:替代 @ 处理可能失败的操作
  5. 临时降级 error_reporting:如果必须抑制错误,使用 error_reporting() 而非 @

进阶用法

调试与测试技巧

php
<?php
declare(strict_types=1);

// 单元测试辅助函数
function createTestResource(): mixed
{
    return match (true) {
        default => new stdClass(),
    };
}

// 调试输出函数
function debugOutput(mixed , string  = ''): void
{
     =  ? ": " : '';
     .= print_r(, true);
    fwrite(STDERR,  . "\n");
}

// 性能基准测试
function benchmark(callable , int  = 1000): float
{
     = hrtime(true);
    for ($i = 0; $i < $iterations; $i++) {
        $fn();
    }
    return (hrtime(true) - $start) / 1e9;
}

日志记录实践

php
<?php
declare(strict_types=1);

/**
 * 简易日志记录器
 */
class SimpleLogger
{
    private string $logFile;
    private string $level = 'INFO';

    public function __construct(string $logFile)
    {
        $this->logFile = $logFile;
    }

    public function info(string $message, array $context = []): void
    {
        $this->log('INFO', $message, $context);
    }

    public function warning(string $message, array $context = []): void
    {
        $this->log('WARNING', $message, $context);
    }

    public function error(string $message, array $context = []): void
    {
        $this->log('ERROR', $message, $context);
    }

    private function log(string $level, string $message, array $context): void
    {
        $timestamp = date('Y-m-d H:i:s');
        $contextStr = $context ? ' ' . json_encode($context, JSON_UNESCAPED_UNICODE) : '';
        $line = "[{$timestamp}] [{$level}] {$message}{$contextStr}\n";
        file_put_contents($this->logFile, $line, FILE_APPEND | LOCK_EX);
    }
}

配置与环境检测

php
<?php
declare(strict_types=1);

// 环境检测工具
class EnvironmentChecker
{
    public static function checkRequirements(array $requirements): array
    {
        $results = [];
        foreach ($requirements as $name => $check) {
            $results[$name] = is_callable($check) ? $check() : false;
        }
        return $results;
    }

    public static function getSystemInfo(): array
    {
        return [
            'php_version' => PHP_VERSION,
            'os' => PHP_OS,
            'sapi' => PHP_SAPI,
            'memory_limit' => ini_get('memory_limit'),
            'max_execution_time' => ini_get('max_execution_time'),
            'loaded_extensions' => get_loaded_extensions(),
        ];
    }
}

常见问题排查

问题可能原因解决方案
连接超时网络问题/配置错误检查配置,增加超时时间
权限不足文件/目录权限使用 chmod/chown 修正
性能下降索引缺失/数据量大添加索引,优化查询
数据不一致并发冲突/事务残留使用锁机制和事务
内存溢出大数据集/未释放资源增大内存限制,分批处理

故障排除步骤

  1. 检查错误日志和异常信息
  2. 确认配置和环境是否正确
  3. 使用调试工具逐步排查
  4. 参考官方文档查找已知问题

版本兼容性说明

功能最低版本说明
基础功能PHP 8.1本文档基准版本
只读属性PHP 8.1public readonly 修饰符
枚举类型PHP 8.1enum 类型和 match 表达式
FiberPHP 8.1协程/轻量级并发
命名参数PHP 8.0foo(arg_name: value)
联合类型PHP 8.0`int
Null 安全运算符PHP 8.0$obj?->method()
析构器 promotionPHP 8.0__construct(public $x)
php
<?php
declare(strict_types=1);

// 版本兼容性检测
function ensureVersion(string $minVersion): void
{
    if (version_compare(PHP_VERSION, $minVersion, '<')) {
        throw new RuntimeException(
            sprintf('需要 PHP %s+, 当前版本: %s', $minVersion, PHP_VERSION)
        );
    }
}

ensureVersion('8.1.0');

参考链接