发布时间:
2026-09-30 05:30:02
最后更新:
2026-09-30 06:21:51
阅读:7 约1 分钟阅读
PHP 是动态类型语言,但现代 PHP 已经提供了相当完整的类型声明体系。参数、返回值、属性、联合类型、可空类型、接口和枚举等功能,让大型 PHP 项目可以建立比传统 PHP 代码更加明确的类型契约。
其中 declare(strict_types=1) 是很多开发者都会遇到的功能。
它的作用并不是把 PHP 变成 Java、C# 这类静态类型语言,也不是简单地打开一个“全项目严格检查开关”。它主要改变当前 PHP 文件中函数调用涉及的标量类型转换规则,让错误的数据类型更早暴露出来。
对于中大型 SaaS、API、后台系统以及长期维护的 PHP 项目,这种差异非常重要。
strict_types怎么开启
最常见的写法是:
prepare(
'SELECT * FROM users WHERE id = :id'
);
$stmt->execute([
'id' => $id
]);
类型声明和 SQL 参数绑定解决的是不同问题。
即使 $id 是 int,也不能因此形成“所有 SQL 注入问题都被 PHP 类型系统解决”的错误认识。
第三方库不需要因为strict_types而强行修改
Composer 项目经常同时包含自己的代码和大量第三方包。
如果自己的文件使用:
declare(strict_types=1);
并不意味着应该直接修改 vendor 目录里的第三方代码。
升级第三方包时,Composer 会重新安装这些文件,手工修改也会丢失。
如果遇到类型兼容问题,首先应该查看库本身支持的 PHP 版本、API 类型声明以及调用方式。
必要时,在自己的应用边界上进行明确的数据转换。
例如数据库、HTTP 请求、JSON 数据等外部输入本身就经常需要转换和验证。
严格类型并不意味着:
“所有输入进来以后都不能转换。”
正确的做法是让转换发生在明确的位置,而不是让类型转换散落在整个业务代码中。
外部输入本来就需要转换
假设 URL 中存在:
?id=123
PHP 从 HTTP 请求获得的查询参数通常是字符串。
业务层可能需要整数。
这时可以在输入边界进行明确验证和转换:
$id = filter_input(
INPUT_GET,
'id',
FILTER_VALIDATE_INT
);
if ($id === false || $id === null) {
throw new InvalidArgumentException('Invalid user ID');
}
然后再把已经验证过的整数传给业务函数:
$user = findUser($id);
这种设计比让业务层到处依赖隐式类型转换更加清晰。
严格类型并不是要求“永远不要转换类型”,而是鼓励开发者明确知道什么时候、为什么以及在哪里发生类型转换。
大型项目应该采用渐进式迁移
如果一个运行多年的 PHP 项目突然给所有文件加上:
declare(strict_types=1);
然后把所有参数和返回值一次性改成严格类型,很可能会产生大量问题。
原因并不是 strict_types 有问题,而是旧代码长期依赖了 PHP 的动态特性。
例如:
function getPrice($value)
{
return $value * 1.13;
}
这个函数可能被不同地方调用:
getPrice(10);
getPrice("10");
getPrice(null);
getPrice(false);
如果直接加入:
function getPrice(float $value): float
再配合严格模式,就可能暴露大量原来被隐式转换掩盖的调用问题。
这种情况下更适合渐进式迁移。
新代码首先使用严格类型。
修改旧模块时逐步增加参数和返回类型。
对数据库、HTTP、JSON、CLI 参数等外部输入建立明确的数据边界。
然后使用 PHPStan 或 Psalm 持续发现剩余类型问题。
PHPDoc仍然有价值
即使项目已经大量使用 PHP 原生类型声明,PHPDoc 仍然没有失去作用。
例如数组的结构:
/**
* @param array{id:int,email:string,active:bool} $user
*/
function processUser(array $user): void
{
// ...
}
原生 PHP 可以知道 $user 是数组,但不能完整表达所有复杂数组结构。
PHPStan、Psalm 等静态分析工具可以利用 PHPDoc 提供更加详细的信息。
因此现代 PHP 项目通常是原生类型声明与 PHPDoc、静态分析工具结合使用,而不是三选一。
strict_types不是性能优化功能
原文把 strict_types 与 PHP 7 JIT、类型推断以及性能损耗联系起来,这种说法容易让读者产生错误认识。
使用 strict_types=1 的主要目的不是优化 PHP 程序性能。
它的核心价值是明确类型契约、减少不必要的隐式转换以及让类型错误更早暴露。
在正常 SaaS 项目中,不应该把性能优化重点放在“strict_types 会不会让代码快几个百分点”。
真正影响 PHP 应用性能的通常是数据库查询、网络请求、缓存命中率、PHP-FPM worker、CPU、内存、I/O、序列化、第三方 API 以及应用本身的算法和架构。
如果项目存在实际性能问题,应该使用 profiling 工具测量,而不是根据是否启用 strict_types 猜测性能。
strict_types也不是安全机制
类型声明可以减少一部分逻辑错误,但不能把它当成安全防护系统。
它不能阻止 SQL 注入。
不能代替 CSRF Token。
不能代替 XSS 防护。
不能代替权限控制。
不能代替 SaaS 租户隔离。
不能代替身份认证。
不能阻止攻击者提交合法类型但恶意的数据。
例如:
function transferMoney(int $from, int $to, float $amount): void
这个函数的类型声明非常清晰。
但如果没有检查当前用户是否拥有 $from 账户的控制权,那么即使所有参数类型都正确,系统仍然可能存在严重的越权漏洞。
一个更合理的PHP项目规范
对于新开发的 PHP 8.x 项目,可以考虑把严格类型作为默认编码规范: