本工具使用的开源库

本工具代码中捆绑了 1 个开源库。

PHP 速查 — 简明参考

PHP 8.3 语法、类型、数组与最常用标准库速查手册,覆盖约 80% 日常场景。

Php

PHP PHP 8.3 (LTS 风格)

Zend Engine (PHP 运行时) · 命令式 · OO · 函数式 · 动态(带可选静态类型)

学习路径

先会用 php hello.php 与 php -S 内置服务器跑起来 → 掌握变量、类型与流程控制 → 深入函数、闭包与数组 → 理解引用语义与对象句柄 → 用类、接口与异常组织代码 → 再按需学文件 I/O、cURL、时间与正则 → 用 Fiber/pcntl 处理并发。FAQ 节适合回头避坑。

1.Hello World 与运行环境

运行 PHP 程序、内置 Web 服务器、命令行参数与退出码。

最小程序

PHP 文件以 <?php 开始,CLI 用 php 文件名运行。echo 输出字符串。

1
2
3
4
5
6
7
8
9
echo "Hello, world!\n";
// 单引号不解析转义与插值
echo 'Hello, world!\n';
echo "\n";
// 双引号内变量插值
$name = 'PHP';
echo "Hello, $name!\n";

CLI 运行

php 命令直接运行脚本。php -l 检查语法,php -r 直接执行代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 运行脚本文件:
// $ php hello.php
// 输出 Hello, world!
// 直接执行一行代码:
// $ php -r 'echo "hi\n";'
// 检查语法(不执行):
// $ php -l hello.php
// 启动交互式 shell:
// $ php -a
// 查看版本:
// $ php -v

内置 Web 服务器

php -S 启动内置服务器,把目录当文档根,适合本地开发调试。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 在项目目录启动:
// $ php -S localhost:8000
// 指定文档根目录:
// $ php -S localhost:8000 -t public
// 指定路由脚本:
// $ php -S localhost:8000 router.php
// 启动后浏览器访问:
// http://localhost:8000/index.php
// 端口被占用时会启动失败

文件结构

<?php 与 ?> 标记切分 PHP 与 HTML。纯 PHP 文件推荐省略结束标签。

1
2
3
4
5
6
7
echo "纯 PHP 文件顶部直接写代码\n";
// 建议省略结束标签 ?>
// 避免文件末尾换行被当作输出
// 混写模板时用 ?> 切回 HTML:
// <p><?= $title ?></p>
// 短标签 <?= 等价 echo

命令行参数

$argv 是参数数组,$argc 是参数个数,argv[0] 是脚本名。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 运行:$ php args.php a b c
var_dump($argc); // 4
var_dump($argv);
// ["args.php", "a", "b", "c"]
// 取业务参数:
$name = $argv[1] ?? 'guest';
echo "hello, $name\n";
// 遍历所有参数:
foreach (array_slice($argv, 1) as $arg) {
echo "arg: $arg\n";
}

退出码

exit() 或 die() 结束脚本并返回状态码,0 成功,非 0 出错。

1
2
3
4
5
6
7
8
9
10
11
12
// 正常结束(返回 0):
exit(0);
// 带错误信息退出:
die("发生错误\n"); // 同 exit
// 带状态码退出:
exit(1); // 非 0 表示失败
// 脚本自然结束等价 exit(0)
// CLI 判断成败:
// $ php run.php && echo "ok"

文件引入

require 引入必须存在的文件,include 失败仅警告,_once 防止重复引入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 引入必须存在的文件:
require 'config.php';
// 引入可选文件(失败仅警告):
include 'optional.php';
// 防重复引入:
require_once 'lib/functions.php';
include_once 'helper.php';
// 用 __DIR__ 拼绝对路径:
require __DIR__ . '/../src/bootstrap.php';
// include 返回布尔值可判断成败

环境检查

php -i 打印配置,php -m 列出扩展,phpinfo() 输出完整信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 命令行查看配置:
// $ php -i
// 查看某个配置项:
// $ php -i | grep memory_limit
// 列出已加载扩展:
// $ php -m
// 确认扩展已安装:
// $ php -m | grep mbstring
// 页面输出完整信息:
phpinfo();
// 生产环境务必关闭,避免泄露配置

2.变量与常量

变量声明、常量、类型声明、严格模式与作用域。

变量声明

变量以 $ 开头,无需声明类型,首次赋值即创建,名字大小写敏感。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// $ 前缀 + 名字,无类型声明:
$count = 0;
$name = 'Rex';
$price = 19.99;
$active = true;
// 变量名大小写敏感:
$Foo = 1;
$foo = 2; // 两个不同变量
// 未定义变量读取触发警告:
// echo $undefined;
// 名字不能以数字开头

值复制赋值

普通赋值按值复制,标量独立。数组也是复制,但底层写时复制优化。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 标量赋值互不影响:
$a = 5;
$b = $a;
$b = 10;
echo $a; // 仍是 5
// 数组赋值也是复制:
$arr1 = [1, 2, 3];
$arr2 = $arr1;
$arr2[0] = 99;
echo $arr1[0]; // 1(不受影响)
// 引用赋值用 &,见指针节

常量

const 定义命名空间常量,define() 定义全局常量,定义后不可修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 命名空间级常量:
const MAX_SIZE = 1024;
// 全局常量:
define('APP_NAME', 'demo');
// 常量可用于表达式:
$half = MAX_SIZE / 2;
// 判断是否已定义:
defined('APP_NAME'); // true
// 动态读取常量名:
constant('MAX_SIZE'); // 1024
// 约定常量名全大写

魔术常量

__DIR__、__FILE__、__LINE__ 等编译期常量在对应位置求值,常用于日志。

1
2
3
4
5
6
7
8
9
10
11
echo __DIR__; // 文件所在目录
echo __FILE__; // 文件完整路径
echo __LINE__; // 当前行号
echo __FUNCTION__; // 当前函数名
echo __CLASS__; // 当前类名
echo __NAMESPACE__; // 当前命名空间
// 日志记录位置:
error_log(__FILE__ . ':' . __LINE__);
// __DIR__ 拼接路径最安全

作用域与 static

函数内变量默认局部,global 引入全局变量,static 保留多次调用间的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$global_count = 0;
function countUp(): void
{
static $n = 0; // 调用间保留
$n++;
echo $n;
}
// 函数内访问全局:
function readGlobal(): int
{
global $global_count;
return $global_count;
}
countUp(); // 1
countUp(); // 2

参数与返回类型

参数与返回值可声明类型,弱模式下自动转换,配合 strict_types 变严格。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 参数与返回类型:
function add(int $a, int $b): int
{
return $a + $b;
}
// 可空类型 ?int:
function find(?int $id): ?array
{
return $id === null ? null : [$id];
}
// 联合类型 int|float:
function area(int|float $w): float
{
return (float) $w * 2;
}

严格模式

declare(strict_types=1) 严格校验类型,不匹配直接抛 TypeError。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 必须写在文件第一行:
declare(strict_types=1);
function add(int $a, int $b): int
{
return $a + $b;
}
add(1, 2); // 3
// add('1', 2); // 严格模式抛 TypeError
// 弱模式下 '1' 自动转成 1
// 每个文件单独声明,不跨文件继承

命名约定

类与命名空间大驼峰,函数与变量小驼峰,常量全大写,遵循 PSR-12。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 类名大驼峰:
class UserProfile {}
// 函数与变量小驼峰:
function getUserName() {}
$userName = 'Rex';
// 常量全大写:
const MAX_ITEMS = 100;
// 私有成员可加下划线:
class Demo
{
private $privateData;
}
// PSR-12:类大驼峰,方法小驼峰

3.数据类型

标量、数组、null、枚举与类型系统,理解弱转换与类型检查。

标量类型

int、float、string、bool 四种标量。字符串是字节序列,中文按多字节处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$age = 30; // int
$pi = 3.14; // float
$ok = true; // bool
$name = 'Rex'; // string
// 整数溢出自动变浮点:
$big = PHP_INT_MAX + 1; // float
// 字符串是字节序列:
$s = 'Hello, 世界';
// 显式类型转换:
(int) '42'; // 42
(string) 123; // '123'

数组

PHP 数组是有序映射,整数与字符串键,同时充当列表与字典。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 列表:
$colors = ['red', 'green', 'blue'];
// 字典:
$user = ['name' => 'Rex', 'age' => 5];
// 混合键:
$mixed = [1, 'a', true];
// 追加元素:
$colors[] = 'yellow';
// 取长度:
count($colors); // 4
// 键值顺序即插入顺序

null 与空值

null 表示没有值。未赋值变量、显式 null、函数返回 null 都是 null。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$value = null; // 显式 null
// 判断是否为 null:
$value === null; // true
is_null($value); // true
// 可空参数类型:
function greet(?string $name): void
{
echo $name ?? 'guest';
}
// 用 ?? 提供默认值:
$name = $input['name'] ?? 'guest';

callable 与 iterable

callable 表示可调用,iterable 表示可遍历的数组或 Traversable 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// iterable 参数可收数组或对象:
function sumAll(iterable $items): int
{
$total = 0;
foreach ($items as $n) {
$total += $n;
}
return $total;
}
sumAll([1, 2, 3]); // 6
// callable 收函数名或闭包:
function apply(callable $fn, int $x): int
{
return $fn($x);
}
apply(fn($x) => $x * 2, 5); // 10

mixed 与 void

mixed 表示任意类型,void 无返回值,never 表示永不返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// mixed:任意类型:
function debug(mixed $v): void
{
var_dump($v);
}
// void:无返回值:
function logMsg(string $msg): void
{
echo $msg . "\n";
}
// never:永远不返回:
function abort(): never
{
throw new \RuntimeException('终止');
}
// never 函数必须抛异常或 exit

枚举 enum

enum(8.1+)定义一组具名常量,纯枚举或带值枚举,可加方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 纯枚举:
enum Color
{
case Red;
case Green;
case Blue;
}
// 带值枚举:
enum Status: string
{
case Ok = 'ok';
case NotFound = '404';
}
// 取值与比较:
$s = Status::Ok;
$s->value; // 'ok'
$s === Status::Ok; // true
// 配合 match 做安全匹配

