Skip to content

TypeError、ValueError 与 ArgumentCountError

概述

这三种错误类是 PHP 日常开发中最常遇到的 Error 子类。它们分别处理类型不匹配值不合法参数数量错误三种最常见的函数调用问题。理解它们的区别和触发条件,有助于编写更健壮的类型安全代码。

PHP 版本说明

  • TypeError:PHP 7.0+(最常用的 Error 子类之一)
  • ArgumentCountError:PHP 7.0+(继承自 TypeError)
  • ValueError:PHP 8.0+(新增,与 TypeError 配对使用)

基础概念

三种错误的定位

错误类型问题维度类比示例
TypeError类型不对期望苹果给了橘子参数期望 int,传了 string
ValueError值不对期望正数给了负数参数期望 >= 1,传了 0
ArgumentCountError数量不对期望两个给了三个函数需要 2 个参数,传了 1 个

继承关系

Error
├── TypeError
│   └── ArgumentCountError
└── ValueError

TypeErrorValueError 是平级关系,都继承自 ErrorArgumentCountError 继承自 TypeError,所以 catch (\TypeError $e) 会同时捕获 ArgumentCountError

语法与代码

TypeError 详解

TypeError 在以下场景触发:

  1. 参数类型声明不匹配
  2. 返回值类型声明不匹配
  3. 内部函数参数类型不正确
php
<?php

declare(strict_types=1);

// 场景 1:参数类型不匹配
function setName(string $name): void
{
    echo "设置名称: {$name}";
}

try {
    setName(123); // int, 期望 string
} catch (\TypeError $e) {
    echo $e->getMessage();
    // 严格模式: "setName(): Argument #1 ($name) must be of type string, int given"
}

严格模式 vs 宽松模式下的 TypeError

php
<?php

declare(strict_types=1);

// 严格模式(declare(strict_types=1))下,不会进行隐式类型转换
function add(int $a, int $b): int
{
    return $a + $b;
}

try {
    add('5', '10'); // string, 期望 int → TypeError
} catch (\TypeError $e) {
    echo '严格模式触发: ' . $e->getMessage();
}

// --- 另一个文件,无 strict_types ---
// 宽松模式下,'5' 和 '10' 会被隐式转换为 int(5) 和 int(10)
// 不会触发 TypeError

ValueError 详解

ValueError 在参数类型正确但值不在可接受范围内时触发(PHP 8.0+)。

php
<?php

declare(strict_types=1);

// 类型正确(int),但值不合法(不能 <= 0)
try {
    $result = array_chunk([1, 2, 3], 0);
} catch (\ValueError $e) {
    echo $e->getMessage();
    // "array_chunk(): Argument #2 ($length) must be at least 1"
}

// 类型正确(string),但值不合法(不是有效的正则表达式)
try {
    preg_match('/[/', 'test');
} catch (\ValueError $e) {
    echo $e->getMessage();
    // "preg_match(): Passing NULL to parameter #1 ($pattern) of type string is deprecated"
}

常见触发 ValueError 的内置函数

php
<?php

declare(strict_types=1);

$errors = [];

// 1. array_chunk - size 必须 >= 1
try { array_chunk([1, 2], 0); } catch (\ValueError $e) { $errors[] = $e->getMessage(); }

// 2. intdiv - 除数不能为 0
try { intdiv(10, 0); } catch (\DivisionByZeroError $e) { $errors[] = $e->getMessage(); }

// 3. json_encode 深度溢出
try { json_encode([1], JSON_THROW_ON_ERROR, 0); } catch (\ValueError $e) { $errors[] = $e->getMessage(); }

// 4. substr 偏移量
try { substr('hello', 1, -100); } catch (\ValueError $e) { $errors[] = $e->getMessage(); }

// 5. strlen 不能为负数
try { str_repeat('a', -1); } catch (\ValueError $e) { $errors[] = $e->getMessage(); }

foreach ($errors as $err) {
    echo "- {$err}" . PHP_EOL;
}

ArgumentCountError 详解

ArgumentCountError 在函数调用时参数数量与声明不匹配时触发。

php
<?php

declare(strict_types=1);

function findUser(int $id, bool $withPosts = false): array
{
    return ['id' => $id, 'with_posts' => $withPosts];
}

try {
    findUser(); // 0 个参数,至少需要 1 个
} catch (\ArgumentCountError $e) {
    echo $e->getMessage();
    // "findUser() expects at least 1 argument, 0 given"
}

// 有默认值的参数不会触发
try {
    $user = findUser(1); // 正常:只传必需参数
    echo json_encode($user); // {"id":1,"with_posts":false}
} catch (\ArgumentCountError $e) {
    echo '不会到这里';
}

ArgumentCountError 与可变参数

php
<?php

declare(strict_types=1);

// 使用 ...$rest 可变参数,传入任意数量都不会触发 ArgumentCountError
function variadic(string $required, string ...$optional): void
{
    echo "必需参数: {$required}, 可选参数数量: " . count($optional);
}

// 这些都不会触发 ArgumentCountError
variadic('hello');             // 1 个参数
variadic('hello', 'world');    // 2 个参数
variadic('hello', 'a', 'b');   // 3 个参数

详细说明

TypeError vs ValueError 的本质区别

php
<?php

declare(strict_types=1);

