PHP 关键字与语言特性详解

PHP 关键字与语言特性字典。每条给语义、版本约束、多场景示例、边界与坑。PHP 8.x 新语法(enum/match/readonly/属性提升)已并入条目。

1. declare(strict_types=1) – 严格类型模式

描述

文件级开关,必须写在文件最顶部(在 namespace 之前)。开启后参数/返回值类型不匹配直接抛 TypeError,不再做隐式转换。只影响当前文件。

语法和示例

<?php
declare(strict_types=1);
function add(int $a, int $b): int { return $a + $b; }
add(1, 2);          // 3
add('1', 2);        // TypeError(默认模式会静默转成 1)
add(1.5, 2);        // TypeError(float 不会截断成 int)
// 但函数内部仍可隐式转换
function loose(int $a) { $b = $a + 1.5; }  // $b 是 float,合法

注意事项

  • strict_types 必须写在文件第一行(注释之外),否则 fatal error
  • 只对「本文件内定义/调用的函数」生效——调用别处函数时以被调用方文件的设置决定
  • 返回值声明同样受严格检查:声明 int 就不能返回 ‘1’
  • 严格模式不等于「类型安全」——数组元素类型、对象属性都不检查
  • 推荐新项目全开;存量项目逐步开(先从新文件开始)

2. 变量作用域与超全局 $GLOBALS

描述

函数/方法内不可见外部变量,除非 global 声明或使用 $GLOBALS。超全局数组($_GET/$_POST/$_SERVER/$_SESSION/$_COOKIE/$_FILES/$_ENV/$_REQUEST)在任何作用域直接可见。

语法和示例

<?php
$count = 0;
function bump(): void {
    global $count;          // 显式引入全局
    $count++;
    $GLOBALS['count']++;    // 等价写法
}
function demo(): void {
    // echo $count;         // 报 Warning: undefined variable
    $local = 1;             // 函数内局部变量
}
// 超全局:无需 global
function read(): string {
    return $_SERVER['HTTP_HOST'] ?? '';
}
// 静态变量:跨调用保持
function counter(): int {
    static $n = 0;          // 只初始化一次
    return ++$n;
}

注意事项

  • global 使函数依赖外部状态,可读性与可测性都差——优先用参数传值
  • 静态变量在 PHP-FPM 下是「每次请求重置」的,不能当进程级缓存
  • $GLOBALS 是超全局,任何作用域可用,但同样难维护
  • PHP 8.1+ 未定义变量从 Notice 升为 Warning,读未定义数组键需 isset/??
  • 作用域里 include 文件的变量可见性继承当前作用域,容易踩坑

3. 类型:松散比较 == 与严格比较 ===

描述

PHP 8 收紧松散比较:数字与字符串比较时,仅当字符串是「合法数字字符串」才按数字比,否则按字符串比。=== 同时比较值与类型。

语法和示例

<?php
var_dump(0 == 'abc');    // false(PHP 8;PHP 7 是 true)
var_dump(1 == '1');      // true(合法数字串)
var_dump(1 === '1');     // false(类型不同)
var_dump('1e3' == '1000'); // true(都是数字串)
var_dump(null == false); // true
var_dump('' == 0);       // false(PHP 8)
var_dump([] == false);   // true
// 安全比较
in_array('1', [1, 2], true);   // 严格模式
array_search(0, ['a', 0], true);

注意事项

  • 永远用 === 与 in_array(…, true)——== 的历史坑太多
  • null == false 为 true 是 PHP 的特殊规则,容易误判
  • 字符串与数字比较在 PHP 8 前有巨大坑(0 == ‘abc’ 为 true),升级时注意行为变化
  • 浮点数比较不要用 == ,用 abs($a – $b) < 精度阈值
  • == 与 === 在数组比较上语义不同(顺序敏感、类型敏感)

4. array – 数组(有序哈希映射)

描述

PHP 数组把 list 与 dict 合一:键只能是 int 或 string,按插入顺序排序。赋值是写时复制(值语义)。

语法和示例

