declare(strict_types=1)必须放在文件第一行,因为它只对当前文件生效,且必须是字节流开头(前面不能有空格、bom、注释),放错位置等于未启用严格类型检查。

直接结论:PHP函数参数类型错误,90%以上源于没开strict_types=1、没写类型声明、或写了却忽略调用时的隐式转换陷阱。不是“写对了就没事”,而是“不主动拦住,它就悄悄错”。
为什么declare(strict_types=1)必须放在文件第一行
它只对当前文件生效,且必须是**字节流开头**(前面不能有空格、BOM、注释)。放错位置等于没开——比如写在<?php 之后但前面有空行,或写在命名空间声明之后,都会被忽略。
常见错误现象:add("1", "2")在弱类型下返回3,开了严格模式却仍返回3,说明strict_types根本没生效。
- 检查方式:在文件顶部加
var_dump(ini_get('zend.assertions'));没用,得用get_defined_constants(true)['Core']['ZEND_STRICT_TYPES'](仅调试) - IDE(如PhpStorm)不会报错,但PHPStan、Psalm这类静态分析工具会明确标出“strict_types not declared”
- Composer autoload 的文件也需各自声明,不能靠入口文件“带进来”
int、string这些类型声明到底拦什么
它只校验**传入值的运行时类型**,不校验值是否合法。比如function parse_id(int $id): string { return 'user_' . $id; },传0、-999、PHP_INT_MAX + 1(溢出成float)都过得了类型关,但业务上可能全是错的。
使用场景:适合做“契约兜底”,比如API层接收请求参数后,先过类型声明筛一轮,再进业务验证。
-
int拦不住"123"(弱类型下自动转),但strict_types=1开启后会抛TypeError -
array接受[]、['a' => 1],但不拦null——除非你写array|null并配合?? [] - 对象类型如
User $user只检查是否为User实例或其子类,不检查$user->id是否为空
参数默认值和null的隐藏冲突
传null不触发默认值,这是最常踩的坑。比如function send_mail(string $to, string $subject = 'Hi') {},调用send_mail('a@b.com', null)会直接报TypeError,而不是用默认主题。
性能影响:默认值本身无开销,但若默认值是函数调用(如date('Y-m-d')),它会在每次函数调用时执行,不管参数是否被省略。
- 想让
null也走默认逻辑,得手动判断:$subject = $subject ?? 'Hi'; - 数组参数更灵活:
function send_mail(array $opts = []) { $to = $opts['to'] ?? ''; $subject = $opts['subject'] ?? 'Hi'; } - PHP 8 命名参数可绕过位置限制,但定义时仍要遵守“默认参数右置”,且调用方必须显式写键名
手动验证该在类型声明前还是后
顺序错了,TypeError会先于你的InvalidArgumentException抛出,导致业务错误信息被掩盖。
正确做法:类型声明是第一道门,手动验证是第二道门。前者保底线,后者保业务。
- 先让PHP拦住明显错类型(如传
array进string),再用filter_var($email, FILTER_VALIDATE_EMAIL)查格式 - 避免重复校验:
is_string($name)在已有string $name声明时纯属冗余 - 对可选参数,类型声明写
string|null,再用if ($name !== null) { ... }分支处理,比isset()更准确
真正难的不是写对语法,而是意识到:类型声明不是银弹,它只回答“是不是这个类型”,不回答“这个值能不能用”。业务边界、空值语义、第三方输入污染——这些都得靠人盯住,机器只负责把“明显不对”的东西立刻挡在门外。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











