交集类型 A&B
概述
交集类型(Intersection Types)自 PHP 8.1 引入,允许一个类型声明同时满足多个类型的要求。使用 & 符号分隔各个类型,声明的值必须同时属于所有类型。交集类型只能用于类和接口,不能用于标量类型。
核心要点
- 交集类型使用
&语法:Countable&Iterator、Serializable&Countable等。 - PHP 8.1 引入交集类型。
- 仅可用于类和接口类型,不能用于
int、string、bool等标量类型。 - 值必须同时实现所有声明的接口/类。
- 与联合类型(
|)互补:联合类型是"或"关系,交集类型是"且"关系。 - :::
基础概念
什么是交集类型
交集类型表示一个值必须同时满足多个类型约束。在类型理论中,交集类型 A&B 的值集合是类型 A 的值集合与类型 B 的值集合的交集。
php
<?php
declare(strict_types=1);
// $iterator 必须同时实现 Countable 和 Iterator
function countAndIterate(Countable&Iterator $iterator): int
{
$count = count($iterator);
foreach ($iterator as $item) {
// ...
}
return $count;
}交集类型与联合类型的区别
| 特性 | 交集类型 A&B | 联合类型 A|B |
|---|---|---|
| 语法 | &(且) | |(或) |
| 含义 | 必须同时满足所有类型 | 满足任一类型即可 |
| 可用类型 | 仅类和接口 | 所有类型 |
| PHP 版本 | 8.1+ | 8.0+ |
| 典型场景 | 要求对象实现多个接口 | 参数可以是多种类型之一 |
php
<?php
declare(strict_types=1);
// 交集:对象必须同时实现 Countable 和 Iterator
function process(Countable&Iterator $obj): void {}
// 联合:对象只需实现 Countable 或 Iterator
function handle(Countable|Iterator $obj): void {}语法与代码示例
基本交集类型
php
<?php
declare(strict_types=1);
// 参数必须同时实现 Countable 和 Iterator
function analyzeCollection(Countable&Iterator $collection): void
{
echo "Total items: " . count($collection) . PHP_EOL;
echo "First item: " . $collection->current() . PHP_EOL;
}
// 要使用 analyzeCollection,类必须同时实现两个接口
class MyCollection implements Countable, Iterator
{
private array $items = [];
private int $position = 0;
public function __construct(array $items)
{
$this->items = $items;
}
public function count(): int
{
return count($this->items);
}
public function current(): mixed
{
return $this->items[$this->position] ?? null;
}
public function key(): int
{
return $this->position;
}
public function next(): void
{
$this->position++;
}
public function rewind(): void
{
$this->position = 0;
}
public function valid(): bool
{
return isset($this->items[$this->position]);
}
}多接口交集
php
<?php
declare(strict_types=1);
interface Renderable
{
public function render(): string;
}
interface Cacheable
{
public function getCacheKey(): string;
public function getCacheTtl(): int;
}
interface Loggable
{
public function getLogContext(): array;
}
// 参数必须同时实现三个接口
function processComponent(
Renderable&Cacheable&Loggable $component
): string {
$key = $component->getCacheKey();
$output = $component->render();
$context = $component->getLogContext();
return $output;
}
class Widget implements Renderable, Cacheable, Loggable
{
public function render(): string
{
return '<div class="widget">Widget</div>';
}
public function getCacheKey(): string
{
return 'widget:v1';
}
public function getCacheTtl(): int
{
return 3600;
}
public function getLogContext(): array
{
return ['component' => 'widget'];
}
}返回值使用交集类型
php
<?php
declare(strict_types=1);
function createMultiIterator(): Countable&Iterator
{
return new class implements Countable, Iterator {
private array $data = ['a', 'b', 'c'];
private int $pos = 0;
public function count(): int { return count($this->data); }
public function current(): mixed { return $this->data[$this->pos] ?? null; }
public function key(): int { return $this->pos; }
public function next(): void { $this->pos++; }
public function rewind(): void { $this->pos = 0; }
public function valid(): bool { return isset($this->data[$this->pos]); }
};
}详细说明
交集类型的限制
交集类型有比联合类型更严格的限制:
| 限制 | 说明 | 示例 |
|---|---|---|
| 只能用于类/接口 | 不能用于标量类型 | int&string 非法 |
| 不能使用 self/parent/static | 这些是动态类型 | self&Countable 非法 |
| 不能包含 mixed/never | 这些是极端类型 | mixed&Countable 非法 |
| 不能重复类型 | 大小写不敏感 | A&B&A 非法 |
php
<?php
declare(strict_types=1);
// 以下全部非法
function bad1(int&string $x): void {} // 标量类型不能用于交集
function bad2(self&Countable $x): void {} // 不能使用 self
function bad3(Countable&Iterator&Countable $x): void {} // 重复 Countable标量类型不能用于交集类型
这是交集类型最核心的限制。两个标量类型不可能同时成立(一个值不可能同时是 int 和 string):
php
<?php
declare(strict_types=1);
// 以下全部非法
function bad1(int&float $x): void {}
function bad2(string&bool $x): void {}
function bad3(int&string $x): void {}
// 只有类/接口可以用于交集类型
function good(Countable&Iterator $x): void {} // OK
function good2(Traversable&ArrayAccess $x): void {} // OK交集类型在 DNF 中的使用
在 PHP 8.2 中引入 DNF 类型后,交集类型可以作为联合类型的组成部分(参见 DNF 类型):
php
<?php
declare(strict_types=1);
// PHP 8.2+ DNF 类型
function process((Countable&Iterator)|array $input): void {}实战示例
多接口约束的中间件
php
<?php
declare(strict_types=1);
interface MiddlewareInterface
{
public function handle(Request $request, Closure $next): Response;
}
interface CacheMiddleware
{
public function getCacheKey(Request $request): ?string;
public function getCacheTtl(): int;
}
interface LogMiddleware
{
public function shouldLog(Request $request): bool;
public function log(Request $request, Response $response): void;
}
// 要求中间件同时支持缓存和日志
function registerAdvancedMiddleware(
MiddlewareInterface&CacheMiddleware&LogMiddleware $middleware
): void {
echo "Registered: " . get_class($middleware) . PHP_EOL;
}
class AdvancedMiddleware implements MiddlewareInterface, CacheMiddleware, LogMiddleware
{
public function handle(Request $request, Closure $next): Response
{
$key = $this->getCacheKey($request);
if ($key !== null) {
return new Response("Cached: {$key}");
}
return $next($request);
}
public function getCacheKey(Request $request): ?string
{
return md5($request->getPath());
}
public function getCacheTtl(): int
{
return 300;
}
public function shouldLog(Request $request): bool
{
return true;
}
public function log(Request $request, Response $response): void
{
// 记录日志
}
}类型安全的集合过滤
php
<?php
declare(strict_types=1);
function filterByInterfaces(
array $items,
string $interface
): array {
return array_filter($items, fn($item) => $item instanceof $interface);
}
// 使用交集类型约束回调函数的参数
function processFiltered(
Countable&Iterator $filtered,
callable $processor
): void {
$count = count($filtered);
echo "Processing {$count} items..." . PHP_EOL;
foreach ($filtered as $item) {
$processor($item);
}
}注意事项
什么时候使用交集类型
使用交集类型的典型场景:
| 场景 | 是否适合 | 原因 |
|---|---|---|
| 对象需要同时实现多个接口 | 适合 | 交集类型的核心用途 |
| 函数返回特定能力的对象 | 适合 | 明确返回值的能力集 |
| 需要约束对象同时具备多种行为 | 适合 | 编译时确保接口实现 |
| 标量类型的组合 | 不适合 | 标量类型不能用于交集 |
| 需要参数是多种类型之一 | 不适合 | 应使用联合类型 |
交集类型与 trait 的关系
php
<?php
declare(strict_types=1);
trait CountableTrait
{
private int $count = 0;
public function count(): int
{
return $this->count;
}
}
// trait 本身不能用于类型声明,但通过接口可以实现类似效果
interface CountableInterface
{
public function count(): int;
}
function processCountable(CountableInterface $item): void
{
echo "Count: " . count($item) . PHP_EOL;
}交集类型的继承
php
<?php
declare(strict_types=1);
interface A { public function a(): void; }
interface B { public function b(): void; }
class Base
{
public function process(A&B $obj): void
{
$obj->a();
$obj->b();
}
}
class Child extends Base
{
// 协变:可以使用更具体的类型(子接口)
public function process(A&B $obj): void
{
echo "Processing..." . PHP_EOL;
$obj->a();
$obj->b();
}
}最佳实践
在需要多种能力约束时使用交集类型:当函数参数需要同时具备多种行为(如可计数+可迭代),交集类型比多个
instanceof检查更优雅。控制交集的复杂度:交集类型中的接口数量不宜过多,通常 2~3 个即可。过多会导致实现类过于臃肿。
配合接口小而精的原则:设计用于交集类型的接口应保持最小化,每个接口只负责一种能力。
不要试图在交集类型中使用标量类型:这是语法错误。如果需要表示"参数是 int 或 string",使用联合类型
int|string。与 DNF 类型配合使用:PHP 8.2+ 的 DNF 类型允许在联合类型中嵌入交集类型,提供更灵活的表达能力。
注意接口的兼容性:确保交集类型中的接口不会定义冲突的方法签名(参数或返回类型不兼容)。