&lt;?php
$list = ['a', 'b', 'c'];             // 键 0,1,2
$map  = ['go' =&gt; 1, 'php' =&gt; 2];
$list[] = 'd';                       // 尾部追加
$map['js'] = 3;
// 键的类型转换规则
$a = [true =&gt; 1];    // 键转 1
$b = ['1' =&gt; 1];     // 数字字符串键转 int 1
$c = [1.5 =&gt; 'x'];   // 浮点键截断为 int 1
// 遍历(遍历的是快照,内部改动不影响本次)
foreach ($map as $k =&gt; $v) { }
// 拷贝与引用
$copy = $list;                       // 值拷贝(写时复制)
$ref  = &amp;$list;                      // 引用
unset($list[1]);                     // 删键(不重排)
$reindexed = array_values($list);

注意事项

  • 键为 float 会被截断成 int;bool 转 0/1;null 键变空字符串
  • unset 后数组键不会重排,json_encode 会变成对象而不是数组——需 array_values 重建
  • 数组赋值是值语义,但对象赋值是引用语义($obj2 = $obj1 是同一对象)
  • foreach 里修改数组:改值不影响遍历(快照),但改引用 &$v 会影响
  • 数组合并:+ 保左优先,array_merge 数字键重排、字符串键覆盖

5. match 表达式(8.0+)

描述

switch 的表达式版:严格比较(===)、无 fall-through、可返回值、必须穷尽或给 default。用 match(true) 可做条件分发。

语法和示例

&lt;?php
$msg = match ($code) {
    200, 201 =&gt; '成功',
    302 =&gt; '重定向',
    404 =&gt; '未找到',
    default =&gt; '其他',
};
// 条件分发(替代 switch + if 链)
$level = match (true) {
    $score &gt;= 90 =&gt; 'A',
    $score &gt;= 80 =&gt; 'B',
    $score &gt;= 60 =&gt; 'C',
    default =&gt; 'F',
};
// 返回值可直接用
$len = match (strlen($s)) { 0 =&gt; 'empty', default =&gt; 'ok' };

注意事项

  • match 用 === 严格比较,’1′ 与 1 不匹配(switch 是松散比较)
  • 没有 default 且未穷尽时抛 UnhandledMatchError(不是返回 null)
  • match 的分支只能是一个表达式,不能写多语句(要复杂逻辑先抽函数)
  • match(true) 模式要按范围从大到小或小到大排列,先命中的先返回
  • PHP 8.0 才有,低版本用 switch 或 if 链

6. 构造器属性提升(8.0+)与 readonly(8.1+)

描述

构造器参数直接声明为类属性,省去手写声明与赋值。配 readonly 实现不可变属性(只能初始化一次)。8.2+ 支持 readonly 类。

语法和示例

&lt;?php
class Point {
    public function __construct(
        public readonly float $x,
        public readonly float $y,
        private string $label = 'pt',
    ) {}
}
$p = new Point(1.0, 2.0);        // 其余参数用默认值
// $p-&gt;x = 9;                     // Error:readonly 不可改
// 只读类(8.2+):所有属性自动 readonly
readonly class Config {
    public function __construct(public string $env) {}
}
// 可见性可混合:public/private/protected + readonly

注意事项

  • readonly 属性只能在声明类的作用域内初始化(不能从外部写)
  • 提升的参数不能有默认值之外的初始化表达式(如 new Foo() 目前允许但需注意)
  • readonly 不保证对象内部不可变(引用类型属性仍可变)
  • 提升的可选参数不能与可变参数一起用(顺序限制)
  • 反射修改 readonly 属性会抛 Error(8.1 起)

7. enum 枚举(8.1+)

描述

纯枚举(无值)或 backed 枚举(带 int/string 值)。自带 cases() 列出全部、from/tryFrom 从值还原。可实现接口、带方法与常量。

语法和示例

&lt;?php
enum Status: string {
    case Draft = 'draft';
    case Published = 'published';
    public function label(): string {
        return match($this) {
            self::Draft =&gt; '草稿',
            self::Published =&gt; '已发布',
        };
    }
}
Status::cases();                 // 全部枚举项数组
Status::from('draft');           // Status::Draft(非法值抛 ValueError)
Status::tryFrom('x');            // null(不抛)
Status::Draft-&gt;label();          // '草稿'
Status::Draft-&gt;value;            // 'draft'
// 纯枚举
enum Suit { case Heart; case Spade; }
// 可实现接口
enum Level: int implements JsonSerializable { case Low = 1; }

