Skip to content

联合类型 A|B

概述

联合类型(Union Types)自 PHP 8.0 引入,允许一个类型声明接受多个可能的类型。使用竖线 | 分隔各个类型,声明参数或返回值可以是这些类型中的任意一个。联合类型极大地增强了 PHP 类型系统的表达能力,减少了对 DocBlock 注释的依赖。

核心要点

  • 联合类型使用 | 语法:int|stringarray|Collection 等。
  • PHP 8.0 引入联合类型,?T 语法糖等同于 T|null(PHP 7.1+)。
  • false 伪类型可在联合类型中表示失败返回值。
  • 联合类型中不能包含冗余类型(如 int|INTbool|true)。
  • :::

基础概念

什么是联合类型

联合类型表示一个值可以属于多种类型之一。在类型理论中,联合类型 A|B 的值集合是类型 A 的值集合与类型 B 的值集合的并集。

php
<?php
declare(strict_types=1);

// $id 可以是 int 或 string
function findUser(int|string $id): array
{
    // ...
}

// 返回值可以是 string 或 null
function getName(?string $name): string|null
{
    return $name;
}

可为 null 类型语法糖

PHP 7.1 引入了 ?T 语法糖,等价于 T|null

php
<?php
declare(strict_types=1);

// 以下两种写法完全等价
function foo(?string $value): void {}
function foo(string|null $value): void {}

语法与代码示例

基本联合类型

php
<?php
declare(strict_types=1);

// 接受 int 或 string 参数
function processId(int|string $id): string
{
    return "Processing ID: " . (string) $id;
}

echo processId(42);       // OK: int
echo processId("abc");    // OK: string
echo processId(3.14);     // 非严格模式下会强制转换;严格模式下 TypeError

多类型联合

php
<?php
declare(strict_types=1);

// 接受多种标量类型
function formatValue(int|float|string $value): string
{
    if (is_int($value)) {
        return sprintf('Integer: %d', $value);
    }
    if (is_float($value)) {
        return sprintf('Float: %.2f', $value);
    }
    return sprintf('String: "%s"', $value);
}

echo formatValue(42);          // Integer: 42
echo formatValue(3.14159);      // Float: 3.14
echo formatValue("hello");      // String: "hello"

联合类型中的 false 伪类型

false 是 PHP 8.0 联合类型中引入的特殊用法,用于表示函数可能返回 false(通常表示失败):

php
<?php
declare(strict_types=1);

// 模仿 strpos() 的返回类型
function myStrpos(string $haystack, string $needle): int|false
{
    $pos = strpos($haystack, $needle);
    if ($pos === false) {
        return false;
    }
    return $pos;
}

$result = myStrpos("Hello World", "World");
if ($result !== false) {
    echo "Found at position: {$result}";
}

类类型与标量类型的联合

php
<?php
declare(strict_types=1);

interface StringableInterface
{
    public function __toString(): string;
}

// 接受 string 或任何实现了 __toString 的对象
function writeOutput(string|StringableInterface $input): void
{
    echo (string) $input . PHP_EOL;
}

writeOutput("Hello");           // OK: string
writeOutput(new class implements StringableInterface {
    public function __toString(): string { return "World"; }
});  // OK: 对象

接口类型的联合

php
<?php
declare(strict_types=1);

interface Countable { }
interface ArrayAccess { }

// 接受同时实现两个接口的对象
function processCollection(Countable|ArrayAccess $collection): void
{
    echo get_class($collection) . PHP_EOL;
}

详细说明

联合类型的限制

PHP 对联合类型有严格的限制,避免逻辑错误:

限制示例说明
不能重复类型int|string|intFatal Error
不能使用 mixedint|mixedmixed 已包含所有类型,冗余
不能使用 neverint|nevernever 是所有类型的子类型,冗余
bool 不能与 true/false 同时使用bool|truebool 已包含 true 和 false
object 不能与类类型同时使用object|stdClassobject 已包含所有类
iterable 不能与 array/Traversable 同时使用iterable|arrayiterable 已包含 array 和 Traversable
php
<?php
declare(strict_types=1);

// 以下全部非法
function bad1(): int|string|int {}       // 重复 int
function bad2(): int|mixed {}            // mixed 包含 int
function bad3(): bool|true {}            // bool 包含 true
function bad4(): iterable|Traversable {} // iterable 包含 Traversable

隐式类型转换与联合类型

在非严格模式下,当传入值的类型不在联合类型中时,PHP 会按照以下优先级尝试转换:

  1. int
  2. float
  3. string
  4. bool
php
<?php
// 非 strict_types 模式下的转换规则
// int|string 类型
42       --> 42       // 精确匹配 int
"42"     --> "42"     // 精确匹配 string
42.0     --> 42       // float 兼容 int
true     --> 1        // bool 兼容 int
[]       --> TypeError // array 不兼容

// int|float|bool 类型
"45"     --> 45       // 数字字符串选 int
"45.0"   --> 45.0     // 浮点数字符串选 float
"hello"  --> true      // 非数字字符串回退到 bool
""       --> false     // 空字符串回退到 bool

联合类型与 DocBlock 的对比

在 PHP 8.0 之前,开发者依赖 DocBlock 注释来表达多类型参数:

php
<?php
declare(strict_types=1);

// PHP 8.0 之前:只能用 DocBlock
/**
 * @param int|string $id
 * @return string|null
 */
function oldFindUser($id) {}

// PHP 8.0+:原生联合类型
function findUser(int|string $id): string|null {}

原生联合类型的优势:

  • IDE 支持更好:原生类型能被 IDE 和静态分析工具直接识别。
  • 运行时类型检查:PHP 引擎在运行时自动校验类型。
  • 代码更简洁:不再需要冗余的 DocBlock。

实战示例

多数据源处理器

php
<?php
declare(strict_types=1);

interface DataSource { }

class DatabaseSource implements DataSource
{
    public function __construct(private PDO $pdo) {}
}

class ApiSource implements DataSource
{
    public function __construct(private string $baseUrl) {}
}

class FileSource implements DataSource
{
    public function __construct(private string $filePath) {}
}

function fetchData(
    DatabaseSource|ApiSource|FileSource $source,
    string $query
): array {
    if ($source instanceof DatabaseSource) {
        // 数据库查询
        return [];
    }
    if ($source instanceof ApiSource) {
        // API 请求
        return [];
    }
    // 文件读取
    return [];
}

函数式编程中的 Either 模式

php
<?php
declare(strict_types=1);

class Success
{
    public function __construct(public mixed $value) {}
}

class Failure
{
    public function __construct(public string $error) {}
}

// 使用联合类型模拟 Either
function divide(int $a, int $b): Success|Failure
{
    if ($b === 0) {
        return new Failure("Division by zero");
    }
    return new Success($a / $b);
}

$result = divide(10, 2);
if ($result instanceof Success) {
    echo "Result: {$result->value}" . PHP_EOL;
} else {
    echo "Error: {$result->error}" . PHP_EOL;
}

注意事项

联合类型的 null 处理

php
<?php
declare(strict_types=1);

// 三种等价的写法
function nullable1(?string $value): void {}        // 语法糖
function nullable2(string|null $value): void {}    // 显式联合
function nullable3(null|string $value): void {}    // 顺序无关

类型收窄(Type Narrowing)

在联合类型中,通常需要在函数体内进行类型收窄:

php
<?php
declare(strict_types=1);

function process(int|string|array $input): string
{
    if (is_int($input)) {
        return "Integer: {$input}";
    }
    if (is_string($input)) {
        return "String: {$input}";
    }
    // 此处 $input 一定是 array
    return "Array with " . count($input) . " items";
}

match 表达式与联合类型

match 表达式是处理联合类型返回值的理想工具:

php
<?php
declare(strict_types=1);

function describeType(int|float|string|bool|null $value): string
{
    return match (true) {
        is_int($value) => "integer",
        is_float($value) => "float",
        is_string($value) => "string",
        is_bool($value) => "boolean",
        is_null($value) => "null",
    };
}

最佳实践

  1. 优先使用原生联合类型而非 DocBlock:原生类型提供运行时检查和更好的 IDE 支持。

  2. 保持联合类型尽可能简洁:过多的类型会使代码难以维护。如果联合类型超过 3 个类型,考虑重构。

  3. 善用 false 表示失败:遵循 PHP 内置函数的惯例,使用 int|falsestring|false 表示可能失败的操作。

  4. 使用 match 表达式处理联合类型match 可以基于类型安全地进行分支处理。

  5. 在严格模式下使用联合类型declare(strict_types=1) 可以避免意外的隐式类型转换。

  6. 注意联合类型中的类型顺序:虽然顺序不影响语义,但按照从具体到一般的顺序排列有助于可读性。

进阶用法

调试与测试技巧

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

参考链接