写 PHP 时间久了,经常会出现一种情况:代码里明明给一个数组变量写了说明,编辑器却仍然提示类型是 mixed,自动补全也不可用。问题通常不在编辑器,而在于注释没有按照 PHPDoc 的规则写。PHP 本身是动态类型语言,变量不需要提前声明类型,但项目一复杂,这种灵活性反而会变成维护负担。于是 PHP 社区通过 PHPDoc 的 @var 标记,给变量补充类型信息,让阅读代码的人和工具都能准确理解变量含义。

一、PHP 变量注释指的不是普通注释
如果把 PHP 变量注释单纯理解为给变量写一句说明,往往只看到了一半。例如在变量上方写 // 用户名,人确实能看懂,但 IDE 不会把它当作类型信息。PHP 原生语法里只有单行注释、块注释和文档注释,真正对变量类型起作用的,是符合 PHPDoc 规范的文档注释块。PHPDoc 起源于 JavaDoc,用 /** ... */ 包裹,并通过 @var 这样的标签告诉工具某一项的语义。
@var 注释并不是 PHP 运行时的强制约束,PHP 解释器会完全忽略注释内容,变量的实际类型仍然由赋值决定。但 PHPStorm、VSCode 的 Intelephense 插件以及 PHPStan、Psalm 等静态分析工具会读取这些注释,在代码运行前检查类型是否一致。因此,@var 注释的价值主要体现在开发体验和工程质量上,而不是语法层面。
理解这一点后,就不会把普通注释和 PHPDoc 注释混为一谈。写变量注释,本质是在给编辑器和静态分析工具提供类型元数据。
二、PHPDoc @var 的语法与常见位置
标准的 @var 注释格式为:/** @var 类型 变量名 描述 */。类型在前,变量名在后,描述用来补充业务含义。一个典型的类成员属性注释如下:
class User
{
/** @var string 用户名 */
private string $name;
}
PHP 7.4 以后类属性已经支持原生类型声明,此时 @var 更多是补充描述,或者在类型是数组等复合结构时提供更细的信息。例如一个只存放用户 ID 的数组,单独写 array 不够精确,用 @var int[] 或 @var array<int> 可以告诉工具元素类型。
局部变量同样可以用 @var 注释,但要注意注释必须紧挨变量声明。下面代码展示如何标注一个从 JSON 解码得到的数组:
/** @var array<string, mixed> $config */ $config = json_decode($jsonString, true); /** @var int $userId 当前登录用户ID */ $userId = (int) $_GET['id'];
@var 还可以出现在 foreach 循环中,尤其是遍历一个对象集合时。假设 fetchUserList() 返回的类型没有在方法签名中明确,循环前可以这样标注:
/** @var User[] $users */
$users = fetchUserList();
foreach ($users as $user) {
echo $user->getName();
}
这里 @var 使用了 User[] 表示 User 对象数组。静态分析工具读到这个注释后,会知道 $user 是 User 实例,从而提供 getName() 的自动补全和类型检查。如果不写,$users 可能被推断为 mixed 或 array,循环里的 $user 会被当成未知类型。
三、变量注释规范:类型怎么写才准确
变量注释能不能发挥价值,取决于类型写得是否准确。PHP 类型系统包含标量类型、类名、数组、联合类型、可空类型以及泛型数组等形式。@var 注释中的类型写法应该和这些形式保持一致,才能被工具准确识别。
常见的类型标注方式如下:
/** @var int|null 可能为空的整数 */
$count = null;
/** @var string|array 字符串或数组 */
$result = '';
/** @var array<string, User> 键为字符串,值为User对象 */
$userMap = [];
/** @var array<int, array{id: int, name: string}> 结构化数组 */
$rows = [];
在实际项目中,联合类型和可空类型最容易写错。早期 PHPDoc 使用 string|null 表示可空,PHP 8 原生联合类型则写作 ?string 或 string|null。工具对两者都支持,但团队最好统一下来,避免新旧混用。数组元素类型建议始终写明,array 只表示数组,无法让工具推导出元素结构。
另一个常见问题是变量名与代码不一致。例如写 /** @var string $name */ 但实际变量叫 $username,工具会认为这是两个不同的声明。还有些开发者只写类型不写变量名,比如 /** @var string */ 后面的变量不一定能被正确关联,尤其是同一行有多个变量时。因此规范上要求:类属性和局部变量都应写清变量名。
四、常见误区和静态分析实践
第一个误区是用普通单行注释代替 @var。比如 // int $id 这样的写法,PHPStorm 默认不会把它解析成类型信息,静态分析工具也不会采信。第二个误区是给所有变量都添加 @var,包括像 $i = 0 这种一眼就能看出的循环计数器,这会让注释噪音增大,降低可读性。
静态分析工具可以在不运行代码的情况下检查注释和赋值的匹配度。比如下面的代码,如果 @var 标注为 string,但赋值为 int,PHPStan 会报告类型不匹配:
/** @var string $code */ $code = 200;
这种检查能提前发现很多隐藏 bug。如果团队使用 PHPStan 或 Psalm,建议在配置中开启较高等级,让变量注释真正生效。不过在 PHP 8 之后,类属性、函数参数和返回值大多可以用原生类型声明替代 @var 的一部分功能,局部变量和数组元素类型仍然依赖 @var 注释。
总结来说,PHP 变量注释的核心是 PHPDoc 的 @var 标记。写法上要遵循类型、变量名、描述的顺序,类型信息尽量精确,避免冗余注释。只有把注释纳入团队规范,并借助 IDE 和静态分析工具持续检查,才能让动态类型的 PHP 代码获得接近静态语言的开发体验。