Skip to content

交集类型 A&B

概述

交集类型(Intersection Types)自 PHP 8.1 引入,允许一个类型声明同时满足多个类型的要求。使用 & 符号分隔各个类型,声明的值必须同时属于所有类型。交集类型只能用于类和接口,不能用于标量类型。

核心要点

  • 交集类型使用 & 语法:Countable&IteratorSerializable&Countable 等。
  • PHP 8.1 引入交集类型。
  • 仅可用于类和接口类型,不能用于 intstringbool 等标量类型。
  • 值必须同时实现所有声明的接口/类。
  • 与联合类型(|)互补:联合类型是"或"关系,交集类型是"且"关系。
  • :::

基础概念

什么是交集类型

交集类型表示一个值必须同时满足多个类型约束。在类型理论中,交集类型 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

标量类型不能用于交集类型

这是交集类型最核心的限制。两个标量类型不可能同时成立(一个值不可能同时是 intstring):

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();
    }
}

最佳实践

  1. 在需要多种能力约束时使用交集类型:当函数参数需要同时具备多种行为(如可计数+可迭代),交集类型比多个 instanceof 检查更优雅。

  2. 控制交集的复杂度:交集类型中的接口数量不宜过多,通常 2~3 个即可。过多会导致实现类过于臃肿。

  3. 配合接口小而精的原则:设计用于交集类型的接口应保持最小化,每个接口只负责一种能力。

  4. 不要试图在交集类型中使用标量类型:这是语法错误。如果需要表示"参数是 int 或 string",使用联合类型 int|string

  5. 与 DNF 类型配合使用:PHP 8.2+ 的 DNF 类型允许在联合类型中嵌入交集类型,提供更灵活的表达能力。

  6. 注意接口的兼容性:确保交集类型中的接口不会定义冲突的方法签名(参数或返回类型不兼容)。

参考链接