注意事项

  • enum 的 case 是单例对象,不能 new,也不能 clone
  • backed enum 的值必须唯一(重复值 fatal)
  • enum 不能有状态(属性),只能有常量与方法
  • from 遇非法值抛 ValueError;tryFrom 返回 null——外部输入用 tryFrom
  • json_encode 默认输出枚举的 value,要自定义实现 JsonSerializable

8. trait – 横向复用

描述

方法集合可注入多个类,绕过单继承限制。可含属性、抽象方法、静态方法。同名冲突用 insteadof 排除、as 改名。

语法和示例

&lt;?php
trait Timestampable {
    public ?string $createdAt = null;
    public function touch(): void { $this-&gt;createdAt = date('c'); }
    abstract public function id(): int;    // 要求使用方实现
}
class Post {
    use Timestampable;
    public function id(): int { return 1; }
}
// 冲突处理
trait A { public function hi() { return 'A'; } }
trait B { public function hi() { return 'B'; } }
class C {
    use A, B {
        A::hi insteadof B;    // 用 A 的实现,排除 B
        B::hi as hiB;         // 把 B 的实现改名保留
    }
}

注意事项

  • trait 不是类型——不能 instanceof、不能被 extends
  • trait 的属性与类属性同名且默认值不同会 fatal error
  • trait 里的 $this 指向使用它的类实例
  • 静态方法在 trait 里可用,但 static:: 后期绑定仍按使用类解析
  • 过度使用 trait 会让依赖关系难追踪,优先考虑组合(依赖注入)

9. 可见性 private/protected/public

描述

对象级可见性。与 Java 不同:同类的不同实例可互相访问私有成员。PHP 8.4 支持非对称可见性(public private(set) 这种)。

语法和示例

&lt;?php
class Wallet {
    private int $balance = 0;
    public function __construct(private readonly string $owner) {}
    public function transferFrom(Wallet $other, int $n): void {
        $other-&gt;balance -= $n;      // 同类实例可访问私有!
        $this-&gt;balance += $n;
    }
    protected function audit(): void {}
}
// 8.4 非对称可见性
class Post {
    public private(set) string $title = '';
}

注意事项

  • protected 在子类可访问,但只能通过 $this 或父类作用域,不能访问「另一个实例的」protected
  • private 方法不被子类继承——子类同名方法独立存在
  • 接口方法必须 public;抽象方法可见性由实现类决定
  • 属性类型声明后不可赋其他类型(PHP 有类型属性,8.0+)

10. 命名空间与 use 导入

描述

namespace 声明当前文件所属命名空间(必须在首行,declare 之后);use 导入类/函数/常量并可别名。PSR-4 目录与命名空间一一对应。

语法和示例

&lt;?php
namespace App\Service;
use App\Model\User;
use App\Model\{Order, Item};           // 组导入
use App\Model\User as U;               // 别名
use function App\Util\format;          // 导入函数
use const App\Util\LIMIT;              // 导入常量
// 全局类需反斜杠
new \DateTimeImmutable();
// 当前命名空间引用
self::class;  static::class;
// 相对命名空间
namespace\sub\foo();

注意事项

  • namespace 必须是文件第一条语句(除 declare 与注释)
  • 同一个文件可以有多个 namespace(不推荐,会影响可读性)
  • use 只影响编译期的名字解析,不触发加载——加载靠 autoload
  • 全局函数(如 strlen)未导入时 PHP 会回退到全局命名空间,但类不会
  • PSR-4 要求命名空间与目录层级严格对应,否则 autoload 找不到

11. null 合并 ?? 与三目 ?:

描述

?? 只在左侧为 null/不存在时取右(不报未定义键);?: 在左侧为任意 falsy(0/”/’0’/[]/null/false)时取右。两者语义完全不同。

语法和示例

&lt;?php
$page = $_GET['page'] ?? 1;        // 键不存在也不报
$name = $input['name'] ?? '匿名';
$deep = $a['b']['c'] ?? null;      // 链式安全
// 与 ?: 的差异(关键)
$zero = 0;
$r1 = $zero ?? 10;                 // 0(0 不是 null)
$r2 = $zero ?: 10;                 // 10(0 是 falsy)
$empty = '';
$r3 = $empty ?? 10;                // ''
$r4 = $empty ?: 10;                // 10
// 链式
$v = $a ?? $b ?? $c ?? 'default';

