为什么需要类型标注?
如果学过 ts/python 就知道为什么
复杂度不会凭空消失, 只会转移
为了语法方便而使用脚本,而不是强类型语言,那么更多的活就要交给解释器来做
比如: 推断变量类型, 以便分配所需的内存空间, 然后再执行
现在, 编码人员把这个推断类型的活做了, 那么解释器大可以不必推断变量需要多少内存 直接使用你标注的类型对应需要的内存, 然后直接执行即可, 那么速度肯定会快一些
支持 PHP 类型标注版本历史
PHP的类型系统是逐步完善的:
- PHP 7.0 (2015年): 引入标量类型声明(int, float, string, bool)
- PHP 7.1: 支持可空类型(?type)
- PHP 7.2: 增加object类型
- PHP 7.4 (2019年): 引入属性类型声明(类属性类型)
- PHP 8.0 (2020年): 引入联合类型、static返回类型、mixed类型
- PHP 8.1: 增加交集类型、readonly属性
- PHP 8.2: 进一步完善类型系统
类型标注的作用
- 提高代码可读性: 明确参数和返回值类型, 使代码自解释
- 增强IDE支持: 提供更好的自动补全、导航和重构功能
- 错误前置: 早期捕获类型错误, 而不是在业务逻辑执行时才发现
- 静态分析基础: 为 PHPStan、Psalm 等静态分析工具提供基础
- 代码质量提升: 减少类型相关的bug, 使API契约更加明确
- 性能优化: 在某些情况下, PHP 引擎可以对类型明确的代码进行优化
使用类型标注的模式
- 宽松模式: 默认的模式, 为了更好的兼容
- 严格模式: 要开启严格模式, 必须在文件顶部声明
php
<?php
// 宽松模式
function sum(int $a, int $b): int {
return $a + $b;
}
$res = sum("5", "10"); // 成功运行, 尝试将字符串转换为整数
echo $res;php
<?php
declare(strict_types=1);
// 严格模式
function sum(int $a, int $b): int {
return $a + $b;
}
// $res = sum("5", "10"); // 无法运行, 会直接报错
// Fatal error: Uncaught TypeError: sum(): Argument #1 ($a) must be of type int, string given,
$res = sum(5, 10); // 可以运行, 不会报错
echo $res;使用 PHP 类型标注
可用的类型
php
<?php
declare(strict_types=1);
// 通用类型
function f1(
string $name, // string 类型
int $age, // int 类型
float $price, // float 类型
bool $isMale, // bool 类型
array $hobbies, // array 类型(与ts不同, 无法限制元素的具体类型)
mixed $other, // mixed 类型: 相当于 typescript 的 any 类型
callback $func, // callback 类型: 必须传入函数(与ts不同, 不能限制参数)
iterable $items, // iterable 类型: 可以被 foreach 语法迭代的类型
) {
var_dump($name, $age, $price, $isMale, $hobbies, $other);
$func();
}
// 联合类型
function f2(
int|float $a, // int|float 类型
int|float $b, // int|float 类型
int|null $c // int|null 可选类型(就是其他类型与null的联合类型)
){
$res = $a + $b;
if ($c) {
$res += $c;
}
return $res;
}
// 仅函数返回值可用类型
function f3(): void {
// void: 表示没有返回值
}
function f4(): never {
// never: 表示永远不会返回
throw new Exception('never');
}普通变量无法标注
这点与 TS 和 Python 不一样, 千万注意
php
<?php
declare(strict_types=1);
// 普通变量: 无法像 ts 那样直接设置标注, 需使用注释, 感觉很鸡肋
/* @var string $name */
$name = "John";
/* @var int $name */
$age = 30;
/* @var float $name */
$price = 19.99;
/* @var boolean $name */
$isActive = true;
var_dump($name, $age, $price, $isActive);
echo "<hr/>";函数参数与返回值
php
<?php
declare(strict_types=1);
function f1(
string $name, // string 类型
int $age, // int 类型
float $price, // float 类型
bool $isMale, // bool 类型
mixed $other // mixed 类型: 相当于 typescript 的 any 类型
): void {
var_dump($name, $age, $price, $isMale);
}类
php
<?php
declare(strict_types=1);
class User {
public string $name;
public int $age;
public float|null $price = null;
public array $hobbies = [];
// 构造函数可以不用标注返回值
public function __construct(string $name, int $age) {
$this->name = $name;
}
// 返回当前类的类型
public function addItem(mixed $item): self {
return $this; // 为了支持链式调用
}
// 返回当前类的类型2
public function deleteItem(mixed $item): User {
return $this;
}
public static function getInstance(): static {
return new static(); // 返回当前类的实例(后期静态绑定)
}
}
$user = new User('tom', 10);
$user->addItem('')->deleteItem('');
echo "<pre>";
var_dump($user);
echo "</pre>";
// object(User)#1 (3) {
// ["name"]=> string(3) "tom"
// ["age"]=> uninitialized(int)
// ["price"]=> NULL
// ["hobbies"]=> array(0) { }
// }
// 可以将类/接口当作类型
function f1(User $user) {
$user->addItem('')->deleteItem('');
}
f1();接口 & 交集类型
php
<?php
declare(strict_types=1);
interface IRunable {
public function run(): void;
}
interface IReadable {
public function read(): void;
}
interface IWriteable{
public function write(): void;
}
class TxtFile implements IReadable, IWriteable {
public function read():void {}
public function write():void {}
}
class PhpFile implements IRunable, IReadable, IWriteable {
public function run():void {}
public function read():void {}
public function write():void {}
}
function write(IReadable & IWriteable $file) {
// IReadable & IWriteable 表示: 传入的参数 $file 必须同时实现这两个接口
}
function execute(IRunable & IReadable & IWriteable $file) {
// 表示传入的参数 $file 必须同时实现 IRunable, IReadable, IWriteable 3个接口
}
// 这都没有问题, 传入的对象都实现了对应的接口
// write(new TxtFile());
// execute(new PhpFile());
// 这样就不行, TxtFile 这个类并没有实现 IRunable 接口, 也就是说它并没有 execute 方法
// Fatal error: Uncaught TypeError: execute(): Argument #1 ($file) must be
// of type IRunable&IReadable&IWriteable, TxtFile given,
execute(new TxtFile());