联合与交集类型

int|float 表示两者之一,交集类型要求同时满足多个接口约束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 联合类型(8.0+):
function format(int|float $n): string
{
return number_format((float) $n, 2);
}
// 可空简写 ?T 等价 T|null:
function maybe(?string $s): string
{
return $s ?? '空';
}
// 交集类型(8.1+):
function dump(\Countable&\Traversable $c): void
{
echo count($c);
}
// DNF 类型 8.2+:(A&B)|null

弱类型转换

弱模式下运算时自动转换类型,字符串数字比较规则特殊,见 FAQ。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 字符串与数字运算自动转换:
$n = '3' + 2; // 5
$n = '3' * '2'; // 6
// 字符串强转数字:
(int) '42px'; // 42
(float) '3.5.9'; // 3.5
// 布尔强转:
(bool) '0'; // false
(bool) '0.0'; // true(非 '0')
// 严格模式下这些会报错

类型检查函数

is_int、is_string 等 is_* 系列判断类型,返回布尔值。

1
2
3
4
5
6
7
8
9
10
11
12
is_int(3); // true
is_float(3.0); // true
is_string('x'); // true
is_bool(true); // true
is_array([1]); // true
is_null(null); // true
is_numeric('42'); // true(可转数字)
is_callable('strlen'); // true
// 宽松判断用 is_numeric:
is_int('42'); // false
is_numeric('42'); // true

4.引用与内存语义

PHP 没有裸指针:变量引用、写时复制、对象句柄与 null 处理。

引用是什么

PHP 引用是变量的别名,两个名字指向同一内容,一改俱改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 引用赋值,$b 成为 $a 的别名:
$a = 1;
$b = &$a;
$b = 2;
echo $a; // 2(同改)
// 数组元素也可引用:
$arr = [1, 2];
$last = &$arr[1];
$last = 99;
echo $arr[1]; // 99
// 引用不是地址指针
// 不能取地址、做指针运算

写时复制

数组与对象赋值先共享数据,真正写入时才复制,节省内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 赋值先共享,不立即复制:
$a = [1, 2, 3];
$b = $a;
echo $b[0]; // 1,只读不复制
// 写入时触发复制:
$b[0] = 99;
echo $a[0]; // 1(不受影响)
// 大数组赋值很便宜:
// 只有修改才有复制开销
// foreach 中修改建议另建数组

引用赋值

unset 只解除当前名字的引用,不影响其它别名指向的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$a = 'original';
$b = &$a;
// unset 只断开 $b 的引用:
unset($b);
echo $a; // original(仍在)
// 数组元素间引用:
$data = [1, 2];
$alias = &$data[0];
$data[0] = 100;
echo $alias; // 100
// 观察引用计数:
// debug_zval_dump($a)

引用参数

参数前加 & 按引用传递,函数内修改会反映到调用方变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 引用参数修改原变量:
function increment(int &$n): void
{
$n++;
}
$x = 5;
increment($x);
echo $x; // 6
// 返回值带状态:
function parse(string $s, &$ok): int
{
$ok = is_numeric($s);
return (int) $s;
}
parse('42', $valid); // $valid 变 true

对象句柄

对象变量存的是句柄,赋值、传参都指向同一实例,要副本用 clone。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Counter
{
public int $n = 0;
}
$c1 = new Counter();
$c2 = $c1; // 同一实例
$c2->n = 5;
echo $c1->n; // 5(共享)
// 独立副本用 clone:
$c3 = clone $c1;
$c3->n = 0;
echo $c1->n; // 仍 5
// === 判断是否同一实例:
$c1 === $c2; // true

null 处理

isset 判断存在且非 null,is_null 判断是否为 null,empty 判断是否为空。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$arr = ['a' => null, 'b' => 1];
// isset:存在且非 null:
isset($arr['a']); // false(值是 null)
isset($arr['b']); // true
isset($arr['x']); // false(不存在)
// is_null:是否为 null:
is_null($arr['a']); // true
// 直接访问不存在键会警告:
// $v = $arr['x'];
// 用 ?? 安全读取:
$v = $arr['x'] ?? '默认';

空合并 ??

?? 左侧不存在或为 null 时返回右侧,可链式,可配合 throw。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 读取可能不存在的键:
$name = $_GET['name'] ?? 'guest';
// 链式空合并:
$config = $a ?? $b ?? 'default';
// 与 throw 组合(8.0+):
$value = $input['key'] ?? throw new \InvalidArgumentException('缺 key');
// 不触发未定义键警告:
// 直接下标访问才会警告
// 赋值运算符 ??=(8.0+)

可变变量

$$var 用变量值作另一个变量的名字,可读性差,建议用数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 可变变量:
$name = 'foo';
$foo = 42;
echo $$name; // 42(即 $foo)
// 数组方式更清晰:
$values = ['foo' => 42];
echo $values[$name]; // 42
// 嵌套可变变量易混淆:
// $$$name 几乎无法阅读
// 建议用关联数组替代

unset 与内存

unset 释放名字并递减引用计数,归零后内存可回收,循环引用由 GC 处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 释放大变量:
$big = str_repeat('x', 1000000);
unset($big); // 立刻可回收
// 释放数组元素:
$arr = [1, 2, 3];
unset($arr[1]);
// 键不重排,值变 null
// 循环引用:
class Node
{
public ?Node $next = null;
}
$a = new Node();
$b = new Node();
$a->next = $b;
$b->next = $a; // 形成环
unset($a, $b);
// 由循环垃圾收集器清理

5.流程控制

条件、循环、switch、match 与 break/continue。

if / else

if 判断条件执行分支,elseif 依次尝试,else 兜底。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$n = 0;
if ($n > 0) {
echo "正数";
} elseif ($n === 0) {
echo "零";
} else {
echo "负数";
}
// 单行简写:
if ($n > 0) echo "正数";
// 用 return 提前退出:
// 减少嵌套层级

三元运算符

条件 ? 真值 : 假值 是表达式可赋值,嵌套会损害可读性。

1
2
3
4
5
6
7
8
9
10
$score = 85;
$grade = $score >= 90 ? 'A' : 'B';
echo $grade; // B
// ?: 与 ?? 区别:
$x = $a ?: '默认'; // $a 为假时用默认
$y = $a ?? '默认'; // $a 为 null 时用默认
// 嵌套三元可读性差:
// 建议拆成 if 或 match

switch 语句

switch 默认宽松比较,匹配后需 break 否则穿透,default 兜底。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$status = 200;
switch ($status) {
case 200:
case 201:
echo "成功";
break;
case 404:
echo "未找到";
break;
default:
echo "其它";
}
// case '0' 会宽松匹配 0,需注意
// 复杂判断建议用 match

match 表达式

match(8.0+)严格比较有返回值,分支自动返回,无需 break。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$status = 404;
$label = match ($status) {
200, 201 => '成功',
404, 410 => '未找到',
default => '其它',
};
echo $label; // 未找到
// 无 default 且不匹配会抛
// UnhandledMatchError 异常
// match true 做条件判断:
$result = match (true) {
$score >= 90 => 'A',
$score >= 80 => 'B',
default => 'C',
};

for 循环

for(初始化; 条件; 步进) 经典计数循环,步进可自由控制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for ($i = 0; $i < 3; $i++) {
echo "$i ";
} // 0 1 2
// 递减步进:
for ($i = 10; $i >= 0; $i -= 2) {
echo "$i ";
} // 10 8 6 4 2 0
// 多重循环:
for ($i = 0; $i < 3; $i++) {
for ($j = 0; $j < 2; $j++) {
echo "$i$j ";
}
}

foreach 遍历

foreach 遍历数组与对象,可同时取键值,引用修改需注意残留。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$items = ['a' => 1, 'b' => 2];
// 只取值:
foreach ($items as $v) {
echo "$v ";
} // 1 2
// 取键值:
foreach ($items as $k => $v) {
echo "$k=$v ";
} // a=1 b=2
// 引用修改元素:
foreach ($items as &$v) {
$v *= 2;
}
unset($v); // 关键!断开引用

while 与 do-while

while 先判断后执行可能零次,do-while 至少执行一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// while 先判断:
$n = 0;
while ($n < 3) {
echo $n++;
} // 012
// do-while 至少一次:
$i = 10;
do {
echo $i;
} while ($i < 3); // 10
// 逐行读文件常见写法:
$h = fopen('log.txt', 'r');
while (($line = fgets($h)) !== false) {
echo $line;
}
fclose($h);

break 与 continue

break 跳出循环或 switch,continue 跳到下一次迭代,可带数字跳出多层。

1
2
3
4
5
6
7
8
9
10
11
12
13
for ($i = 0; $i < 10; $i++) {
if ($i % 2 === 0) continue; // 跳过偶数
if ($i > 5) break; // 提前结束
echo "$i ";
} // 1 3 5
// 跳出两层循环:
for ($i = 0; $i < 3; $i++) {
for ($j = 0; $j < 3; $j++) {
if ($j === 1) break 2;
echo "$i$j ";
}
} // 00 10

6.函数与闭包

函数定义、默认参数、可变参数、命名参数、闭包与生成器。

函数定义

function 定义函数,参数带类型,return 返回。函数名大小写不敏感。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function add(int $a, int $b): int
{
return $a + $b;
}
// 调用:
add(1, 2); // 3
// 函数名大小写不敏感:
ADD(1, 2); // 3
// 先调用后定义也可以:
sayHi();
function sayHi(): void
{
echo "hi\n";
}
// 重复定义同名函数致命错误

默认参数

参数可给默认值,调用时可省略。默认值须是常量表达式。

1
2
3
4
5
6
7
8
9
10
11
12
13
function greet(string $name, string $title = 'Mr.'): string
{
return "$title $name";
}
greet('Nick'); // Mr. Nick
greet('Anna', 'Ms.'); // Ms. Anna
// 默认值必须是常量表达式:
// 不能写函数调用或变量
// 可选参数放在必选参数后面:
// function f($a, $b = 1, $c = 2)

可变参数

