Skip to content

never 返回类型

概述

never 是 PHP 8.1 引入的返回类型,表示函数永远不会正常返回。声明为 never 的函数要么抛出异常,要么调用 exit()/die() 终止程序,要么进入无限循环。never 是类型理论中的最底层类型(bottom type),是所有其他类型的子类型。

核心要点

  • never 只能用于返回类型声明,不能用于参数类型或属性类型。
  • never 函数必须通过抛出异常、调用 exit() 或无限循环来终止执行。
  • never 不能作为联合类型的一部分(如 never|int 是非法的)。
  • never 是所有类型的子类型,可以替换任何返回类型(协变)。
  • PHP 8.1+ 可用。

基础概念

什么是 never

never 表示"这个函数永远不会正常返回"。如果一个函数被声明为 never,PHP 将确保:

  • 函数内部调用了 exit()die()
  • 函数抛出了异常(throw
  • 函数进入了无限循环

如果 never 函数执行到了末尾(即正常返回),PHP 会抛出 TypeError

never 与 void 的区别

这是最容易混淆的概念,需要特别注意:

特性voidnever
可用版本PHP 7.1+PHP 8.1+
含义函数不返回值,但会正常结束函数永远不正常结束
可以省略 return可以可以,但必须有 exit/throw/循环
return; 是否合法合法非法(Fatal Error)
return null; 是否合法非法非法
正常执行到函数末尾合法非法(TypeError)
在联合类型中不允许不允许
类型层次无返回值所有类型的子类型

语法与代码示例

抛出异常

php
<?php
declare(strict_types=1);

function throwInvalidArgumentException(string $message): never
{
    throw new InvalidArgumentException($message);
}

function assertNotNull(mixed $value, string $name = 'value'): never
{
    if ($value === null) {
        throw new InvalidArgumentException(
            sprintf('Expected %s not to be null', $name)
        );
    }
}

// assertNotNull() 后面的代码永远不会执行
assertNotNull($data, '$data');
echo "This line will never be reached if $data is null";

调用 exit()

php
<?php
declare(strict_types=1);

function terminate(int $statusCode): never
{
    echo "Shutting down with status {$statusCode}" . PHP_EOL;
    exit($statusCode);
}

function handleCliCommand(array $argv): never
{
    $command = $argv[1] ?? null;

    match ($command) {
        'start' => startServer(),
        'stop' => stopServer(),
        default => terminate(1),
    };
}

在 match 表达式中使用 never

php
<?php
declare(strict_types=1);

function handleHttpMethod(string $method): string
{
    return match ($method) {
        'GET' => 'Read resource',
        'POST' => 'Create resource',
        'PUT' => 'Update resource',
        'DELETE' => 'Delete resource',
        default => throw new BadRequestException(
            "Unsupported HTTP method: {$method}"
        ),
    };
}

// match 的 default 分支使用 never 确保穷举所有情况
function processStatus(string $status): string
{
    return match ($status) {
        'active' => 'User is active',
        'inactive' => 'User is inactive',
        'banned' => 'User is banned',
        default => throw new LogicException("Unknown status: {$status}"),
    };
}

never 作为 throw 表达式(PHP 8.0+)

php
<?php
declare(strict_types=1);

function ensureDirectoryExists(string $path): void
{
    if (!is_dir($path)) {
        throw new RuntimeException("Directory not found: {$path}");
    }
}

// never 函数可以作为表达式的一部分使用
function getConfigValue(string $key): mixed
{
    return $GLOBALS['config'][$key]
        ?? throw new InvalidArgumentException("Config key not found: {$key}");
}

never 函数执行完毕的错误

php
<?php
declare(strict_types=1);

function myNeverFunction(): never
{
    echo "Hello";
    // 函数在这里正常结束 —— 错误!
}

// Fatal Error: Uncaught TypeError: myNeverFunction():
// Return value must be of type never, none returned

致命错误

如果 never 函数正常执行完毕而没有抛出异常或调用 exit(),PHP 将抛出 TypeError。这实际上是一个有用的安全机制,确保你在代码审查时不会遗漏分支处理。

详细说明

never 是最底层类型(Bottom Type)

在类型理论中,never 是最底层类型(也称为空类型或底部类型)。这意味着:

  1. never 是所有类型的子类型:在继承场景中,never 可以替换任何返回类型。
  2. never 没有实例:不存在属于 never 类型的值。
  3. 联合类型中的 never 是冗余的int|never 等价于 int,因此 PHP 不允许这样做。
php
<?php
declare(strict_types=1);

interface ProcessorInterface
{
    public function process(): string;
}

class AlwaysThrows implements ProcessorInterface
{
    public function process(): never
    {
        // never 是 string 的子类型,因此协变合法
        throw new RuntimeException("Always throws");
    }
}

never 在代码分析中的价值

never 返回类型不仅是运行时的约束,更是静态分析工具的信号。IDE 和静态分析器可以利用 never 来推断代码的可达性(reachability):

php
<?php
declare(strict_types=1);

function validateAge(int $age): never
{
    if ($age < 0 || $age > 150) {
        throw new ValueError("Invalid age: {$age}");
    }
}

function processAge(int $age): string
{
    validateAge($age);
    // 静态分析器知道:执行到这里 $age 一定合法(0~150)
    return "Age is {$age}";
}

never 不能用于参数或属性

php
<?php
declare(strict_types=1);

// 错误:never 不能用于参数
function badParam(never $x): void {}      // Fatal Error

// 错误:never 不能用于属性
class BadClass
{
    public never $property;              // Fatal Error
}

实战示例

参数验证器

php
<?php
declare(strict_types=1);

class Assert
{
    public static function that(mixed $value, string $name = 'value'): self
    {
        return new self($value, $name);
    }

    public function __construct(
        private mixed $value,
        private string $name
    ) {
    }

    public function isString(): self
    {
        if (!is_string($this->value)) {
            throw new TypeError(
                sprintf('Expected %s to be string, got %s',
                    $this->name,
                    get_debug_type($this->value)
                )
            );
        }
        return $this;
    }

    public function isNotEmpty(): self
    {
        if ($this->value === '' || $this->value === null || $this->value === []) {
            throw new InvalidArgumentException(
                sprintf('Expected %s not to be empty', $this->name)
            );
        }
        return $this;
    }

    public function notNull(): self
    {
        if ($this->value === null) {
            throw new InvalidArgumentException(
                sprintf('Expected %s not to be null', $this->name)
            );
        }
        return $this;
    }

    public function intRange(int $min, int $max): self
    {
        if ($this->value < $min || $this->value > $max) {
            throw new OutOfRangeException(
                sprintf('Expected %s to be between %d and %d, got %d',
                    $this->name, $min, $max, $this->value)
            );
        }
        return $this;
    }
}

// 使用示例
Assert::that($username, 'username')->isString()->isNotEmpty();
Assert::that($age, 'age')->intRange(0, 150);

命令行控制器

php
<?php
declare(strict_types=1);

class CliApplication
{
    private array $routes = [];

    public function register(string $command, callable $handler): void
    {
        $this->routes[$command] = $handler;
    }

    public function run(array $argv): never
    {
        $command = $argv[1] ?? 'help';

        if (!isset($this->routes[$command])) {
            $this->printError("Unknown command: {$command}");
            $this->showHelp();
            exit(1);
        }

        ($this->routes[$command])(array_slice($argv, 2));
        exit(0);
    }

    private function showHelp(): never
    {
        echo "Usage: php app.php <command> [options]" . PHP_EOL;
        echo "Available commands:" . PHP_EOL;

        foreach (array_keys($this->routes) as $cmd) {
            echo "  - {$cmd}" . PHP_EOL;
        }

        exit(0);
    }

    private function printError(string $message): void
    {
        fwrite(STDERR, "Error: {$message}" . PHP_EOL);
    }
}

枚举穷举检查

php
<?php
declare(strict_types=1);

enum Status: string
{
    case Active = 'active';
    case Inactive = 'inactive';
    case Banned = 'banned';
}

function getStatusLabel(Status $status): string
{
    return match ($status) {
        Status::Active => 'Active',
        Status::Inactive => 'Inactive',
        Status::Banned => 'Banned',
    };
}

// 如果新增了枚举值但忘记在 match 中处理,
// never 分支会在运行时抛出未处理异常
function handleStatus(Status $status): never
{
    match ($status) {
        Status::Active => doActive(),
        Status::Inactive => doInactive(),
        Status::Banned => doBanned(),
    };
    // 如果枚举新增了值而 match 没有覆盖,
    // PHP 会报 TypeError(never 函数正常返回)
}

穷举检查技巧

match 表达式后面添加一个永远不会到达的代码路径,可以用于检测未来新增枚举值时是否遗漏了分支处理。never 返回类型可以强化这一点。

注意事项

never 与 void 的选择指南

场景推荐类型原因
函数执行完毕,不返回数据void函数正常结束
函数抛出异常表示错误never函数不会正常返回
函数调用 exit() 终止程序never函数不会正常返回
函数进入无限循环never函数不会正常返回
函数可能返回值或抛出异常具体类型(如 string异常不是"正常返回"

常见错误

错误写法正确写法说明
function f(): never { return; }function f(): never { exit(); }never 函数不能有 return
function f(never $x): voidfunction f(int $x): voidnever 不能用于参数
function f(): never|intfunction f(): nevernever 不能在联合类型中
function f(): never { echo "ok"; }function f(): void { echo "ok"; }如果函数正常结束,用 void

never 的继承与协变

由于 never 是所有类型的子类型,它可以在子类中替换任何返回类型:

php
<?php
declare(strict_types=1);

abstract class Controller
{
    abstract public function execute(): mixed;
}

class ExitController extends Controller
{
    public function execute(): never
    {
        echo "Application shutting down..." . PHP_EOL;
        exit(0);
        // never 是 mixed 的子类型,协变合法
    }
}

最佳实践

  1. 用于"守卫函数":参数验证失败时抛出异常的函数应该声明为 never,明确告知调用者这些函数不会正常返回。

  2. 用于 CLI 入口点:命令行应用的入口函数通常会调用 exit(),适合声明为 never

  3. 用于 match 的 default 分支:在 match 表达式中,如果 default 分支用于抛出异常表示"不应该到达这里",整个函数可以是 never

  4. 配合静态分析工具never 可以帮助 PHPStan、Psalm 等工具检测死代码和未覆盖的分支。

  5. 不要过度使用:只有函数确实永远不会正常返回时才使用 never。如果一个函数可能正常结束,应使用 void 或具体类型。

  6. 与 throw 表达式结合:PHP 8.0 引入的 throw 表达式使得 never 在 match 和三元表达式中更加自然地使用。

参考链接