Skip to content

为什么需要类型标注?

如果学过 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());

Released under the MIT License.