...$args 收集所有剩余参数为数组,放在参数列表最后。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function sum(int ...$nums): int
{
return array_sum($nums);
}
sum(1, 2, 3); // 6
sum(10, 20); // 30
sum(); // 0
// 传入数组需展开:
$arr = [1, 2, 3];
sum(...$arr); // 6
// 类型声明约束每个元素:
// int ...$nums 保证全为 int

命名参数

调用时用 参数名: 值 传参(8.0+),可跳过可选参数,顺序不限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function makeProfile(
string $name,
string $email = '',
int $age = 0,
): array {
return [$name, $email, $age];
}
// 跳过 email 直接给 age:
makeProfile(
name: 'Rex',
age: 5,
);
// ['Rex', '', 5]
// 与数组解包结合:
$args = ['name' => 'Rex', 'age' => 5];
makeProfile(...$args);

返回类型

返回类型声明在冒号后,支持 void、?T、联合与数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function nothing(): void {} // 无返回值
function maybe(): ?int // 可为 null
{
return null;
}
function both(): int|string // 联合返回
{
return 'ok';
}
function shape(): array // 返回数组
{
return ['name' => 'Rex'];
}
function never(): never // 永不返回
{
throw new \Exception('终止');
}
// 返回类型不匹配抛 TypeError

闭包

匿名函数用 function() 定义,use 捕获外部变量,可赋给变量或作回调。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 匿名函数赋给变量:
$square = function (int $x): int {
return $x * $x;
};
$square(4); // 16
// use 按值捕获:
$factor = 2;
$mul = function ($x) use ($factor): int {
return $x * $factor;
};
$mul(5); // 10
// use 按引用捕获可修改外部:
$count = 0;
$inc = function () use (&$count): void {
$count++;
};
$inc();
echo $count; // 1

箭头函数

fn() => 表达式 自动按值捕获外层变量,单表达式简洁,常用于回调。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$factor = 2;
$double = fn(int $x) => $x * $factor;
$double(10); // 20
// 数组回调更简洁:
$nums = [1, 2, 3, 4];
$evens = array_filter(
$nums,
fn($n) => $n % 2 === 0,
); // [2, 4]
// 箭头函数只有一个表达式:
// 多语句需用普通闭包
// 值捕获,改外部需闭包 use &

可调用对象

8.1+ 用 strlen(...) 创建 first-class callable,把函数变成一等值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 一等可调用(8.1+):
$len = strlen(...);
$len('hello'); // 5
// 对象方法:
$c = new Counter();
$inc = $c->increment(...);
$inc();
// callable 类型收参数:
function run(callable $fn): void
{
echo $fn();
}
run(fn() => 'done');
// call_user_func 是旧写法
// 新代码优先用 (...) 语法

生成器

yield 逐项产出值的函数叫生成器,惰性求值,处理大数据省内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function rangeGen(int $n): Generator
{
for ($i = 0; $i < $n; $i++) {
yield $i;
}
}
foreach (rangeGen(5) as $v) {
echo "$v "; // 0 1 2 3 4
}
// 生成器惰性,边用边产:
$gen = rangeGen(3);
$gen->current(); // 0
$gen->next();
$gen->current(); // 1
// 键值生成:
yield 'key' => 'value';

7.字符串

字符串字面量、插值、拼接、多字节处理与常用函数。

字符串字面量

单引号按原样输出,双引号解析转义与变量。常用两种风格。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 单引号原样输出:
echo 'Hello\n'; // 不转义
// 双引号解析转义:
echo "Hello\n"; // 换行
// 双引号变量插值:
$name = 'Rex';
echo "hi $name"; // hi Rex
// 单引号内的单引号需转义:
echo 'it\'s';
// 多行文本用拼接或 sprintf
// heredoc 易受缩进影响

变量插值

双引号内 $var 直接插值,复杂表达式用花括号 {$arr['k']}。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$name = 'Rex';
$age = 5;
// 简单变量:
echo "姓名 $name"; // 姓名 Rex
// 数组元素用花括号:
$user = ['name' => 'Rex'];
echo "{$user['name']}"; // Rex
// 对象属性:
class Dog
{
public string $name = '旺财';
}
$dog = new Dog();
echo "{$dog->name}"; // 旺财
// 复杂表达式用花括号包住
// 简洁场景用 . 拼接

字符串拼接

. 拼接两个字符串,.= 追加。多次拼接用 implode 或 sprintf 更清晰。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$first = 'Rex';
$last = 'Wang';
// 拼接:
$full = $first . ' ' . $last;
echo $full; // Rex Wang
// 追加:
$msg = '';
$msg .= 'hello ';
$msg .= 'world';
echo $msg; // hello world
// 数组转字符串:
implode('-', [1, 2, 3]); // 1-2-3
// 数字拼接自动转字符串:
echo '第' . 3 . '名'; // 第3名

多字节函数

处理中文用 mb_ 前缀函数按字符计数,strlen 按字节计数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$s = 'Hello, 世界';
// 字节数 vs 字符数:
strlen($s); // 12(字节)
mb_strlen($s); // 8(字符)
// 按字符截取:
mb_substr($s, 0, 5); // 'Hello'
// 字节截取会切坏中文
// 大小写转换:
mb_strtoupper('hello');
// 拆成字符数组:
mb_str_split($s);
// 处理中文务必用 mb_ 系列

格式化 sprintf

sprintf/printf 用 %s、%d 占位符格式化,比拼接更易读。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$name = 'Rex';
$age = 5;
// 格式化:
$line = sprintf('%s 今年 %d 岁', $name, $age);
echo $line; // Rex 今年 5 岁
// 直接输出:
printf('%.2f', 3.14159); // 3.14
// 常用占位符:
// %s 字符串 %d 整数 %f 浮点
// %b 二进制 %x 十六进制
// %-5s 左对齐 %05d 补零
// 参数顺序可交换:
sprintf('%2$s-%1$s', 'a', 'b'); // b-a

查找与替换

strpos 找位置,str_contains 判断包含,str_replace 批量替换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$s = 'Hello, world!';
// 判断包含(8.0+):
str_contains($s, 'world'); // true
// 找位置:
strpos($s, 'world'); // 7
strpos($s, 'xyz'); // false
// 替换:
str_replace('world', 'PHP', $s);
// 'Hello, PHP!'
// 忽略大小写替换:
str_ireplace('HELLO', 'Hi', $s);
// 按位置替换:
// substr_replace($s, 'X', 0, 5)

拆分与合并

explode 按分隔符拆成数组,implode 把数组拼回字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 拆分:
$parts = explode(',', 'a,b,c');
// ['a', 'b', 'c']
// 限制数量:
explode(',', 'a,b,c', 2);
// ['a', 'b,c']
// 空串元素保留:
explode(',', 'a,,c');
// ['a', '', 'c']
// 合并:
implode(' | ', ['a', 'b']);
// 'a | b'
// 按空白拆分:
preg_split('/\s+/', 'a b c');
// ['a', 'b', 'c']

大小写与去空白

strtolower/strtoupper 转大小写,trim 去首尾空白,ltrim/rtrim 只去一边。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$s = ' Hello World ';
// 去首尾空白:
trim($s); // 'Hello World'
// 只去一边:
ltrim(' hi'); // 'hi'
rtrim('hi '); // 'hi'
// 大小写:
strtolower('ABC'); // 'abc'
strtoupper('abc'); // 'ABC'
ucfirst('hello'); // 'Hello'
ucwords('hello world');// 'Hello World'
// 去除指定字符:
trim('...hi...', '.'); // 'hi'

8.数组与集合

数组创建、增删改查、遍历、排序与 Spl 数据结构。

创建数组

[] 或 array() 创建数组。数组字面量自动分配递增整数键。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 字面量创建:
$list = [1, 2, 3];
// 指定键:
$map = ['a' => 1, 'b' => 2];
// 省略键自动递增:
$list = ['x', 'y']; // 键 0,1
// 空数组:
$empty = [];
// 指定键追加:
$arr[5] = 'five';
$arr[] = 'six'; // 键 6
// 数字字符串键自动转整数

访问与修改

下标访问元素,[] 追加,unset 删除。键不存在时读取会警告。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$user = ['name' => 'Rex', 'age' => 5];
// 访问:
echo $user['name']; // Rex
// 修改:
$user['age'] = 6;
// 追加:
$user['city'] = '北京';
// 删除:
unset($user['age']);
// 安全读取避免警告:
$age = $user['age'] ?? '未知';
// 嵌套访问:
$data['a']['b']['c'];

增删与计数

array_push/array_pop 操作栈尾,array_shift/unshift 操作队头,count 计数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$stack = [1, 2, 3];
// 尾部入栈:
$stack[] = 4; // 推荐
array_push($stack, 5, 6);
// 尾部弹出:
$last = array_pop($stack); // 6
// 头部弹出:
$first = array_shift($stack); // 1
// 头部插入:
array_unshift($stack, 0);
// 计数:
count($stack);
// 判断存在:
in_array(2, $stack); // true

遍历数组

foreach 遍历值或键值对,列表解构遍历二维数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$users = [
['name' => 'Rex', 'age' => 5],
['name' => 'Anna', 'age' => 30],
];
// 只取值:
foreach ($users as $u) {
echo $u['name'];
}
// 取键值:
foreach ($users as $i => $u) {
echo "$i: {$u['name']}";
}
// 列表解构:
foreach ($users as ['name' => $n]) {
echo $n;
}
// while+each 是旧写法,勿用

映射与过滤

array_map 逐元素转换,array_filter 按回调过滤,array_reduce 归约。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$nums = [1, 2, 3, 4];
// 映射:
$doubled = array_map(
fn($n) => $n * 2,
$nums,
); // [2, 4, 6, 8]
// 过滤:
$evens = array_filter(
$nums,
fn($n) => $n % 2 === 0,
); // [2, 4]
// 归约:
$sum = array_reduce(
$nums,
fn($acc, $n) => $acc + $n,
0,
); // 10
// 多数组同时映射:
array_map(fn($a, $b) => $a + $b, [1, 2], [3, 4]);

排序

