assert 断言
概述
assert() 是 PHP 的断言函数,用于在开发阶段验证代码假设。如果断言表达式为 false,PHP 会抛出异常或发出警告。在 PHP 8.0 中,assert 的行为发生了重大变更——不再将字符串参数作为 PHP 代码执行,使其更加安全。
PHP 版本说明
assert()自 PHP 4 起可用- PHP 7.0:断言失败默认抛出
AssertionError(而非 Warning) - PHP 8.0:不再执行字符串参数(安全改进),字符串参数等于
false assert.expect_exception:PHP 7.0 废弃,PHP 8.0 移除assert.exception:默认1(抛出异常),设为0则发出警告
基础概念
断言的作用
断言用于验证开发者的假设,而非验证用户输入。断言失败的预期行为是"代码有 Bug",而不是"用户输入有误"。
| 场景 | 使用断言 | 使用条件验证 |
|---|---|---|
| 验证函数参数范围 | 不适合 | 适合(抛出 ValueError) |
| 验证内部不变量 | 适合 | 不适合 |
| 验证开发者假设 | 适合 | 不适合 |
| 验证返回值约定 | 适合 | 不适合 |
assert 与 assert_options 配置
ini
; php.ini 配置
assert.active = 1 ; 启用断言(默认 1)
assert.exception = 1 ; 断言失败抛出异常(默认 1)
assert.bail = 0 ; 断言失败时终止脚本(默认 0)
assert.warning = 1 ; 断言失败发出 Warning(PHP 7.0 之前)
assert.callback = "" ; 断言失败回调函数语法与代码
基本断言用法
php
<?php
declare(strict_types=1);
// 基本语法:assert(expression, description)
$age = 25;
// 简单断言
assert($age > 0, '年龄必须大于 0');
// 复杂表达式
assert(
is_int($age) && $age >= 0 && $age <= 150,
'年龄必须是 0~150 之间的整数'
);
// 断言不会在生产环境中执行(assert.active=0 时)
// 但表达式中的副作用仍然存在,因此不应在断言中使用有副作用的表达式PHP 8.0+ 断言行为
php
<?php
declare(strict_types=1);
// PHP 8.0+ 断言失败抛出 AssertionError(默认行为)
try {
$value = -1;
assert($value >= 0, '值必须非负');
} catch (\AssertionError $e) {
echo $e->getMessage(); // "值必须非负"
}
// PHP 8.0+ 中,字符串参数视为断言失败
assert('1 === 1'); // PHP 8.0+ → 失败(字符串不是 bool)
assert(true); // 成功
// PHP 8.0+ 推荐使用表达式
assert(1 === 1, '数学正确'); // 成功PHP 8.0 重大变更
在 PHP 7.x 中,assert('1 === 1') 会将字符串作为 PHP 代码执行。PHP 8.0 起这种行为被移除,字符串参数直接被视为断言失败。这是一个安全性改进,防止通过字符串断言执行任意代码。
断言回调函数
php
<?php
declare(strict_types=1);
// 自定义断言回调
assert_options(ASSERT_CALLBACK, function (string $file, int $line, ?string $assertion): void {
$msg = "断言失败 in {$file}:{$line}";
if ($assertion !== null) {
$msg .= " — {$assertion}";
}
error_log($msg);
if (php_sapi_name() !== 'cli') {
http_response_code(500);
echo json_encode(['error' => 'Internal assertion failed']);
}
});
// 触发回调
assert(false, '数据库连接不应为 null');在类方法中使用断言
php
<?php
declare(strict_types=1);
class UserRepository
{
private array $users = [];
public function add(User $user): void
{
// 断言:用户 ID 必须唯一(开发期验证)
$id = $user->id;
assert(
!isset($this->users[$id]),
"用户 ID {$id} 已存在,违反唯一性约束"
);
$this->users[$id] = $user;
}
public function find(int $id): ?User
{
// 断言:ID 必须为正数
assert($id > 0, "用户 ID 必须为正数,收到: {$id}");
return $this->users[$id] ?? null;
}
public function count(): int
{
// 断言:内部状态一致性验证
$actualCount = count($this->users);
assert(
$actualCount >= 0,
"用户数量不能为负数: {$actualCount}"
);
return $actualCount;
}
}
// 使用
$user = new User(id: 1, name: 'Alice');
$repo = new UserRepository();
$repo->add($user);
$repo->add($user); // AssertionError: 用户 ID 1 已存在assert.exception 配置
php
<?php
declare(strict_types=1);
// 方式 1:通过 assert_options 设置
assert_options(ASSERT_EXCEPTION, 0); // 断言失败发出 Warning,不抛异常
// 方式 2:通过 ini_set
ini_set('assert.exception', '0');
// 配置为 0 时,断言失败产生 Warning
assert(false, '这会产生 Warning');
// 恢复默认(抛出异常)
assert_options(ASSERT_EXCEPTION, 1);详细说明
assert() 函数签名
php
assert(
mixed $assertion,
string|Throwable $description = ""
): bool| 参数 | 类型 | 说明 |
|---|---|---|
$assertion | mixed | 断言表达式(PHP 8.0+,字符串视为 false) |
$description | string|Throwable | 失败描述或自定义异常 |
PHP 5.x → 7.x → 8.0 演进
| 特性 | PHP 5.x | PHP 7.0~7.4 | PHP 8.0+ |
|---|---|---|---|
| 默认行为 | Warning | AssertionError | AssertionError |
| 字符串参数 | 作为 PHP 代码执行 | 作为 PHP 代码执行 | 视为 false |
assert.exception | 不存在 | 默认 1(抛异常) | 默认 1 |
| 安全性 | 低(代码注入风险) | 低 | 高 |
断言 vs 异常 vs 条件验证
php
<?php
declare(strict_types=1);
function calculateDiscount(int $price, int $percent): int
{
// 条件验证:对用户输入,使用 if + 抛出异常
if ($price < 0) {
throw new \ValueError("价格不能为负数: {$price}");
}
if ($percent < 0 || $percent > 100) {
throw new \ValueError("折扣必须在 0~100 之间: {$percent}");
}
$discounted = (int) ($price * (100 - $percent) / 100);
// 断言:验证内部计算结果(开发者假设)
assert(
$discounted >= 0 && $discounted <= $price,
'折扣计算结果必须在合理范围内'
);
return $discounted;
}description 参数传入 Throwable
php
<?php
declare(strict_types=1);
class InvalidStateException extends \RuntimeException {}
// 传入 Throwable 作为 description
assert(
$state === 'active',
new InvalidStateException('用户状态必须为 active')
);
// 等价于
// if ($state !== 'active') {
// throw new InvalidStateException('用户状态必须为 active');
// }实战示例
开发期的契约式设计
php
<?php
declare(strict_types=1);
final class Money
{
private readonly int $amount;
public function __construct(int $amount)
{
// 前置条件:使用条件验证(面向用户)
if ($amount < 0) {
throw new \ValueError("金额不能为负数: {$amount}");
}
$this->amount = $amount;
}
public function add(Money $other): Money
{
$sum = $this->amount + $other->amount;
// 后置条件:使用断言(面向开发者)
assert(
$sum >= $this->amount && $sum >= $other->amount,
'加法结果不应小于任一操作数'
);
return new Money($sum);
}
public function multiply(int $factor): Money
{
assert($factor >= 0, '乘数不能为负');
$product = $this->amount * $factor;
// 不变量验证
assert(
$product >= 0,
'金额乘法结果不能为负'
);
return new Money($product);
}
public function amount(): int
{
return $this->amount;
}
}
// 开发期使用断言验证假设
$price = new Money(100);
$discounted = $price->multiply(-1); // AssertionError: 乘数不能为负注意事项
断言不是输入验证:永远不要用
assert验证用户输入。断言可能在生产环境中被禁用,导致安全检查被跳过。PHP 8.0 不再执行字符串断言:如果从 PHP 7.x 升级到 8.0,所有
assert('expression')形式的断言都需要改为表达式形式。断言中的副作用:不要在断言表达式中编写有副作用的代码(如
$x = assert($y = compute())),因为生产环境断言可能被禁用。assert.bail = 1 很危险:此选项会在断言失败时立即终止脚本,即使有自定义异常处理器也不会执行。
PHPUnit 中的 assert 是不同的:PHPUnit 的
assertTrue()等断言方法是测试框架的一部分,与 PHP 内置的assert()无关。
最佳实践
推荐做法
- 断言仅用于开发期验证:验证开发者假设、不变量、前后条件,而非用户输入
- 生产环境禁用断言执行:设置
assert.active = 0,但保留断言语法(避免表达式副作用) - 使用描述性消息:断言失败时提供清晰的上下文信息,帮助快速定位问题
- 使用 Throwable 作为 description:可以抛出自定义异常类型,获得更好的异常分类
- 配合静态分析工具:PhpStan、Psalm 等工具可以在编译期发现断言能覆盖的问题
php
<?php
declare(strict_types=1);
// 生产环境配置建议
// php.ini
// assert.active = 1
// assert.exception = 1
// zend.assertions = -1 ; 生产环境使用 -1(编译时移除断言代码)
// 开发环境
// zend.assertions = 1 ; 启用断言
// 测试环境
// zend.assertions = 1 ; 启用断言进阶用法
调试与测试技巧
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 修正 |
| 性能下降 | 索引缺失/数据量大 | 添加索引,优化查询 |
| 数据不一致 | 并发冲突/事务残留 | 使用锁机制和事务 |
| 内存溢出 | 大数据集/未释放资源 | 增大内存限制,分批处理 |
故障排除步骤
- 检查错误日志和异常信息
- 确认配置和环境是否正确
- 使用调试工具逐步排查
- 参考官方文档查找已知问题
版本兼容性说明
| 功能 | 最低版本 | 说明 |
|---|---|---|
| 基础功能 | PHP 8.1 | 本文档基准版本 |
| 只读属性 | PHP 8.1 | public readonly 修饰符 |
| 枚举类型 | PHP 8.1 | enum 类型和 match 表达式 |
| Fiber | PHP 8.1 | 协程/轻量级并发 |
| 命名参数 | PHP 8.0 | foo(arg_name: value) |
| 联合类型 | PHP 8.0 | `int |
| Null 安全运算符 | PHP 8.0 | $obj?->method() |
| 析构器 promotion | PHP 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');