Skip to content

类型声明与严格模式

概述

PHP 类型声明(Type Declarations)允许你为函数参数、返回值和类属性指定预期的数据类型。配合 declare(strict_types=1) 严格模式,可以在运行时强制类型检查,在类型不匹配时抛出 TypeError 异常。类型声明是 PHP 从弱类型向强类型演进的核心机制,能显著提高代码的可靠性和可读性。

前置知识

在阅读本节之前,你需要了解:

  • PHP 类型系统的基本分类(标量/复合/特殊类型)
  • PHP 的函数定义和类的基本语法
  • declare() 语句的作用
  • 异常处理(try/catch)的基本概念

基础概念

类型声明的位置

PHP 类型声明可以用于三个位置:

位置语法PHP 版本
函数参数function foo(int $x)PHP 5.1+(array)、PHP 7.0+(标量)
返回值function foo(): intPHP 7.0+
类属性public int $x;PHP 7.4+(类型属性)
构造函数属性提升public function __construct(private readonly int $x)PHP 8.0+

弱类型 vs 强类型模式

php
<?php
// 弱类型模式(默认)—— 自动类型转换
function sum(int $a, int $b): int {
    return $a + $b;
}
sum("5", "10"); // 正常工作,"5"→5, "10"→10,返回 15

// 强类型模式 —— 不进行类型转换
declare(strict_types=1);
function sumStrict(int $a, int $b): int {
    return $a + $b;
}
sumStrict("5", "10"); // TypeError thrown

语法与代码

参数类型声明

php
<?php
declare(strict_types=1);

// 标量类型声明
function calculateArea(float $width, float $height): float
{
    return $width * $height;
}

// 字符串参数
function greet(string $name): string
{
    return "Hello, {$name}!";
}

// 布尔参数
function setDebug(bool $enabled): void
{
    echo $enabled ? 'Debug ON' : 'Debug OFF';
}

// 可空参数(允许 null)
function findUser(?int $id): ?string
{
    if ($id === null) {
        return null;
    }
    return "User #{$id}";
}

// 默认值参数
function paginate(int $page = 1, int $perPage = 20): array
{
    return ['page' => $page, 'per_page' => $perPage];
}

返回值类型声明

php
<?php
declare(strict_types=1);

// 返回 int
function countItems(array $items): int
{
    return count($items);
}

// 返回 void(无返回值)
function logMessage(string $message): void
{
    echo "[LOG] {$message}" . PHP_EOL;
}

// 返回 never(PHP 8.1+,表示函数永远不会正常返回)
function throwError(string $message): never
{
    throw new RuntimeException($message);
}

// 返回 static(PHP 8.0+,返回当前类的实例)
class Builder
{
    public function setName(string $name): static
    {
        $this->name = $name;
        return $this; // 返回 static 保证链式调用的类型安全
    }
}

联合类型声明(PHP 8.0+)

php
<?php
declare(strict_types=1);

// 参数接受多种类型
function processId(int|string $id): string
{
    return is_int($id) ? "#{$id}" : $id;
}

// 返回值可以是多种类型之一
function findValue(array $data, string $key): int|string|null
{
    return $data[$key] ?? null;
}

// 联合类型中包含 null 等价于 ?type 语法
function getUsername(int|null $id): ?string
{
    if ($id === null) {
        return null;
    }
    return "User {$id}";
}

交集类型声明(PHP 8.1+)

php
<?php
declare(strict_types=1);

// 参数必须同时实现多个接口
interface Countable
{
    public function count(): int;
}

interface Iteratorable
{
    public function iterator(): Traversable;
}

// 交集类型:必须同时满足 Countable 和 Iteratorable
function countAndIterate(Countable&Iteratorable $collection): void
{
    echo "Count: " . $collection->count() . PHP_EOL;
    foreach ($collection->iterator() as $item) {
        echo "- {$item}" . PHP_EOL;
    }
}

类属性类型声明(PHP 7.4+)

php
<?php
declare(strict_types=1);

class Product
{
    // PHP 7.4+ 类型属性
    private string $name;
    private float $price;
    private int $stock;
    private ?string $description; // 可空属性

    public function __construct(
        string $name,
        float $price,
        int $stock = 0,
        ?string $description = null,
    ) {
        $this->name = $name;
        $this->price = $price;
        $this->stock = $stock;
        $this->description = $description;
    }

    public function getName(): string { return $this->name; }
    public function getPrice(): float { return $this->price; }
}

// PHP 8.1+ 构造函数属性提升 + readonly
class ProductModern
{
    public function __construct(
        public readonly string $name,
        public readonly float $price,
        public readonly int $stock = 0,
        public readonly ?string $description = null,
    ) {}
}

详细说明

strict_types 的作用范围

declare(strict_types=1) 只影响声明它的文件中函数调用的行为,不影响函数定义:

// file_a.php — 不启用严格模式
function add(int $a, int $b): int { return $a + $b; }
add("1", "2"); // 正常工作(弱类型模式)

// file_b.php — 启用严格模式
declare(strict_types=1);
include 'file_a.php';
add("1", "2"); // TypeError!(严格模式检查发生在调用方)

TypeError 异常处理

php
<?php
declare(strict_types=1);

function divide(int $a, int $b): float
{
    return $a / $b;
}

try {
    divide("10", "2"); // TypeError
} catch (TypeError $e) {
    echo "类型错误: " . $e->getMessage();
    // 类型错误: divide(): Argument #1 ($a) must be of type int, string given
}

类型声明的完整性对比表

类型参数声明返回值声明属性声明备注
int7.0+7.0+7.4+
float7.0+7.0+7.4+
string7.0+7.0+7.4+
bool7.0+7.0+7.4+
array5.1+7.0+7.4+
callable5.4+7.0+
iterable7.1+7.1+7.4+
object7.2+7.2+7.4+任意对象
self5.0+7.0+
static8.0+8.0+
?Type7.1+7.1+7.4+可空类型
A|B8.0+8.0+8.0+联合类型
A&B8.1+8.1+8.1+交集类型
void7.1+无返回值
never8.1+永不返回
mixed8.0+8.0+8.0+任意类型
enum8.1+8.1+8.1+

实战示例

完整的类型安全服务类

php
<?php
declare(strict_types=1);

/**
 * 类型安全的用户服务类
 * 展示各种类型声明的实际使用
 */
class UserService
{
    public function __construct(
        private readonly array $config = []
    ) {}

    /**
     * 参数类型声明 + 返回值类型声明 + 可空类型
     */
    public function findById(int $id): ?User
    {
        // 模拟查找
        return $id > 0 ? new User($id, "User {$id}") : null;
    }

    /**
     * 联合类型参数
     */
    public function find(int|string $identifier): ?User
    {
        if (is_int($identifier)) {
            return $this->findById($identifier);
        }
        return new User(0, $identifier);
    }

    /**
     * iterable 参数(接受数组和 Traversable 对象)
     */
    public function findByNames(iterable $names): array
    {
        $results = [];
        foreach ($names as $name) {
            $results[] = new User(0, (string)$name);
        }
        return $results;
    }

    /**
     * callable 参数
     */
    public function filterUsers(array $users, callable $callback): array
    {
        return array_values(array_filter($users, $callback));
    }
}

class User
{
    public function __construct(
        public readonly int $id,
        public readonly string $name,
    ) {}
}

// 使用示例
$service = new UserService();

// int 参数
$user = $service->findById(1);

// 联合类型参数
$user = $service->find('alice');
$user = $service->find(42);

// iterable 参数
$users = $service->findByNames(['Alice', 'Bob', 'Charlie']);

// callable 参数
$active = $service->filterUsers($users, fn(User $u) => $u->id > 0);

注意事项

1. 严格模式仅影响当前文件

declare(strict_types=1) 只影响声明它的文件中发起的函数调用。被调用的函数所在的文件是否启用严格模式不影响调用方。

2. 内置函数不受 strict_types 影响

php
<?php
declare(strict_types=1);

// 内置函数仍然会进行类型转换
strlen(42);        // PHP 8.0+ 抛出 TypeError
array_key_exists(0, ['a']); // 仍然正常工作

3. 属性类型必须初始化

php
<?php
class Example
{
    private string $name; // 必须在构造函数中初始化

    public function __construct(string $name)
    {
        $this->name = $name;
    }
}

4. 严格模式的性能

启用 strict_types=1 本身几乎不影响性能(只有在类型不匹配时才会有额外的检查开销)。类型声明可以帮助编译器优化,某些情况下反而可能提升性能。

最佳实践

  1. 始终使用 strict_types=1:在新项目中默认启用
  2. 声明返回值类型:每个函数都应有返回值类型声明
  3. 优先使用具体类型:用 int 而非 mixed,用 string 而非 int|string
  4. 可空类型用 ?Type?intint|null 更简洁
  5. 联合类型优于 mixed:能确定可能的类型时使用联合类型
  6. void 返回值:不返回有意义的值时使用 void
  7. never 返回值:抛出异常或终止脚本的函数使用 never(PHP 8.1+)
  8. readonly 属性:不可变数据使用 readonly(PHP 8.1+)
php
<?php
declare(strict_types=1);

// 最佳实践示例
class OrderService
{
    public function __construct(
        private readonly OrderRepository $repo,
        private readonly EventDispatcher $dispatcher,
    ) {}

    public function createOrder(array $items): Order  // 返回值声明
    {
        $order = $this->repo->create($items);
        $this->dispatcher->dispatch(new OrderCreatedEvent($order));
        return $order;
    }

    public function cancelOrder(Order $order): void  // void 返回值
    {
        $order->markAsCancelled();
        $this->repo->save($order);
    }
}

下一节

下一节将详细介绍 null 类型,了解 NULL 值的特性、is_null() 函数以及 null 安全运算符的使用。

参考链接