sort 排序值、ksort 按键、asort 按值且保留键关联,usort 自定义比较。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$nums = [3, 1, 4, 1, 5];
// 按值排序(重建键):
sort($nums); // [1, 1, 3, 4, 5]
rsort($nums); // 降序
// 保留键排序:
$ages = ['a' => 30, 'b' => 20];
asort($ages); // 按值升序,键保留
ksort($ages); // 按键排序
// 自定义比较:
usort(
$users,
fn($x, $y) => $x['age'] <=> $y['age'],
);
// 多维列排序:
array_multisort($ages, SORT_DESC);

合并与切片

array_merge 合并数组,array_slice 取子数组,array_combine 拼键值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 合并(字符串键后者覆盖):
$a = ['a' => 1, 'b' => 2];
$b = ['b' => 3, 'c' => 4];
$m = array_merge($a, $b);
// ['a' => 1, 'b' => 3, 'c' => 4]
// 数字键合并会重排:
array_merge([1], [2]); // [1, 2]
// 取子数组:
$nums = [1, 2, 3, 4, 5];
array_slice($nums, 1, 2); // [2, 3]
// 键值拼数组:
array_combine(['a', 'b'], [1, 2]);
// ['a' => 1, 'b' => 2]
// 去重:
array_unique([1, 1, 2]); // [1, 2]

解构赋值

列表 [] 或键值 [] 解构把数组元素赋给多个变量,foreach 中也可用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 列表解构:
[$a, $b] = [1, 2];
echo $a; // 1
// 指定键解构:
[0 => $x, 2 => $z] = [1, 2, 3];
echo $x; // 1
// 键值解构:
['name' => $n] = ['name' => 'Rex'];
echo $n; // Rex
// 剩余收集:
[$first, ...$rest] = [1, 2, 3];
// $rest = [2, 3]
// 函数返回数组解构:
[$min, $max] = minMax($nums);

Spl 数据结构

SplStack、SplQueue、SplFixedArray 提供固定功能的数据结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 栈(后进先出):
$stack = new SplStack();
$stack->push('a');
$stack->push('b');
$stack->pop(); // 'b'
// 队列(先进先出):
$queue = new SplQueue();
$queue->enqueue('a');
$queue->enqueue('b');
$queue->dequeue(); // 'a'
// 固定大小数组:
$fixed = new SplFixedArray(3);
$fixed[0] = 'hi';
$fixed->setSize(5);
// 对象集合:
$store = new SplObjectStorage();
$store->attach($obj, 'meta');
$store->contains($obj);

9.内存与性能

引用计数、循环引用 GC、WeakReference 与内存测量。

引用计数

zval 记录引用数,归零时立即回收。赋值、传参都会增加计数。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 每个变量带引用计数:
$a = 'hello'; // 计数 1
$b = $a; // 计数 2
unset($a); // 计数 1
unset($b); // 计数 0,回收
// 写时复制:只有写入才复制
// 观察计数(需 xdebug):
// xdebug_debug_zval('a')

循环引用 GC

对象互相引用时引用计数永不归零,由循环垃圾收集器定期清理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Node
{
public ?Node $next = null;
}
$a = new Node();
$b = new Node();
$a->next = $b;
$b->next = $a; // 形成环
unset($a, $b);
// 引用计数仍为 1,不会归零
// GC 缓冲区满时自动扫描:
gc_collect_cycles(); // 手动回收
// 长期对象建议主动解链:
// $a->next = null;

WeakReference 弱引用

WeakReference(8.0+)持有对象但不阻止回收,适合缓存等场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Cache
{
public array $data = [];
}
$obj = new Cache();
// 弱引用不增加强引用计数:
$weak = WeakReference::create($obj);
$weak->get(); // Cache 实例
unset($obj);
// 对象被回收后:
$weak->get(); // null
// 弱映射 8.4+:WeakMap
// 适合给对象关联附加数据

内存测量

memory_get_usage 查看当前占用,memory_get_peak_usage 看峰值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 当前内存占用(字节):
$used = memory_get_usage();
// 峰值占用:
$peak = memory_get_peak_usage();
// 转 MB 展示:
printf('%.2f MB', $peak / 1048576);
// 修改内存上限:
// ini_set('memory_limit', '512M');
// CLI 启动时指定:
// $ php -d memory_limit=1G script.php
// 大数组处理后及时 unset

及时释放

大变量用完后 unset 让引用计数归零,降低峰值,批量处理更稳。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 大数组处理完释放:
$lines = file('big.log');
unset($lines);
// 循环中分批处理:
$result = [];
for ($i = 0; $i < 1000; $i++) {
$result[] = str_repeat('x', 100000);
if ($i % 100 === 99) {
// 分批写盘后清空:
$result = [];
}
}
// 对象引用置空帮助回收:
// $obj = null;

OPcache 缓存

OPcache 缓存编译后的字节码,避免每次请求重复编译,生产必开。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 确认已启用:
// $ php -m | grep opcache
// php.ini 建议:
// opcache.enable=1
// opcache.memory_consumption=128
// opcache.max_accelerated_files=10000
// CLI 不默认启用:
// $ php -d opcache.enable=1 script.php
// 缓存字节码,跳过编译:
// 高流量页面吞吐明显提升
// 开发环境可临时关闭

大数据处理

超大数组内存吃紧,用生成器、流式读取与 SplFixedArray 控制占用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 生成器替代完整数组:
function readLines(string $file): Generator
{
$h = fopen($file, 'r');
while (($line = fgets($h)) !== false) {
yield rtrim($line);
}
fclose($h);
}
// 边读边处理,内存恒定:
foreach (readLines('big.log') as $line) {
process($line);
}
// 大量整数用 SplFixedArray:
$fixed = new SplFixedArray(1000000);
// 避免一次读入整份大文件

性能要点

避免字符串拼接循环、预分配、复用连接与减少冗余查询。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 拼接避免循环累加:
$parts = [];
foreach ($items as $it) {
$parts[] = $it->name;
}
$result = implode(',', $parts);
// 复用连接对象:
$pdo = new PDO($dsn); // 单例共享
// 循环外取计数:
$count = count($list);
// 避免重复查询数据库:
// 先查一次,内存过滤
// 结合 opcache 与缓冲
// 热点路径保持简单

10.面向对象

类、构造、可见性、继承、接口、trait 与只读属性。

类定义

class 定义类,属性带类型,方法在类内声明。new 创建实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class User
{
public string $name = '';
public int $age = 0;
public function describe(): string
{
return "{$this->name}, {$this->age}";
}
}
$u = new User();
$u->name = 'Rex';
$u->age = 5;
echo $u->describe(); // Rex, 5
// 属性需声明时初始化
// 或构造函数中赋值

构造与属性提升

__construct 构造方法初始化对象。8.0+ 构造参数直接提升为属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 传统写法:
class OldUser
{
private string $name;
public function __construct(string $name)
{
$this->name = $name;
}
}
// 属性提升(8.0+):
class User
{
public function __construct(
public string $name,
public int $age,
) {}
}
$u = new User('Rex', 5);
echo $u->name; // Rex
// 提升参数自动成为属性

可见性

public 公开、protected 子类可见、private 仅本类,默认 public。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class BankAccount
{
public string $owner = '';
protected float $balance = 0;
private string $secret = 'xx';
public function deposit(float $n): void
{
$this->balance += $n;
}
}
class Child extends BankAccount
{
public function peek(): float
{
return $this->balance; // protected 可访问
}
}
$acc = new BankAccount();
$acc->owner = 'Rex';
// $acc->balance = 1; // 不可访问

继承

extends 继承父类,重写方法,parent:: 调用父类方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Animal
{
public function __construct(
protected string $name,
) {}
public function speak(): string
{
return "$this->name: ...";
}
}
class Dog extends Animal
{
public function speak(): string
{
return parent::speak() . ' 汪汪';
}
}
$dog = new Dog('旺财');
echo $dog->speak(); // 旺财: ... 汪汪
// 子类覆盖父类方法与属性
// final 方法不可被覆盖

接口

interface 定义方法签名,implements 实现。类可实现多个接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
interface Speakable
{
public function speak(): string;
}
interface Runnable
{
public function run(): void;
}
// 实现多个接口:
class Robot implements Speakable, Runnable
{
public function speak(): string
{
return 'beep';
}
public function run(): void
{
echo "running\n";
}
}
// 接口可继承接口:
// interface X extends Speakable {}
// 依赖接口编程便于替换

抽象类

abstract class 不能被实例化,抽象方法由子类实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
abstract class Shape
{
abstract public function area(): float;
public function describe(): string
{
return '面积: ' . $this->area();
}
}
class Circle extends Shape
{
public function __construct(
private float $r,
) {}
public function area(): float
{
return 3.14159 * $this->r * $this->r;
}
}
$c = new Circle(2);
echo $c->describe(); // 面积: 12.56636

trait 复用

trait 横向复用方法到多个类,use 引入,可组合多个 trait。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
trait Loggable
{
public function log(string $msg): void
{
echo "[LOG] $msg\n";
}
}
trait Timestampable
{
public function createdAt(): string
{
return date('Y-m-d H:i:s');
}
}
class Order
{
use Loggable, Timestampable;
public function create(): void
{
$this->log('创建订单');
}
}
$order = new Order();
$order->create(); // [LOG] 创建订单
// 类中的方法优先于 trait

静态成员

static 属性与方法属于类而非实例,self:: 或类名:: 访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Counter
{
private static int $total = 0;
public static function hit(): void
{
self::$total++;
}
public static function get(): int
{
return self::$total;
}
}
Counter::hit();
Counter::hit();
echo Counter::get(); // 2
// 静态方法内无 $this:
// 不能访问非静态成员
// 静态属性所有实例共享
// 延迟静态绑定用 static::

只读属性

readonly 属性(8.1+)只能在声明与构造中赋值一次,之后只读。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Point
{
public function __construct(
public readonly float $x,
public readonly float $y,
) {}
}
$p = new Point(1.0, 2.0);
echo $p->x; // 1
// $p->x = 3; // 只读错误
// 只读类(8.2+):
readonly class Config
{
public function __construct(
public string $dsn,
) {}
}
// 适合值对象与不可变配置
// 属性提升 + readonly 常用

11.错误处理

异常层级、try/catch/finally、自定义异常与错误处理。

try / catch

try 块抛出的异常由 catch 捕获,捕获后程序继续运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try {
$n = 1 / 0; // 8.0+ 抛 DivisionByZeroError
} catch (\DivisionByZeroError $e) {
echo '除数不能为零';
}
// 捕获异常:
try {
throw new \RuntimeException('出错了');
} catch (\RuntimeException $e) {
echo $e->getMessage(); // 出错了
}
// 未捕获异常导致致命错误
// 脚本终止,错误码 255

Throwable 层级

Throwable 是基类,Error 表示引擎错误,Exception 表示程序异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Throwable
// ├── Error(引擎级)
// │ ├── TypeError
// │ ├── ValueError
// │ └── DivisionByZeroError
// └── Exception(程序级)
// ├── LogicException
// └── RuntimeException
// 捕获任意错误/异常:
try {
risky();
} catch (\Throwable $e) {
echo $e->getMessage();
}
// Error 与 Exception 都实现 Throwable
// 可分别 catch 精准处理

自定义异常

继承 Exception 或 RuntimeException 定义业务异常,携带额外字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ValidationException extends \RuntimeException
{
public function __construct(
public readonly string $field,
string $message = '',
) {
parent::__construct(
$message ?: "字段 {$field} 无效",
);
}
}
// 抛出:
throw new ValidationException('email');
// 捕获时取字段:
try {
validate($input);
} catch (ValidationException $e) {
echo $e->field; // email
echo $e->getMessage();
}

多重捕获

catch 多个异常类型用 | 分隔,按顺序匹配,子类先于父类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try {
parse($input);
} catch (\InvalidArgumentException | \LengthException $e) {
// 两类异常同样处理
echo $e->getMessage();
} catch (\Exception $e) {
// 其它异常单独处理
logError($e);
}
// 匹配顺序从上到下:
// 先写具体异常,再写父类
// 8.0+ 可省略变量名:
// catch (\Exception) { ... }

finally

finally 无论是否抛异常都会执行,用于清理资源、关闭连接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function process(): string
{
$conn = openConnection();
try {
return doWork($conn);
} finally {
$conn->close(); // 必执行
}
}
// 即使 return 也先执行 finally
// 释放文件句柄:
$h = fopen('a.txt', 'r');
try {
// 处理
} finally {
fclose($h);
}
// 可只用 finally,不捕获异常

全局异常处理器

set_exception_handler 兜底处理未捕获异常,统一日志与响应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
set_exception_handler(function (\Throwable $e): void {
// 记录日志:
error_log($e->getMessage());
// 返回 500:
http_response_code(500);
echo '服务器开小差了';
});
// 之后未捕获异常都走这里
// 恢复默认处理器:
restore_exception_handler();
// set_error_handler 处理非异常错误
// 8.0+ 大多数错误也是异常

Error 与 Exception

引擎错误是 Error(类型/参数),业务异常是 Exception,分开捕获。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// TypeError:类型不匹配:
function add(int $a, int $b): int
{
return $a + $b;
}
try {
add('a', 'b'); // 抛 TypeError
} catch (\TypeError $e) {
echo '参数类型错误';
}
// ValueError:值不合法:
try {
array_chunk([1, 2], 0); // 抛 ValueError
} catch (\ValueError $e) {
echo '长度必须为正';
}
// 引擎错误多数继承 Error
// 与 Exception 分开捕获

异常链

catch 中抛出新异常时把原异常作为第三个参数,getPrevious 追溯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
try {
$data = fetchFromApi();
} catch (\HttpException $e) {
throw new \RuntimeException(
'获取数据失败',
0,
$e, // 前一个异常
);
}
// 追溯原因:
try {
loadAll();
} catch (\RuntimeException $e) {
$prev = $e->getPrevious();
if ($prev) {
echo $prev->getMessage();
}
}
// 日志记录完整调用栈:
// error_log($e->getTraceAsString());

12.文件 I/O

读取写入文件、JSON、CSV 与标准流。

读取文件

file_get_contents 读整个文件为字符串,小文件首选,失败返回 false。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 读整个文件:
$text = file_get_contents('data.txt');
if ($text === false) {
echo '读取失败';
}
// 读为数组(每行一个元素):
$lines = file('log.txt');
// 忽略换行与空行:
$lines = file(
'log.txt',
FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES,
);
// 远程读取(需 allow_url_fopen):
$json = file_get_contents('https://example.com/api');

写入文件

file_put_contents 写字符串,追加用 FILE_APPEND,返回写入字节数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 覆盖写入:
$n = file_put_contents('out.txt', 'Hello!');
// 追加模式:
file_put_contents('log.txt', "更多\n", FILE_APPEND);
// 写入数组:
$lines = ['a', 'b'];
file_put_contents('list.txt', implode("\n", $lines));
// 检查写入失败:
if (file_put_contents('x.txt', 'data') === false) {
echo '写入失败';
}
// 权限问题会返回 false

逐行读取

fopen + fgets 逐行读取,内存恒定,适合大文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$h = fopen('big.log', 'r');
if ($h === false) {
exit('无法打开文件');
}
while (($line = fgets($h)) !== false) {
// 去掉行尾换行:
$line = rtrim($line, "\r\n");
process($line);
}
fclose($h);
// 生成器封装更友好:
// function lines(string $f): Generator
// 大文件流式处理,勿一次读入

打开模式

fopen 的 r/w/a 模式控制读写行为,w 会清空原内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// r:只读,文件需存在
$h = fopen('a.txt', 'r');
// r+:读写,文件需存在
$h = fopen('a.txt', 'r+');
// w:写入,清空或创建
$h = fopen('a.txt', 'w');
// a:追加,写入末尾
$h = fopen('a.txt', 'a');
// b:二进制模式(Windows 需要)
$h = fopen('img.bin', 'wb');
// 写完记得 fclose 释放句柄
// 句柄泄漏在长进程会堆积

JSON 编解码

json_encode 转 JSON,json_decode 解析。第二参数 true 得数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$data = ['name' => 'Rex', 'age' => 5];
// 编码:
$json = json_encode($data);
// {"name":"Rex","age":5}
// 美化与中文不转义:
json_encode(
$data,
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE,
);
// 解析为数组:
$arr = json_decode($json, true);
// 解析为对象:
$obj = json_decode($json);
// 检查错误:
if (json_last_error() !== JSON_ERROR_NONE) {
echo json_last_error_msg();
}

CSV 读写

fgetcsv 解析一行 CSV,fputcsv 写一行,自动处理引号与转义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 读取:
$h = fopen('data.csv', 'r');
$headers = fgetcsv($h);
while (($row = fgetcsv($h)) !== false) {
$item = array_combine($headers, $row);
echo $item['name'];
}
fclose($h);
// 写入:
$h = fopen('out.csv', 'w');
fputcsv($h, ['name', 'age']);
fputcsv($h, ['Rex', 5]);
fclose($h);
// 逗号/引号自动转义
// 分隔符可自定义:fgetcsv($h, 0, ';')

目录与 glob

glob 按模式匹配文件,scandir 列目录,mkdir 建目录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 通配符匹配文件:
foreach (glob('data/*.csv') as $file) {
echo $file;
}
// 列目录:
$items = scandir('.');
// 判断类型:
is_dir('src'); // true
is_file('a.txt'); // true
// 创建目录(含父级):
mkdir('logs/2026', 0755, true);
// 删除文件:
unlink('tmp.txt');
// 判断存在:
file_exists('a.txt');

标准流

STDIN、STDOUT、STDERR 常亮对应标准输入输出,CLI 常用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 读取一行输入:
$line = fgets(STDIN);
$name = trim($line);
// 输出到标准输出:
fwrite(STDOUT, "hello $name\n");
// 输出到错误流:
fwrite(STDERR, "警告:出错了\n");
// 逐行处理输入:
while (($line = fgets(STDIN)) !== false) {
echo strtoupper($line);
}
// 配合管道:
// echo "hi" | php app.php

13.常见坑

日常开发最容易踩的坑与正确写法。

== 与 ===

== 宽松比较会转换类型,=== 严格比较类型与值。判断尽量用 ===。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// BAD:宽松比较的意外
0 == 'foo'; // true
0 == ''; // true
'1' == 1; // true
// GOOD:严格比较
0 === 'foo'; // false
'1' === 1; // false
// 判零要严格区分:
$count = 0;
if ($count == false) { /* 误判 */ }
if ($count === 0) { /* 正确 */ }
// 惯例:除特殊情况都用 ===

foreach 引用陷阱

foreach 用 & 修改元素后,最后一个元素仍指向原数组,复用 $item 会污染。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$items = [1, 2, 3];
// BAD:循环后 $item 残留引用
foreach ($items as &$item) {
$item *= 2;
}
// 后续复用 $item 会意外改数组
// GOOD:用完 unset 断开
foreach ($items as &$item) {
$item *= 2;
}
unset($item);
// 或改用下标修改:
foreach ($items as $i => $v) {
$items[$i] = $v * 2;
}

isset 与键存在

isset 认为值为 null 的键不存在,判断键是否真实存在用 array_key_exists。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$arr = ['a' => null];
// BAD:键存在但值 null
isset($arr['a']); // false
// GOOD:判断键是否真实存在
array_key_exists('a', $arr); // true
// isset 用于读取前判断:
$name = isset($arr['b']) ? $arr['b'] : '默认';
// ?? 等价 isset 且给默认:
$name = $arr['b'] ?? '默认';
// 需区分 null 与缺失时用 array_key_exists

弱类型比较坑

弱比较下字符串与数字比较规则特殊,'0' 与 false 相等,需显式转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// BAD:弱比较的意外
'0' == false; // true
'0' == null; // true
'abc' == 0; // true
'1e3' == 1000; // true(科学计数)
// GOOD:显式转换后再比较
(int) 'abc' === 0; // 明确意图
filter_var('1e3', FILTER_VALIDATE_INT);
// 哈希比较用 hash_equals:
// hash_equals($a, $b) 防时序攻击
// 数据库结果注意返回类型

