热点





PHP strict_types 怎么开启?严格类型检查对项目开发有哪些帮助

发表时间: 2026-09-30 05:30:02   人气:89 次   

PHP 是动态类型语言,但现代 PHP 已经提供了相当完整的类型声明体系。参数、返回值、属性、联合类型、可空类型、接口和枚举等功能,让大型 PHP 项目可以建立比传统 PHP 代码更加明确的类型契约。

其中 declare(strict_types=1) 是很多开发者都会遇到的功能。

它的作用并不是把 PHP 变成 Java、C# 这类静态类型语言,也不是简单地打开一个“全项目严格检查开关”。它主要改变当前 PHP 文件中函数调用涉及的标量类型转换规则,让错误的数据类型更早暴露出来。

对于中大型 SaaS、API、后台系统以及长期维护的 PHP 项目,这种差异非常重要。

strict_types怎么开启

最常见的写法是:


declare(strict_types=1);

function add(int $a, int $b): int
{
return $a + $b;
}

declare(strict_types=1); 必须放在文件开头的声明区域。

它是文件级声明,不是 php.ini 中的全局 PHP 配置,也不存在一个可以在 composer.json 中直接设置 strict_types=1 的项目级开关。

因此,如果项目有多个 PHP 文件,每个需要使用严格标量类型调用规则的文件通常都应该明确声明:

declare(strict_types=1);

现代 PHP 项目通常可以制定编码规范,要求新 PHP 文件默认使用这个声明。

strict_types到底改变了什么

这里最容易产生误解。

PHP 的类型声明本身一直可以用于约束函数参数和返回值。例如:

function calculate(int $value): int
{
return $value * 2;
}

问题在于 PHP 对标量类型存在一定的类型转换行为。

在没有严格类型声明的情况下,某些调用可能发生标量类型转换。

例如:

function calculate(int $value): int
{
return $value * 2;
}

calculate("10");

在弱类型模式下,字符串 "10" 可能被转换为整数 10。

如果调用方传入的是:

calculate("hello");

由于这个字符串不能正常转换为整数,运行时仍可能产生 TypeError,并不是所有错误都可以通过弱类型模式“自动转换解决”。

启用严格模式以后:


declare(strict_types=1);

calculate("10");

这里就会因为参数类型不匹配而抛出 TypeError。

这意味着 strict_types 的核心价值并不是“所有类型错误突然都能被发现”,而是减少 PHP 在标量参数调用过程中替你进行的隐式转换。

它不是全局严格类型系统

假设项目有两个文件:

// A.php
declare(strict_types=1);

foo("123");

以及:

// B.php
foo("123");

不能简单理解成“只要 A.php 开启了 strict_types,整个项目以后都是严格模式”。

strict_types 是调用文件级别的行为控制。

这也是为什么大型项目应该把它纳入代码规范,而不是试图寻找一个所谓的全局开关。

第三方 Composer 包是否使用 strict_types,也应该尊重其自身代码设计。不能因为自己的项目启用了严格模式,就认为所有依赖库的代码都会自动变成严格模式。

类型声明本身比strict_types更重要

很多开发者讨论严格类型时,只盯着这一行:

declare(strict_types=1);

但真正构成现代 PHP 类型安全基础的是完整的类型声明。

例如:

function findUser(int $id): ?User
{
// ...
}

这里同时表达了几个信息。

参数必须是整数。

返回值可以是 User。

也可以返回 null。

再例如 PHP 8 的联合类型:

function formatId(int|string $id): string
{
return (string) $id;
}

它明确告诉调用者,这个函数设计上允许整数或者字符串。

这比在代码内部写大量“猜测输入类型”的逻辑更加清晰。

strict_types并不会让PHP变成静态类型语言

这是理解现代 PHP 类型系统非常重要的一点。

PHP 仍然是动态类型语言。

strict_types=1 不会让 PHP 在程序运行之前像传统静态类型语言那样完成整个项目的类型检查。

PHP 的类型声明主要在运行时参与验证。

例如:

declare(strict_types=1);

function saveUser(int $id): bool
{
// ...
}

如果运行时传入错误类型,PHP 可以抛出 TypeError。

而 PHPStan、Psalm 等静态分析工具,则是在程序运行之前分析源代码中的类型关系。

两者解决的问题不同。

可以把它们理解为两层防线。

PHP 原生类型声明负责运行时契约。

PHPStan 或 Psalm 负责静态分析。

两者结合以后,大型项目的类型问题可以在更多阶段被发现。

strict_types对数组没有原文描述的那种作用

原文举例说,如果函数要求数组参数,传入字符串以后严格模式可以避免“将字符串转换为空数组”。

这个例子本身就不准确。

PHP 不会因为普通弱类型模式就把任意字符串自动转换成空数组,然后让:

function process(array $data)

悄悄接受它。

数组、对象等类型和标量类型的行为并不能简单套用 strict_types 的规则。

因此不能把 strict_types 描述成“任何类型不匹配都会从自动转换变成 TypeError”。

应该准确理解为,它主要改变函数调用中的标量类型强制转换行为。

返回值也应该明确声明

现代 PHP 项目应该尽可能为函数和方法声明返回类型。

例如:

function getUserName(int $id): string
{
return "Brian";
}

如果某个分支错误地返回了数组:

return [];

运行时就会产生类型错误。

这比函数返回什么完全取决于调用方“猜测”要可靠得多。

