类型声明与严格模式
概述
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(): int | PHP 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
}类型声明的完整性对比表
| 类型 | 参数声明 | 返回值声明 | 属性声明 | 备注 |
|---|---|---|---|---|
int | 7.0+ | 7.0+ | 7.4+ | |
float | 7.0+ | 7.0+ | 7.4+ | |
string | 7.0+ | 7.0+ | 7.4+ | |
bool | 7.0+ | 7.0+ | 7.4+ | |
array | 5.1+ | 7.0+ | 7.4+ | |
callable | 5.4+ | 7.0+ | 否 | |
iterable | 7.1+ | 7.1+ | 7.4+ | |
object | 7.2+ | 7.2+ | 7.4+ | 任意对象 |
self | 5.0+ | 7.0+ | 否 | |
static | 8.0+ | 8.0+ | 否 | |
?Type | 7.1+ | 7.1+ | 7.4+ | 可空类型 |
A|B | 8.0+ | 8.0+ | 8.0+ | 联合类型 |
A&B | 8.1+ | 8.1+ | 8.1+ | 交集类型 |
void | 否 | 7.1+ | 否 | 无返回值 |
never | 否 | 8.1+ | 否 | 永不返回 |
mixed | 8.0+ | 8.0+ | 8.0+ | 任意类型 |
enum | 8.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 本身几乎不影响性能(只有在类型不匹配时才会有额外的检查开销)。类型声明可以帮助编译器优化,某些情况下反而可能提升性能。
最佳实践
- 始终使用 strict_types=1:在新项目中默认启用
- 声明返回值类型:每个函数都应有返回值类型声明
- 优先使用具体类型:用
int而非mixed,用string而非int|string - 可空类型用 ?Type:
?int比int|null更简洁 - 联合类型优于 mixed:能确定可能的类型时使用联合类型
- void 返回值:不返回有意义的值时使用
void - never 返回值:抛出异常或终止脚本的函数使用
never(PHP 8.1+) - 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 安全运算符的使用。