function setAge(int $age): void
{
    if ($age < 0 || $age > 150) {
        throw new \ValueError("年龄必须在 0~150 之间,收到: {$age}");
    }
    echo "年龄设置为: {$age}";
}

// TypeError: 类型不对
try {
    setAge('twenty'); // string 不是 int → TypeError
} catch (\TypeError $e) {
    echo 'TypeError: ' . $e->getMessage();
}

// ValueError: 类型对但值不对
try {
    setAge(-1); // int 类型正确,但值 -1 不合法 → ValueError
} catch (\ValueError $e) {
    echo 'ValueError: ' . $e->getMessage();
}
维度TypeErrorValueError
问题传了错误的类型传了不合法的
示例int 期望,string 传入int 期望,-1 传入(需 >= 0)
谁抛出PHP 引擎自动抛出PHP 引擎(内置函数)/ 用户代码
处理方式修复调用方的类型修复调用方的值范围

错误消息格式

PHP 引擎生成的错误消息遵循统一格式:

函数名(): Argument #N ($参数名) must be of type 期望类型, 实际类型 given

例如:

  • setName(): Argument #1 ($name) must be of type string, int given
  • createUser(): Argument #2 ($email) must be of type string, null given

消息中的参数名

从 PHP 8.0 起,错误消息中会包含参数名($name),PHP 7.x 只显示参数编号(Argument #1)。

实战示例

统一的参数验证处理器

php
<?php

declare(strict_types=1);

class ParamValidator
{
    /**
     * 验证整数范围,值不合法时抛出 ValueError
     */
    public static function intRange(
        int $value,
        int $min,
        int $max,
        string $paramName = 'value'
    ): void {
        if ($value < $min || $value > $max) {
            throw new \ValueError(
                message: "{$paramName} 必须在 {$min}~{$max} 之间,收到: {$value}",
                code: 0
            );
        }
    }

    /**
     * 验证字符串长度
     */
    public static function stringLength(
        string $value,
        int $min = 0,
        ?int $max = null,
        string $paramName = 'value'
    ): void {
        $len = mb_strlen($value, 'UTF-8');
        if ($len < $min) {
            throw new \ValueError(
                message: "{$paramName} 长度不能少于 {$min} 个字符,当前: {$len}",
                code: 0
            );
        }
        if ($max !== null && $len > $max) {
            throw new \ValueError(
                message: "{$paramName} 长度不能超过 {$max} 个字符,当前: {$len}",
                code: 0
            );
        }
    }

    /**
     * 验证枚举值
     */
    public static function inArray(
        mixed $value,
        array $allowed,
        string $paramName = 'value'
    ): void {
        if (!in_array($value, $allowed, true)) {
            $allowedStr = implode(', ', array_map(
                fn($v) => var_export($v, true),
                $allowed
            ));
            throw new \ValueError(
                message: "{$paramName} 必须是 [{$allowedStr}] 之一,收到: " . var_export($value, true),
                code: 0
            );
        }
    }
}

// 使用示例
try {
    ParamValidator::intRange(200, 0, 150, '年龄');
} catch (\ValueError $e) {
    echo $e->getMessage(); // "年龄 必须在 0~150 之间,收到: 200"
}

注意事项

  1. strict_types 是按文件设置的declare(strict_types=1) 只影响当前文件中函数调用的类型检查行为,不影响被调用文件中的函数定义。

  2. TypeError 消息包含敏感信息TypeError 的消息中可能包含文件路径和函数签名,生产环境不应直接展示给用户。

  3. ValueError 不在 PHP 7.x 中存在:如果你的项目需要兼容 PHP 7.x,不要直接 catch (\ValueError $e),应使用 catch (\Error $e) 然后检查类型。

  4. ArgumentCountError 对命名参数的影响:PHP 8.0+ 的命名参数不会改变 ArgumentCountError 的触发条件——缺少必需参数仍然会触发此错误。

  5. 内部函数也可能抛出 TypeError:不只是用户定义函数,PHP 内置函数(如 array_filter 的回调参数)也可能抛出 TypeError

最佳实践

推荐做法

  1. 始终开启 strict_types:在文件头部添加 declare(strict_types=1),尽早发现类型问题
  2. 对用户输入手动抛出 ValueError:PHP 内置函数自动抛出 ValueError,用户自定义函数也应遵循此模式
  3. 分层捕获:外层 catch (\Error $e) 兜底,内层分别 catch (\TypeError $e)catch (\ValueError $e) 处理
  4. 参数验证放在函数入口:验证失败立即抛出异常,而非返回特殊值(如 nullfalse
php
<?php

declare(strict_types=1);

// 最佳实践:函数入口处进行参数验证
function calculateDiscount(int $price, int $percent): int
{
    // TypeError 由 PHP 引擎自动处理(严格模式下)
    // ValueError 由开发者手动处理
    if ($price < 0) {
        throw new \ValueError("价格不能为负数,收到: {$price}");
    }
    if ($percent < 0 || $percent > 100) {
        throw new \ValueError("折扣百分比必须在 0~100 之间,收到: {$percent}");
    }

    return (int) ($price * (100 - $percent) / 100);
}

try {
    $result = calculateDiscount(100, 50);
    echo "折后价: {$result}"; // 50
} catch (\TypeError $e) {
    echo "参数类型错误: " . $e->getMessage();
} catch (\ValueError $e) {
    echo "参数值错误: " . $e->getMessage();
}

进阶用法

调试与测试技巧

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');

参考链接