对于数据库查询、服务层、Repository、API Client 等核心代码,明确返回类型尤其有价值。

例如:

function getUser(int $id): ?User
{
// 找不到用户返回 null
}

调用者看到函数签名,就可以知道“找不到用户”属于正常结果,而不是等到运行程序以后才发现。

typed properties同样重要

类型安全不应该只停留在函数参数。

现代 PHP 还支持类型化属性:

class User
{
public int $id;
public string $email;
public bool $active;
}

这可以让对象状态更加明确。

如果业务对象中的 id 应该永远是整数,那么把它声明成 int 比使用没有类型限制的公共属性更加清晰。

当然,类型声明并不能保证业务逻辑一定正确。

一个 int 类型的用户 ID 仍然可能指向不存在的用户。

类型系统解决的是数据类型契约,而不是业务规则。

strict_types不能替代输入验证

例如:

function deleteUser(int $id): void
{
// ...
}

即使启用了严格类型,也不能因此认为:

“只要 $id 是 int,就可以安全删除用户。”

攻击者可能提交一个完全合法的整数,但这个整数对应的是另一个租户的用户。

因此 SaaS 系统仍然必须检查:

用户是否已经登录?

用户属于哪个租户?

这个对象是否属于当前租户?

当前账户是否具有删除权限?

是否需要重新认证?

是否需要审计日志?

类型安全和授权安全是完全不同的层次。

strict_types也不能替代数据库安全

同样:

function findUser(int $id): ?User
{
// ...
}

并不能保证 SQL 安全。

数据库查询仍然应该使用参数化查询:

$stmt = $pdo->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 项目,可以考虑把严格类型作为默认编码规范:


declare(strict_types=1);

然后逐步建立完整类型体系。

函数参数尽可能声明类型。

返回值尽可能声明类型。

类属性使用 typed properties。

可以使用联合类型和可空类型表达真实业务情况。

核心领域对象尽量避免大量 mixed。

公共 API 和服务层应该明确输入输出。

数据库和 HTTP 输入在边界处完成验证和转换。

再通过 PHPStan 或 Psalm 对整个项目进行静态分析。

这样形成的是一个完整的类型治理体系,而不是单独依赖 strict_types。

对于旧项目,则不应该为了追求形式上的“全项目严格模式”而一次性重构所有代码。

更实际的方式是从新模块、核心服务层和高风险业务开始。

随着测试覆盖率提高,再逐步增加类型声明。

如果某个旧函数确实需要接受多种类型,就应该明确表达这种需求,例如:

function normalizeId(int|string $id): int
{
// ...
}

而不是为了满足某种编码规范,强行把真实业务需求隐藏起来。

现代 PHP 的类型系统真正有价值的地方,不是让 PHP 变成一门完全静态类型语言,而是让开发者逐渐减少“这个变量应该是什么类型,全靠运行时才知道”的情况。

strict_types=1 是其中的一部分,但不是全部。

当原生类型声明、typed properties、联合类型、接口、返回类型、PHPDoc、PHPStan 或 Psalm、自动化测试以及明确的数据边界结合起来以后,PHP 项目才会逐渐形成可维护的类型契约。

对于小型脚本,这种规范可能显得有些正式;对于长期运行的 SaaS、支付系统、后台管理系统和大型 API 项目,明确类型带来的维护收益通常远远超过最初增加的代码工作量。

严格类型真正改变的,是开发者处理数据的方式:不是等 PHP 替你猜测这个值应该是什么,而是在代码边界明确告诉程序,这个函数接受什么、返回什么,以及哪些转换必须由开发者主动完成。

分享 Facebook | X | Whatsapp | Linkedin

捐助(Paypal): https://www.paypal.me/observeccp
订阅中国观察电报 Telegram : https://t.me/s/ObserveCCP

责任编辑:雨轩  来源:中国观察  转载请注明作者、出处並保持完整。



今日热点:
更多新闻...
近期焦点:
最新视频

推荐阅读:


更多推荐文章:


相关新闻


更多新闻


推荐文章

德州恢复邮寄投票安全规则 美国选举正在重新补上疫情时代留下的制度漏洞

美国第五巡回上诉法院8月12日推翻联邦地区法院此前针对德州《选举诚信法案》部分条款的永久禁令,恢复了9项与邮寄投票和选民协助有关的安全措施。案件争议的核...

2026年8月23日   28100

AI科技股为何突然暴跌 盛宴背后的债务、估值与回报考验

过去几年,人工智能几乎成为全球资本市场最强大的增长叙事。英伟达的芯片、超大规模云计算企业的数据中心、服务器制造商的订单,以及为 AI 提供算力的基础设施...

2026年10月15日   201986

金正恩死亡传闻引爆网络 朝鲜断网与金与正接班说法缺乏证据

发表:2026-10-23 22:07:23 | 更新:2026-10-23 22:07:23  人气: 2464

金正恩死亡传闻引爆网络 朝鲜断网与金与正接班说法缺乏证据

本文由中国观察編輯部原创。为了促进信息自由传播,本平台原创内容全面开放转载,无须申请授权,只需注明来源、保留原文链接,即可全文转载、引用、翻译。我们不追究版权,只在乎公正的传播。

捐助(Paypal): https://www.paypal.me/observeccp
订阅中国观察电报 Telegram : https://t.me/s/ObserveCCP

最近文章:
更多文章...


近期要闻:
更多新闻...

编辑推荐文章:

最新发表   ⏬
更多文章...
疫情