联合类型 A|B
概述
联合类型(Union Types)自 PHP 8.0 引入,允许一个类型声明接受多个可能的类型。使用竖线 | 分隔各个类型,声明参数或返回值可以是这些类型中的任意一个。联合类型极大地增强了 PHP 类型系统的表达能力,减少了对 DocBlock 注释的依赖。
核心要点
- 联合类型使用
|语法:int|string、array|Collection等。 - PHP 8.0 引入联合类型,
?T语法糖等同于T|null(PHP 7.1+)。 false伪类型可在联合类型中表示失败返回值。- 联合类型中不能包含冗余类型(如
int|INT或bool|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|int | Fatal Error |
| 不能使用 mixed | int|mixed | mixed 已包含所有类型,冗余 |
| 不能使用 never | int|never | never 是所有类型的子类型,冗余 |
| bool 不能与 true/false 同时使用 | bool|true | bool 已包含 true 和 false |
| object 不能与类类型同时使用 | object|stdClass | object 已包含所有类 |
| iterable 不能与 array/Traversable 同时使用 | iterable|array | iterable 已包含 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 会按照以下优先级尝试转换:
intfloatstringbool
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",
};
}最佳实践
优先使用原生联合类型而非 DocBlock:原生类型提供运行时检查和更好的 IDE 支持。
保持联合类型尽可能简洁:过多的类型会使代码难以维护。如果联合类型超过 3 个类型,考虑重构。
善用
false表示失败:遵循 PHP 内置函数的惯例,使用int|false、string|false表示可能失败的操作。使用
match表达式处理联合类型:match可以基于类型安全地进行分支处理。在严格模式下使用联合类型:
declare(strict_types=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');