未定义键警告

访问不存在的数组键触发警告(8.0+),用 ?? 或 isset 提前判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$user = ['name' => 'Rex'];
// BAD:直接访问不存在的键
$city = $user['city'];
// PHP Warning: Undefined array key
// GOOD:空合并提供默认
$city = $user['city'] ?? '未知';
// 或先判断再访问:
$city = isset($user['city'])
? $user['city']
: '';
// 参数默认值:
function f(array $cfg = []) {
$x = $cfg['x'] ?? 0;
}

输出后再发响应头

已输出内容后 header() 会失败,先开启缓冲或先发响应头。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// BAD:输出后才发响应头
echo "hello\n";
// header('Location: /login'); // 报错
// GOOD:先发响应头再输出
header('Location: /login');
exit;
// 无法控制时先开缓冲:
ob_start();
echo "later\n";
ob_end_flush();
// 检查是否已输出:
// headers_sent()

empty 与 isset

empty 对 0、''、'0' 等都返回 true,isset 只关心是否为 null,选择要合语义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$age = 0;
$name = '';
// BAD:empty 把 0 当空
if (empty($age)) { /* 误判为未设置 */ }
// GOOD:数字 0 是合法值
if (!isset($age) || $age === '') { /* 未设置 */ }
// empty 语义:
empty(0); // true
empty('0'); // true
empty(''); // true
empty(null); // true
// 表单空值判断:
// trim($val) !== '' 更明确

SQL 注入防护

拼接 SQL 字符串有注入风险,用 PDO 预处理参数绑定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$id = $_GET['id'];
// BAD:字符串拼接 SQL(注入风险)
// $sql = "SELECT * FROM users WHERE id = $id";
// GOOD:预处理 + 绑定参数
$pdo = new PDO($dsn, $user, $pass);
$stmt = $pdo->prepare(
'SELECT * FROM users WHERE id = ?',
);
$stmt->execute([$id]);
$rows = $stmt->fetchAll();
// 命名参数:
$stmt = $pdo->prepare(
'SELECT * FROM users WHERE id = :id',
);
$stmt->execute([':id' => $id]);
// 输入校验:
// filter_var($id, FILTER_VALIDATE_INT)

14.并发与协程

PHP 的并发模型:多进程、协程、Fiber 与第三方扩展。

进程模型

PHP 默认单线程,每请求独立进程执行,变量随请求释放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// PHP 默认单线程:
// 每个请求独立进程/线程执行
// 共享内存有限,写时复制:
// 子进程不会复制整份内存
// 无共享状态问题:
// 变量随请求结束释放
// 需要并发时的选择:
// 1. 多进程 pcntl_fork
// 2. 协程 Fiber / Swoole
// 3. parallel 扩展真线程
// CLI 服务器模式:
// $ php -S localhost:8000

pcntl_fork 多进程

pcntl_fork 派生子进程,父子进程各自执行,仅 CLI 可用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 仅 CLI,需启用 pcntl 扩展:
// $ php -m | grep pcntl
$pid = pcntl_fork();
if ($pid === -1) {
exit('fork 失败');
}
if ($pid === 0) {
// 子进程:返回值 0
echo "子进程 PID " . getmypid() . "\n";
exit(0);
}
// 父进程:等待子进程
pcntl_wait($status);
echo "父进程 PID " . getmypid() . "\n";
// 子进程共享代码与内存快照
// 改动互不影响(写时复制)

信号处理

pcntl_signal 注册信号处理器,dispatch 派发挂起信号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
declare(ticks = 1);
// 注册信号处理器:
pcntl_signal(SIGINT, function (int $signo): void {
echo "收到信号 $signo,退出\n";
exit(0);
});
// 处理挂起信号:
while (true) {
pcntl_signal_dispatch();
usleep(100000);
}
// 常用信号:
// SIGTERM 终止 SIGINT Ctrl+C
// SIGHUP 挂断 SIGCHLD 子进程退出
// 生产环境可监听信号优雅停机

parallel 扩展

parallel 是 PECL 扩展,提供真正的多线程 worker,非内置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// parallel 是 PECL 扩展,非内置:
// $ pecl install parallel
use parallel\Runtime;
use parallel\Future;
// 启动工作线程:
$rt = new Runtime();
$future = $rt->run(function (int $n): int {
return $n * $n;
}, [7]);
// 阻塞取结果:
$result = $future->value(); // 49
// 线程共享变量需显式传参
// 不能捕获外部变量
// 生产多核密集任务可用
// Web 环境通常选多进程更稳

Swoole 协程

Swoole 高性能网络框架,协程调度 + 常驻内存,适合高并发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Swoole 是高性能网络框架扩展:
// $ pecl install swoole
// 创建协程服务器:
use Swoole\Coroutine\Http\Server;
$server = new Server('0.0.0.0', 9501);
$server->handle('/', function ($req, $res) {
// 每个连接一个协程:
$res->end("hello\n");
});
$server->start();
// 常驻内存 + 协程调度:
// 大量并发下内存远小于线程模型
// 也支持多进程 worker:
// Swoole\Server 的 worker_num 配置

Fiber 协程

Fiber(8.1+)原生协程,suspend 挂起、resume 恢复,手动调度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Fiber(8.1+)协程,手动切换:
$fiber = new Fiber(function (): void {
$value = Fiber::suspend('first');
echo "恢复,收到 $value\n";
});
// 启动,运行到首个 suspend:
$value = $fiber->start();
echo "挂起返回 $value\n"; // first
// 恢复并传值:
$fiber->resume('second');
// 恢复,收到 second
// 单个线程内协作,非并行
// 用于异步代码转同步写法

异步库

无内置 async/await,用 ReactPHP、Amp 等事件循环库实现异步。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 无内置 async/await 关键字:
// 用事件循环库实现异步 I/O
// ReactPHP 事件循环示意:
use React\EventLoop\Factory;
$loop = Factory::create();
$loop->addTimer(1.0, function () {
echo "ok\n";
});
$loop->run();
// 异步库常见抽象:
// Promise / Deferred / await
// 简单并发用多进程即可
// 高并发 I/O 用 Swoole/ReactPHP

进程间通信

多进程共享数据走消息队列、文件或共享内存,避免直接改同一文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 多进程间共享数据:
// 1. 文件/数据库
// 2. 消息队列(Redis 等)
// 3. 共享内存 shmop
// 4. 管道
// 消息队列简单示意:
// Redis 列表做任务队列:
// $redis->lpush('jobs', $task);
// 子进程写管道:
// posix_kill($pid, SIGUSR1);
// 任务结果收集:
// 每个 worker 写独立文件
// 生产系统常用:
// RabbitMQ / Kafka / Redis Stream
// 避免多进程直接改同一文件

15.网络

HTTP 客户端、curl、URL 解析、会话与套接字。

HTTP 请求

file_get_contents 发简单请求,stream_context_create 配超时与头部。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 简单 GET(allow_url_fopen):
$body = file_get_contents(
'https://example.com/api',
);
// 带超时:
$ctx = stream_context_create([
'http' => ['timeout' => 5],
]);
$body = file_get_contents(
'https://example.com',
false,
$ctx,
);
// 生产更推荐 curl 或 guzzle
// 支持状态码与详细控制

curl 扩展

curl_init + curl_exec 完整控制请求,读状态码与错误信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// curl 扩展(CLI 内置):
$ch = curl_init(
'https://api.example.com/data',
);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer xxx',
],
]);
$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
// 出错检查:
if (curl_errno($ch)) {
echo curl_error($ch);
}
// POST 数据:
// CURLOPT_POST + CURLOPT_POSTFIELDS

POST 与 Guzzle

stream_context 发 POST JSON,工程实践优先用 Guzzle 客户端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// POST JSON:
$data = json_encode(['name' => 'Rex']);
$ctx = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\n",
'content' => $data,
],
]);
$res = file_get_contents(
'https://example.com/api',
false,
$ctx,
);
// Guzzle 更符合工程实践:
// $client->request('POST', $url, [
// 'json' => $payload,
// ]);
// 请求头分隔用 CRLF

请求与响应头

header() 发送响应头,http_response_code 设状态码,须在输出前调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 读取响应头:
$ctx = stream_context_create([
'http' => ['ignore_errors' => true],
]);
$body = file_get_contents(
'https://example.com',
false,
$ctx,
);
$headers = $http_response_header ?? [];
// 发送响应头:
header('Content-Type: application/json');
header('Cache-Control: max-age=3600');
// 设置状态码:
http_response_code(404);
// 读取请求头:
// getallheaders() 返回关联数组
// 输出前必须先发 header

URL 解析

parse_url 拆解 URL 各组件,parse_str 解析查询串,http_build_query 拼接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$url = 'https://user:[email protected]:8080/a?q=1#top';
// 解析各组件:
$parts = parse_url($url);
// [
// 'scheme' => 'https',
// 'host' => 'example.com',
// 'port' => 8080,
// 'path' => '/a',
// 'query' => 'q=1',
// 'fragment' => 'top',
// ]
// 查询字符串:
parse_str('a=1&b=2', $out);
// $out = ['a' => '1', 'b' => '2']
// 拼 URL 编码参数:
http_build_query(['a' => 1, 'b' => 2]);
// a=1&b=2

Cookie 与 Session

session_start 启动会话,setcookie 写 cookie,$_COOKIE 读取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 启动会话:
session_start();
// 自动生成会话 cookie
// 存取会话数据:
$_SESSION['user_id'] = 123;
echo $_SESSION['user_id'];
// 设置 cookie:
setcookie('theme', 'dark', [
'expires' => time() + 3600,
'path' => '/',
'httponly' => true,
'samesite' => 'Lax',
]);
// 读取:
echo $_COOKIE['theme'] ?? '默认';
// 销毁会话:
session_destroy();
// 会话数据存服务端,cookie 只存 id

Socket 套接字