注意事项

  • ?? 不做类型转换,「存在且为 null」时才取右——精确符合 isset 语义
  • ?: 会把 ‘0’ 与 [] 都视为空,容易把合法的 0 值误替换
  • ?? 右侧表达式惰性求值(左侧有值时不执行)
  • 嵌套 ?? 从左到右取第一个非 null,不会短路在 falsy 上
  • 8.0+ 的 nullsafe 操作符 ?-> 用于方法链,与 ?? 组合使用

12. 异常体系 throw/try/catch/finally

描述

Throwable 分 Error(引擎错误)与 Exception。PHP 8 把多数引擎错误改为 Error 子类(TypeError/ValueError 等),catch Exception 不再能捕获它们。

语法和示例

&lt;?php
try {
    risky();
} catch (TypeError | ValueError $e) {     // 多类型(7.1+)
    // PHP 8 引擎异常也走这里
} catch (RuntimeException $e) {
    throw new AppException('包装', previous: $e);   // 命名参数传链
} catch (Throwable $e) {
    error_log((string) $e);                // 兜底
} finally {
    cleanup();                             // 无论成败都执行
}
// 自定义异常
class AppException extends RuntimeException {}

注意事项

  • catch Exception 捕不到 Error(TypeError/ArithmeticError 等)——需要 catch Throwable
  • finally 在 return 之前执行;finally 里 return 会覆盖返回值
  • 异常链用第三个参数 previous 传,error_log 不会自动打堆栈
  • PHP 8 前很多错误是 Warning 不抛异常——用 set_error_handler 转异常
  • 不要用异常控制正常流程(性能与可读性都差)

13. __construct/__destruct 与魔术方法

描述

魔术方法以双下划线开头:__construct/__destruct/__get/__set/__call/__toString/__invoke/__clone/__serialize。定义对象生命周期与动态行为。

语法和示例

&lt;?php
class Magic {
    private array $data = [];
    public function __get($name) { return $this-&gt;data[$name] ?? null; }
    public function __set($name, $v) { $this-&gt;data[$name] = $v; }
    public function __isset($name) { return isset($this-&gt;data[$name]); }
    public function __call($m, $args) { return "调用 $m"; }
    public function __toString(): string { return 'Magic'; }
    public function __invoke(): string { return 'callable'; }
    public function __construct() {}
    public function __destruct() {}          // 对象销毁时
}
$m = new Magic();
$m-&gt;foo = 1;                              // 走 __set
$m-&gt;foo;                                  // 走 __get
$m();                                     // 走 __invoke
echo $m;                                  // 走 __toString

注意事项

  • 魔术方法必须 public,否则无效(会警告)
  • __destruct 的执行时机不确定(GC 时机),不适合做关键清理
  • __get/__set 只在属性不可访问时触发——已有同名 public 属性不触发
  • __toString 不能抛异常(历史限制,8.0 后允许但别用)
  • 魔术方法有性能开销,热点路径避免依赖

14. Heredoc / Nowdoc / 字符串插值

描述

双引号解析变量与转义;单引号原样(仅 \’ 与 \\);Heredoc 多行插值;Nowdoc(<<<'ID')不插值。复杂表达式用 {$expr}。

语法和示例

&lt;?php
$name = 'World';
echo "Hello $name";                // 插值
echo "Total: {$order-&gt;total}";      // 复杂表达式必须大括号
echo 'Skip \n $name';              // 原样输出
echo "Line\nBreak";                // 转义生效
// Heredoc(插值,等价双引号)
$sql = &lt;&lt;&lt;SQL
SELECT * FROM users WHERE name = '$name'
SQL;
// Nowdoc(不插值,等价单引号)
$tpl = &lt;&lt;&lt;'TPL'
原始文本 $name
TPL;

注意事项

  • Heredoc/Nowdoc 的结束标识必须行首(8.x 起允许缩进)
  • 双引号里的数组下标要 $arr[0] 或 {$arr[‘k’]}——$arr[key] 在字符串里会解析错
  • 单引号只转义 \’ 和 \\,其他反斜杠原样保留
  • 插值不支持函数调用:\”{$obj->method()}\” 是语法错误(先赋给变量)
  • 大段 SQL 用 Heredoc 可读性远好于拼接
上一篇
下一篇