stream_socket_client 连接远程,fwrite 发送、fgets 读取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 客户端套接字:
$sock = stream_socket_client(
'tcp://example.com:80',
$errno,
$errstr,
10,
);
if (!$sock) {
echo "连接失败: $errstr";
}
fwrite($sock, "GET / HTTP/1.0\r\n\r\n");
while (!feof($sock)) {
echo fgets($sock);
}
fclose($sock);
// 服务端监听:
// stream_socket_server('tcp://0.0.0.0:9000')
// 生产协议服务器用 Swoole
// 手写 socket 易出边界问题

SSE 与 WebSocket

SSE 单向文本推送,WebSocket 双向通信,按需选择。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// SSE:服务器推送文本流
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
while (true) {
echo "data: " . json_encode(['t' => time()]) . "\n\n";
flush();
sleep(1);
}
// 客户端用 EventSource 接收
// 单向、基于 HTTP,简单可靠
// WebSocket 双向通信:
// 需扩展如 Swoole、Ratchet
// 选型:简单推送用 SSE
// 双向交互才用 WebSocket

16.日期与时间

时间戳、格式化、解析、时区与时间运算。

获取当前时间

time() 得秒级时间戳,microtime 得毫秒级,date 格式化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 当前时间戳(秒):
$ts = time();
// 毫秒级:
$ms = microtime(true);
// 格式化当前时间:
echo date('Y-m-d H:i:s'); // 2026-08-02 12:30:00
// 读取默认时区:
date_default_timezone_get();
// 设置时区:
// date_default_timezone_set('Asia/Shanghai');
// 旧写法 time() 函数等价 date() 第 2 参数

格式化日期

date() 用格式符渲染时间戳,支持年月日时分秒与本地化写法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ts = strtotime('2026-08-02 12:30:00');
// 格式化:
echo date('Y-m-d', $ts); // 2026-08-02
echo date('H:i:s', $ts); // 12:30:00
echo date('D, d M Y', $ts); // Sun, 02 Aug 2026
echo date('Y年m月d日', $ts); // 2026年08月02日
// 常用格式符:
// Y 四位年 m 两位月 d 两位日
// H 24小时 i 分钟 s 秒
// D 星期简写 M 月份简写
// 转时间戳(反之):
// strtotime('2026-08-02')

解析日期

strtotime 解析日期文本与相对时间,失败返回 false。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 解析日期字符串为时间戳:
$ts = strtotime('2026-08-02 12:30:00');
echo $ts;
// 相对时间:
strtotime('+1 day'); // 明天
strtotime('next Monday'); // 下周一
strtotime('-2 weeks'); // 两周前
// 解析失败返回 false:
$bad = strtotime('不是日期');
// false
// 复杂解析用 DateTime:
// new DateTime('2026-08-02 12:30:00')
// 注意时区影响解析结果

时间戳

Unix 时间戳是 UTC 秒数,DateTime 提供时间戳与对象互转。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 时间戳(epoch 秒):
$ts = time();
// 创建指定时刻:
$dt = new DateTime('2026-08-02 12:30:00');
$ts = $dt->getTimestamp();
// 时间戳格式化:
$dt = new DateTime();
$dt->setTimestamp($ts);
echo $dt->format('Y-m-d');
// 毫秒时间戳(服务端):
$ms = (int) (microtime(true) * 1000);
// 时间戳是 UTC 秒数
// 显示时按本地时区换算

时区

date_default_timezone_set 设默认时区,DateTime 可带独立时区。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 设置默认时区:
date_default_timezone_set('Asia/Shanghai');
// 查看所有时区:
// DateTimeZone::listIdentifiers()
// DateTime 指定时区:
$dt = new DateTime('now', new DateTimeZone('UTC'));
echo $dt->format('Y-m-d H:i:s');
// 时区换算:
$dt->setTimezone(new DateTimeZone('Asia/Tokyo'));
echo $dt->format('Y-m-d H:i:s P');
// 生产统一存 UTC
// 展示层按用户时区转换
// 不要用 date('T') 猜时区

时间间隔

DateInterval 表示时长,DateTime add/sub 加减间隔。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 两个时刻相差:
$start = new DateTime('2026-08-01');
$end = new DateTime('2026-08-02');
$diff = $start->diff($end);
echo $diff->days; // 1
// 添加时间:
$dt = new DateTime('2026-08-02');
$dt->add(new DateInterval('P3D')); // 加 3 天
echo $dt->format('Y-m-d'); // 2026-08-05
// 减去时间:
$dt->sub(new DateInterval('PT2H')); // 减 2 小时
// DateInterval 格式:
// P 周期 D 天 T 时间部分
// P1Y 一年 P2M 两月 PT1H 一小时
// 间隔单位天数换算:
// $diff->days 总天数

时间差计算

DateTime diff 返回 DateInterval,可读年龄、剩余天数等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 计算年龄:
$birth = new DateTime('1990-05-20');
$now = new DateTime();
$diff = $birth->diff($now);
echo $diff->y . ' 岁'; // 整数年
// 差值各字段:
$diff->y; // 年
$diff->m; // 月
$diff->d; // 日
$diff->h; // 小时
$diff->i; // 分钟
// 人类可读格式:
echo $diff->format('%y 年 %m 月 %d 天');
// 总秒数:
// $diff->s + $diff->i * 60 + ...
// 负数需翻转 invert 标志

休眠与定时器

sleep 阻塞暂停,事件循环定时器可非阻塞调度延时任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 暂停执行(秒):
sleep(2);
// 微秒暂停:
usleep(500000); // 0.5 秒
// 任务延迟(ReactPHP):
use React\EventLoop\Factory;
$loop = Factory::create();
$loop->addTimer(1.0, function () {
echo "1 秒后执行\n";
});
$loop->run();
// Swoole 定时器:
// Swoole\Timer::tick(1000, fn() => ...)
// CLI 长任务注意 sleep 阻塞
// 高并发场景用事件循环定时器

17.进程与 CLI

命令行参数、输入输出、环境变量与进程控制。

命令行参数

$argv 数组存放命令行参数,$argv[0] 是脚本名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// $ php app.php arg1 arg2
// $argv[0] = 'app.php'
// $argv[1] = 'arg1'
// $argv[2] = 'arg2'
// 参数个数:
echo count($argv); // 3
// 忽略脚本名遍历:
foreach (array_slice($argv, 1) as $arg) {
echo $arg;
}
// 解析选项:
// getopt('a:b::') 见下节
// 读取 stdin 输入:
// fgets(STDIN)

读取输入

fgets(STDIN) 读一行,stream_get_contents 读全部,trim 去换行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 交互输入:
echo '请输入姓名:';
$name = trim(fgets(STDIN));
// 批量逐行读取:
while (($line = fgets(STDIN)) !== false) {
echo strtoupper($line);
}
// 读取全部输入:
$all = stream_get_contents(STDIN);
// 判断是否管道输入:
// stream_isatty(STDIN)
// 输入可能为空:
// 交互前检查 posix_isatty

执行外部命令

shell_exec 取输出,exec 取状态码,传参用 escapeshellarg 防注入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 执行外部命令并返回输出:
$out = shell_exec('ls -la');
echo $out;
// 获取状态码:
exec('git status', $lines, $code);
// $code 0 表示成功
// 数组输出每行:
// $lines 每行一个元素
// 禁止把用户输入拼进命令:
// $cmd = 'rm ' . $_GET['f']; // 危险
// 需传参时用 escapeshellarg:
// exec('cat ' . escapeshellarg($file))

环境变量

getenv 读取环境变量,putenv 设置,敏感配置走环境变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 读取环境变量:
echo getenv('HOME');
// 设置环境变量:
putenv('APP_ENV=production');
echo getenv('APP_ENV');
// 读取全部:
print_r(getenv());
// $_ENV 超全局:
echo $_ENV['PATH'] ?? '';
// .env 文件常用库:
// vlucas/phpdotenv
// 敏感信息走环境变量
// 勿硬编码进代码库

进程信息

getmypid 取当前 PID,posix_* 系列查用户与父进程,hrtime 计时。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 当前进程 PID:
$pid = getmypid();
// 父进程 PID:
echo posix_getppid();
// 当前用户:
echo posix_getuid();
echo posix_getpwuid(posix_getuid())['name'];
// 运行时长:
// 用 hrtime() 计微秒:
$start = hrtime(true);
work();
$ms = (hrtime(true) - $start) / 1e6;
echo "$ms ms";
// CLI 进程信息:
// 命令名 / proc/self/cmdline

进程信号

pcntl_signal 控制信号处理,pcntl_wait 等待子进程退出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 忽略信号:
pcntl_signal(SIGTERM, SIG_IGN);
// 默认处理:
pcntl_signal(SIGTERM, SIG_DFL);
// 等待子进程(非阻塞):
$pid = pcntl_fork();
if ($pid === 0) { exit(0); }
// 阻塞等待:
pcntl_wait($status);
// 检查退出码:
echo pcntl_wexitstatus($status);
// 向进程发信号:
// posix_kill($pid, SIGTERM)
// 信号处理里避免复杂操作

管道与终端

stream_isatty 判断交互终端,stderr 输出进度,exit 控制退出码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 管道从 stdin 读:
// echo "hi" | php app.php
// 输出流重定向:
// php app.php > out.txt 2>&1
// 判断是否交互终端:
if (stream_isatty(STDIN)) {
echo "交互模式\n";
} else {
echo "管道输入\n";
}
// 进度输出到 stderr:
fwrite(STDERR, "处理中...\n");
// 后台执行并返回:
// $ php app.php &
// 退出码控制:
exit(0); // 成功
exit(1); // 失败

退出码

exit() 传退出码,0 成功非 0 失败,shell 用 $? 读取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 正常退出:
exit(0);
// 业务错误:
if (!$data) {
exit(1); // 通用错误
}
// 自定义码:
exit(2); // 用法错误
exit(3); // 输入无效
// 脚本内提前退出:
return; // 或 exit
// shell 检查:
// php app.php
// echo $? // 上一命令退出码
// 0 成功,非 0 失败
// 128+ 表示被信号终止

18.正则表达式

匹配、捕获、替换、拆分与多字节模式。

preg_match

preg_match 匹配首个结果,捕获组存进 $m,命名组按名取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 匹配第一个结果:
$s = '订单号 A123 和 B456';
if (preg_match('/[A-Z]\d{3}/', $s, $m)) {
echo $m[0]; // A123
}
// 捕获组:
preg_match('/([A-Z])(\d{3})/', $s, $m);
echo $m[1]; // A
echo $m[2]; // 123
// 命名组:
preg_match('/(?<type>[A-Z])(?<num>\d{3})/', $s, $m);
echo $m['type']; // A
// 正则错误会返回 false

preg_match_all

匹配所有结果,默认按组分组,PREG_SET_ORDER 按匹配分组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$s = '订单号 A123 和 B456';
preg_match_all('/[A-Z]\d{3}/', $s, $m);
print_r($m[0]);
// ['A123', 'B456']
// 分组结构:
preg_match_all('/([A-Z])(\d{3})/', $s, $m);
// $m[0] 全匹配
// $m[1] 第 1 组
// $m[2] 第 2 组
// 键值捕获:
preg_match_all(
'/(\w+)=(\d+)/',
'a=1&b=2',
$m,
PREG_SET_ORDER,
);
// $m[0] = ['a=1', 'a', '1']
// 第二个参数决定返回结构

preg_replace

按正则替换,用 $1 回引用捕获组,preg_replace_callback 回调处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 替换匹配:
$s = '电话 138-1234-5678';
$r = preg_replace('/\d{3}-\d{4}-\d{4}/', '[隐藏]', $s);
// '电话 [隐藏]'
// 用捕获组重构:
preg_replace(
'/(\d{4})(\d{4})/',
'$1-$2',
'13812345678',
);
// '1381-2345-678'(示意)
// 回引用:
preg_replace('/(\w+)\s+\1/', '$1', 'hi hi there');
// 'hi there'
// 按数组批量替换:
preg_replace(['/a/', '/b/'], ['A', 'B'], 'abc');
// 回调替换:
preg_replace_callback('/\d+/', fn($m) => $m[0] * 2, '1 2');

常用模式

字符类、量词与贪婪匹配,正则分隔符与转义规则。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 字符类:
\d // 数字 [0-9]
\w // 单词字符 [A-Za-z0-9_]
\s // 空白
// 量词:
* // 0 或多次
+ // 1 或多次
? // 0 或 1 次
{n,m} // n 到 m 次
// 贪婪与懒惰:
'<b>x</b>'
/<.*>/ // 贪婪:<b>x</b>
/<.*?>/ // 懒惰:<b>
// 转义:
\. \( \) \\ // 需 \\ 前缀
// 分隔符:
// 首字符是定界符,最后补修饰符
// '/' 内转义写成 '\/'

捕获与断言

捕获组、非捕获组、命名组与前后断言。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 捕获组:
/(\d{4})-(\d{2})/
// 匹配 2026-08
// 非捕获组:
/(?:ab)+/
// 只分组不捕获,性能更好
// 命名组:
/(?P<year>\d{4})-(?P<month>\d{2})/
// $m['year']
// 后向引用:
/(\w+)\s+\1/
// \1 引用第 1 组内容
// 断言:
/(?=\d{5}$)/ // 前瞻
/(?<=^\d{3})/ // 后顾
// 组内交替:
/gr(a|e)y/

锚点与边界

^ 行首 $ 行尾,\b 词边界,m 修饰符多行匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 行首行尾:
/^php/ // 以 php 开头
/php$/ // 以 php 结尾
// 词边界:
/\bcat\b/ // 匹配独立单词 cat
// 不匹配 category、concatenate
// 多行模式:
// 修饰符 m 使 ^ $ 匹配每行
preg_match_all(
'/^\d+/m',
"12\nabc\n34",
$m,
);
// ['12', '34']
// 精确匹配:
// /^...$/ 加 \z 防尾随换行
// 单词边界处理中文不适用

正则拆分

preg_split 按正则拆字符串,可保留分隔符与限制数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 按正则拆分:
$parts = preg_split('/[,;]/', 'a,b;c');
// ['a', 'b', 'c']
// 保留分隔符:
preg_split('/(,)/', 'a,b', -1, PREG_SPLIT_DELIM_CAPTURE);
// ['a', ',', 'b']
// 限制数量:
preg_split('/\s+/', 'a b c d', 2);
// ['a', 'b c d']
// 分隔空白:
preg_split('/\s+/', ' a b ');
// ['', 'a', 'b'](首尾空)
// 需去空元素时组合 array_filter
// 简单分隔用 explode 即可

多字节与 u 修饰符

处理中文加 u 修饰符,preg_quote 转义用户输入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 中文处理用 u 修饰符:
preg_match('/\p{Han}+/u', '你好世界abc', $m);
echo $m[0]; // 你好世界
// 多字节字符类:
/\p{Han}/ // 汉字
/\p{L}/ // 任意文字
/\p{N}/ // 数字
// 反转义(危险):
// 用户输入正则前先转义:
$lit = preg_quote($_GET['q'], '/');
// 反转义:
preg_match(
'/' . $lit . '/u',
'a+b',
$m,
);
// 用 u 修饰符避免乱码

19.构建与工具链

Composer 依赖管理、自动加载与 CLI 构建流程。

初始化项目

composer init 生成 composer.json,声明依赖与自动加载规则。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 初始化项目:
// $ composer init
// 生成 composer.json:
// {
// "name": "vendor/demo",
// "type": "project",
// "require": { "php": "^8.1" },
// "autoload": { "psr-4": { "App\\": "src/" } }
// }
// 交互式向导生成
// 之后执行 composer install
// PHP 版本约束写在 require
// 用 ^8.1 表示 8.1.x 及以上

安装依赖

composer install 按 lock 安装,require 添加新依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 安装依赖:
// $ composer install
// 只装生产依赖:
// $ composer install --no-dev
// 安装单个包:
// $ composer require monolog/monolog
// 卸载:
// $ composer remove monolog/monolog
// 更新到最新约束版本:
// $ composer update
// 检查依赖问题:
// $ composer validate
// $ composer audit
// 生产环境加 --no-dev
// 减少攻击面与体积
// composer.lock 必须提交
// 锁定精确版本可复现构建

引入依赖

require vendor/autoload.php 后即可 use 依赖类,自动加载生效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 在项目里使用依赖:
require __DIR__ . '/vendor/autoload.php';
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$log = new Logger('app');
$log->pushHandler(
new StreamHandler('php://stderr'),
);
$log->info('启动完成');
// 自动加载器由 install 生成
// 每次改动 autoload 配置需 dump:
// $ composer dump-autoload
// 类名到文件映射:
// PSR-4 命名空间对应目录

PSR-4 自动加载

命名空间与目录一一对应,App\ 映射到 src/,需 dump-autoload。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// composer.json:
// {
// "autoload": {
// "psr-4": {
// "App\\": "src/"
// }
// }
// }
// src/User.php:
// namespace App;
// class User {}
// 自动加载后:
use App\User;
$u = new User();
// 命名空间路径一一对应:
// App\\Controller\\Home
// → src/Controller/Home.php
// 配置改动后重生成:
// $ composer dump-autoload
// PSR-4 只映射类前缀
// 其余命名空间不受影响

Composer 脚本

composer.json scripts 定义命令别名,支持安装钩子回调。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// composer.json 脚本:
// {
// "scripts": {
// "test": "phpunit",
// "lint": "php -l src/*.php"
// }
// }
// 运行:
// $ composer test
// $ composer lint
// 内置脚本钩子:
// pre-install-cmd、post-install-cmd
// 生命周期回调:
// 在安装前后执行清理/权限设置
// 自定义命令组织到 scripts
// 避免手记长命令
// 跨平台路径注意
// 用 / 而非反斜杠

版本约束

^、~、>= 等约束语法控制依赖版本范围,lock 锁精确版。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 版本约束语法:
// ^8.1 允许 8.1.x(>=8.1 <9)
// ~8.1 允许 8.1.x(<8.2)
// >=8.1 任意 >=8.1
// * 任意版本
// 1.2.3 精确版本
// 分支引用:
// dev-master
// v2.0.0-beta
// 锁定精确版本:
// composer.lock 记录实际版本
// 安装指定版本:
// $ composer require foo/bar:^2.0
// 查看可升级:
// $ composer outdated
// 生产用锁定版本部署

CLI 发布命令

框架命令(artisan / console)执行迁移、缓存与定时任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 查看命令帮助:
// $ php bin/console list
// 运行自定义命令:
// $ php artisan migrate
// $ php bin/console cache:clear
// 生成应用密钥:
// $ php artisan key:generate
// 数据库迁移:
// $ php artisan migrate --seed
// 定时任务:
// $ crontab -e
// * * * * * php /path/app/cron.php
// 生产命令加 --env=production
// 与 CI 集成自动化部署
// 常用框架命令前缀:
// Symfony bin/console
// Laravel artisan

PHP 配置

php -i / php --ini / php -m 检查运行时,ini_get 查询配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 查看配置:
// $ php -i 全部配置
// $ php --ini 加载的 ini 路径
// $ php -m 已加载扩展
// 检查扩展:
// $ php -m | grep curl
// 运行时查询:
echo ini_get('memory_limit');
echo ini_get('upload_max_filesize');
// 运行时修改:
ini_set('memory_limit', '512M');
// CLI 参数指定配置:
// $ php -d error_reporting=E_ALL app.php
// 生产配置检查:
// $ php -v
// $ php --ri opcache

官方链接

直达官方文档与资源。

关于本速查

本页是 PHP 8.3 的自包含速查手册,覆盖语言核心与最常用标准库在真实项目中约 80% 的常见用法。内容偏向现代惯用法:declare(strict_types=1) 严格模式、enum 枚举、readonly 只读属性、match 表达式、命名参数、箭头函数与 first-class callable。权威参考见官方 PHP 手册与 PHP The Right Way。 19 个章节各自聚焦一个主题——从第一个程序到数组、面向对象与常见误区。每节拆成 8–14 个带示例的小节(每个 5–20 行),共约 150 个主题。代码片段刻意短小、自解释。 所有处理都在浏览器中完成——无上传、无追踪。本页是 GuruToolkit 免费开发者工具集的一部分;代码片段可自由使用,无任何担保。

版本 2.1.0