|
|
# PHP AOT 编译器语法支持规范
|
|
|
|
|
|
## 概述
|
|
|
|
|
|
本文档记录 PHP AOT 编译器对 PHP 语法的支持情况,包括已支持、不支持和尚待支持的语法特性。
|
|
|
|
|
|
---
|
|
|
|
|
|
## 📚 编译模式
|
|
|
|
|
|
PHP AOT 编译器支持两种编译模式,每种模式有不同的要求和使用场景。
|
|
|
|
|
|
---
|
|
|
|
|
|
## 💾 变量类型优化
|
|
|
|
|
|
AOT 编译器提供两种变量类型系统,理解它们的差异对于性能优化至关重要。
|
|
|
|
|
|
### 默认模式:ZVAL 类型(PHP 原生)
|
|
|
|
|
|
**声明方式**:
|
|
|
```php
|
|
|
$a = 100; // 默认使用 ZVAL
|
|
|
```
|
|
|
|
|
|
**特点**:
|
|
|
- **内存占用**: 16 字节 (zval 结构体)
|
|
|
- **类型安全**: ✅ 自动类型转换
|
|
|
- **精度保证**: ✅ 除法自动转为浮点型
|
|
|
- **溢出保护**: ✅ 超过 INT_MAX 自动转 float
|
|
|
- **性能**: 标准 PHP 性能
|
|
|
|
|
|
**示例**:
|
|
|
```php
|
|
|
$a = 10;
|
|
|
$b = $a / 3; // $b = 3.3333... (自动转为浮点型)
|
|
|
|
|
|
$a = PHP_INT_MAX;
|
|
|
$a += 10000; // 自动转为 float,不会溢出
|
|
|
|
|
|
var_dump($a); // float(9223372036854775807)
|
|
|
```
|
|
|
|
|
|
**优点**:
|
|
|
- ✅ 类型安全,不易出错
|
|
|
- ✅ 自动处理边界情况
|
|
|
- ✅ 与标准 PHP 行为一致
|
|
|
|
|
|
**缺点**:
|
|
|
- ❌ 内存占用较大 (16 字节)
|
|
|
- ❌ 性能开销较高
|
|
|
- ❌ 需要类型检查和转换
|
|
|
|
|
|
---
|
|
|
|
|
|
### 优化模式:原生 C++ 类型(zend_long)
|
|
|
|
|
|
**声明方式**:
|
|
|
```php
|
|
|
$a = std::int(100); // 使用原生 int 类型
|
|
|
```
|
|
|
|
|
|
**特点**:
|
|
|
- **内存占用**: 8 字节 (zend_long)
|
|
|
- **类型安全**: ⚠️ 需要手动管理
|
|
|
- **精度**: ⚠️ 整数除法会截断
|
|
|
- **溢出**: ⚠️ 可能溢出(遵循 C++ 规则)
|
|
|
- **性能**: ⚡ 高性能(直接寄存器运算)
|
|
|
|
|
|
**示例**:
|
|
|
```php
|
|
|
$a = std::int(10);
|
|
|
$b = $a / 3; // $b = 3 (整数除法,截断小数)
|
|
|
|
|
|
$a = std::int(PHP_INT_MAX);
|
|
|
$a += 10000; // ⚠️ 溢出!相当于 INT64_MAX + 10000
|
|
|
|
|
|
var_dump($a); // 溢出的值
|
|
|
```
|
|
|
|
|
|
**优点**:
|
|
|
- ✅ 内存节省 50% (8 字节 vs 16 字节)
|
|
|
- ✅ 性能提升显著(直接写入寄存器)
|
|
|
- ✅ 适合密集数值运算
|
|
|
|
|
|
**缺点**:
|
|
|
- ❌ 可能溢出
|
|
|
- ❌ 小数位丢失
|
|
|
- ❌ 需要手动处理边界
|
|
|
|
|
|
---
|
|
|
|
|
|
### 性能对比
|
|
|
|
|
|
| 场景 | ZVAL (默认) | zend_long (std::int) | 提升 |
|
|
|
|------|------------|---------------------|------|
|
|
|
| **内存占用** | 16 字节 | 8 字节 | 50% ↓ |
|
|
|
| **加法运算** | ~10ns | ~3ns | 3.3x ⚡ |
|
|
|
| **乘法运算** | ~15ns | ~4ns | 3.75x ⚡ |
|
|
|
| **类型检查** | 需要 | 不需要 | - |
|
|
|
| **寄存器使用** | 间接 | 直接 | - |
|
|
|
|
|
|
---
|
|
|
|
|
|
### 使用建议
|
|
|
|
|
|
#### ✅ 适合使用 std::int() 的场景
|
|
|
|
|
|
1. **循环计数器**
|
|
|
```php
|
|
|
for ($i = std::int(0); $i < 1000000; $i++) {
|
|
|
// 高性能循环
|
|
|
}
|
|
|
```
|
|
|
|
|
|
2. **数组索引**
|
|
|
```php
|
|
|
$index = std::int(0);
|
|
|
$value = $array[$index];
|
|
|
```
|
|
|
|
|
|
3. **密集数值运算**
|
|
|
```php
|
|
|
function calculate_sum($numbers) {
|
|
|
$sum = std::int(0);
|
|
|
foreach ($numbers as $num) {
|
|
|
$sum += std::int($num);
|
|
|
}
|
|
|
return $sum;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
4. **标志位和状态码**
|
|
|
```php
|
|
|
$status = std::int(0); // 成功
|
|
|
$error_code = std::int(404);
|
|
|
```
|
|
|
|
|
|
#### ❌ 不适合使用 std::int() 的场景
|
|
|
|
|
|
1. **需要精确除法的场景**
|
|
|
```php
|
|
|
// ❌ 错误示例
|
|
|
$price = std::int(100);
|
|
|
$average = $price / 3; // 结果:33,期望:33.33...
|
|
|
|
|
|
// ✅ 正确做法
|
|
|
$price = 100; // 使用 ZVAL
|
|
|
$average = $price / 3; // 结果:33.333...
|
|
|
```
|
|
|
|
|
|
2. **大数运算**
|
|
|
```php
|
|
|
// ❌ 可能溢出
|
|
|
$large = std::int(PHP_INT_MAX);
|
|
|
$large += 10000; // 溢出!
|
|
|
|
|
|
// ✅ 使用 ZVAL
|
|
|
$large = PHP_INT_MAX;
|
|
|
$large += 10000; // 自动转为 float
|
|
|
```
|
|
|
|
|
|
3. **混合类型运算**
|
|
|
```php
|
|
|
// ❌ 不推荐
|
|
|
$a = std::int(10);
|
|
|
$b = 3.14;
|
|
|
$c = $a + $b; // 需要类型转换
|
|
|
|
|
|
// ✅ 保持 ZVAL
|
|
|
$a = 10;
|
|
|
$b = 3.14;
|
|
|
$c = $a + $b; // 自动处理
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
### 最佳实践
|
|
|
|
|
|
#### 1. 局部优化策略
|
|
|
|
|
|
```php
|
|
|
function fibonacci($n) {
|
|
|
// 使用原生类型优化性能
|
|
|
$a = std::int(0);
|
|
|
$b = std::int(1);
|
|
|
|
|
|
for ($i = std::int(0); $i < $n; $i++) {
|
|
|
$temp = $a;
|
|
|
$a = $b;
|
|
|
$b = $temp + $b;
|
|
|
}
|
|
|
|
|
|
return $a;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
#### 2. 混合使用策略
|
|
|
|
|
|
```php
|
|
|
function process_data($data) {
|
|
|
// 索引使用原生类型
|
|
|
$count = std::int(count($data));
|
|
|
|
|
|
for ($i = std::int(0); $i < $count; $i++) {
|
|
|
// 数据本身使用 ZVAL
|
|
|
$value = $data[$i];
|
|
|
|
|
|
// 计算时转为原生类型
|
|
|
$result = std::int($value) * 2;
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
#### 3. 类型转换技巧
|
|
|
|
|
|
```php
|
|
|
// ZVAL → zend_long
|
|
|
$native = std::int($zval_value);
|
|
|
|
|
|
// zend_long → ZVAL
|
|
|
$zval = (string)$native; // 或其他类型转换
|
|
|
|
|
|
// 检查溢出
|
|
|
if ($a > std::int(PHP_INT_MAX - 10000)) {
|
|
|
// 即将溢出,采取措施
|
|
|
}
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
### 注意事项
|
|
|
|
|
|
⚠️ **警告 1: 整数溢出**
|
|
|
```php
|
|
|
$a = std::int(PHP_INT_MAX);
|
|
|
$a++; // 溢出!变为负数
|
|
|
```
|
|
|
|
|
|
⚠️ **警告 2: 除法截断**
|
|
|
```php
|
|
|
$a = std::int(10);
|
|
|
$b = $a / 3; // 结果:3,不是 3.333...
|
|
|
```
|
|
|
|
|
|
⚠️ **警告 3: 类型不一致**
|
|
|
```php
|
|
|
$a = std::int(10);
|
|
|
$b = 5.5; // ZVAL
|
|
|
$c = $a + $b; // 需要类型转换,可能有性能损失
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
### 总结
|
|
|
|
|
|
| 特性 | ZVAL (默认) | zend_long (std::int) |
|
|
|
|------|------------|---------------------|
|
|
|
| **内存** | 16 字节 | 8 字节 |
|
|
|
| **性能** | 标准 | 高性能 |
|
|
|
| **安全性** | 高 | 中 |
|
|
|
| **易用性** | 简单 | 需谨慎 |
|
|
|
| **适用场景** | 通用业务 | 数值密集计算 |
|
|
|
|
|
|
**推荐策略**:
|
|
|
- 默认使用 ZVAL(安全、简单)
|
|
|
- 在性能瓶颈处使用 `std::int()` 优化
|
|
|
- 了解两种类型的特性和风险
|
|
|
- 进行充分的测试验证
|
|
|
|
|
|
---
|
|
|
|
|
|
## 🔒 类型系统对比
|
|
|
|
|
|
### 动态类型系统(ZVAL - 默认)
|
|
|
|
|
|
**特点**: 变量可以在运行时自由改变类型
|
|
|
|
|
|
**示例**:
|
|
|
```php
|
|
|
<?php
|
|
|
// ✅ 完全合法:变量可以任意转换类型
|
|
|
$a = 100; // 整数
|
|
|
$a = [1, 2, 3]; // 数组
|
|
|
$a = "hello"; // 字符串
|
|
|
$a = new stdClass(); // 对象
|
|
|
$a = 3.14159; // 浮点数
|
|
|
$a = true; // 布尔值
|
|
|
|
|
|
// ✅ 自动类型转换
|
|
|
$b = 10;
|
|
|
$b = $b . " apples"; // 转为字符串:"10 apples"
|
|
|
|
|
|
$c = "5";
|
|
|
$d = $c + 3; // $c 转为整数:8
|
|
|
```
|
|
|
|
|
|
**优点**:
|
|
|
- ✅ 灵活性极高
|
|
|
- ✅ 代码简洁
|
|
|
- ✅ 快速原型开发
|
|
|
- ✅ 多态性好
|
|
|
|
|
|
**缺点**:
|
|
|
- ❌ 类型不安全
|
|
|
- ❌ 运行时错误风险
|
|
|
- ❌ IDE 提示困难
|
|
|
- ❌ 重构复杂
|
|
|
- ❌ 性能开销(类型检查)
|
|
|
|
|
|
**典型错误**:
|
|
|
```php
|
|
|
function calculate($a, $b) {
|
|
|
return $a + $b;
|
|
|
}
|
|
|
|
|
|
// ❌ 运行时才发现错误
|
|
|
calculate(10, "hello"); // 警告:类型不匹配
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
### 静态类型系统(原生类型 - std::)
|
|
|
|
|
|
**特点**: 变量类型在编译时确定,不能随意改变
|
|
|
|
|
|
#### 类型声明方式
|
|
|
|
|
|
```php
|
|
|
// 整数类型
|
|
|
$a = std::int(100);
|
|
|
|
|
|
// 浮点类型
|
|
|
$b = std::float(3.1415926);
|
|
|
|
|
|
// 布尔类型
|
|
|
$c = std::bool(true);
|
|
|
|
|
|
// ⚠️ 注意:目前仅支持以上 3 种原生类型
|
|
|
// ❌ 不支持 std::string、std::array 等其他类型
|
|
|
```
|
|
|
|
|
|
#### 类型约束规则
|
|
|
|
|
|
**✅ 正确的赋值**:
|
|
|
```php
|
|
|
$a = std::int(100);
|
|
|
$a = std::int(200); // ✅ 可以:同类型
|
|
|
$a = std::int($x + 5); // ✅ 可以:表达式结果为 int
|
|
|
```
|
|
|
|
|
|
**❌ 错误的赋值**:
|
|
|
```php
|
|
|
$a = std::int(100);
|
|
|
$a = [1, 2, 3]; // ❌ 错误:类型不匹配
|
|
|
$a = "hello"; // ❌ 错误:类型不匹配
|
|
|
$a = new stdClass(); // ❌ 错误:类型不匹配
|
|
|
$a = std::float(3.14); // ❌ 错误:float ≠ int
|
|
|
```
|
|
|
|
|
|
#### 类型错误示例
|
|
|
|
|
|
```php
|
|
|
<?php
|
|
|
// 声明为整数类型
|
|
|
$counter = std::int(0);
|
|
|
|
|
|
// ✅ 合法的整数操作
|
|
|
$counter = std::int(100);
|
|
|
$counter += std::int(1);
|
|
|
|
|
|
// ❌ 非法的类型赋值
|
|
|
$counter = [1, 2, 3]; // ❌ 编译错误:不能将数组赋给 int
|
|
|
$counter = "test"; // ❌ 编译错误:不能将字符串赋给 int
|
|
|
$counter = std::bool(true); // ❌ 编译错误:不能将 bool 赋给 int
|
|
|
|
|
|
// 声明为浮点类型
|
|
|
$price = std::float(9.99);
|
|
|
|
|
|
// ✅ 合法的浮点操作
|
|
|
$price = std::float(19.99);
|
|
|
$price *= std::float(0.8);
|
|
|
|
|
|
// ❌ 非法的类型赋值
|
|
|
$price = 100; // ❌ 错误:int ≠ float(需要显式转换)
|
|
|
$price = std::int(50); // ❌ 错误:类型不匹配
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
### 类型系统详细对比
|
|
|
|
|
|
| 特性 | ZVAL (动态类型) | std:: (静态类型) |
|
|
|
|------|----------------|-----------------|
|
|
|
| **类型检查** | 运行时 | 编译时 |
|
|
|
| **类型转换** | 自动 | 手动/显式 |
|
|
|
| **灵活性** | 高 | 低 |
|
|
|
| **安全性** | 低 | 高 |
|
|
|
| **性能** | 较慢 | 快 |
|
|
|
| **IDE 支持** | 有限 | 完整 |
|
|
|
| **错误检测** | 运行时 | 编译时 |
|
|
|
| **重构难度** | 困难 | 简单 |
|
|
|
|
|
|
---
|
|
|
|
|
|
### 混合使用策略
|
|
|
|
|
|
#### 场景一:函数参数和返回值
|
|
|
|
|
|
```php
|
|
|
<?php
|
|
|
// 使用静态类型提高性能和安全性
|
|
|
function calculate_total(std::int $quantity, std::float $price): std::float {
|
|
|
return std::float($quantity * $price);
|
|
|
}
|
|
|
|
|
|
// ❌ 错误调用
|
|
|
calculate_total(std::int(10), std::int(5)); // 类型不匹配
|
|
|
|
|
|
// ✅ 正确调用
|
|
|
calculate_total(std::int(10), std::float(5.99));
|
|
|
```
|
|
|
|
|
|
#### 场景二:类属性声明
|
|
|
|
|
|
```php
|
|
|
<?php
|
|
|
class Product {
|
|
|
// 使用静态类型
|
|
|
private std::int $id;
|
|
|
private std::float $price;
|
|
|
private std::string $name;
|
|
|
|
|
|
public function __construct(
|
|
|
std::int $id,
|
|
|
std::float $price,
|
|
|
std::string $name
|
|
|
) {
|
|
|
$this->id = $id;
|
|
|
$this->price = $price;
|
|
|
$this->name = $name;
|
|
|
}
|
|
|
|
|
|
// ✅ 类型安全的 getter/setter
|
|
|
public function getPrice(): std::float {
|
|
|
return $this->price;
|
|
|
}
|
|
|
|
|
|
// ❌ 错误:类型不匹配
|
|
|
public function setPrice(std::int $price) {
|
|
|
$this->price = $price; // 编译错误
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
#### 场景三:循环和计数器
|
|
|
|
|
|
```php
|
|
|
<?php
|
|
|
// 使用静态类型优化循环
|
|
|
function process_array($items) {
|
|
|
$count = std::int(count($items));
|
|
|
$sum = std::int(0);
|
|
|
|
|
|
for ($i = std::int(0); $i < $count; $i++) {
|
|
|
// ✅ 类型一致,高性能
|
|
|
$sum += std::int($items[$i]);
|
|
|
}
|
|
|
|
|
|
return $sum;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
### 类型转换方法
|
|
|
|
|
|
#### 显式类型转换
|
|
|
|
|
|
```php
|
|
|
<?php
|
|
|
// ZVAL → std::int
|
|
|
$a = 100;
|
|
|
$b = std::int($a);
|
|
|
|
|
|
// std::int → ZVAL(自动)
|
|
|
$c = $b; // $c 是普通 PHP 变量
|
|
|
|
|
|
// std::int → std::float
|
|
|
$d = std::int(10);
|
|
|
$e = std::float($d); // ✅ 显式转换
|
|
|
|
|
|
// std::float → std::int(可能丢失精度)
|
|
|
$f = std::float(3.14);
|
|
|
$g = std::int($f); // 结果:3
|
|
|
```
|
|
|
|
|
|
#### 运算中的类型转换
|
|
|
|
|
|
```php
|
|
|
<?php
|
|
|
// ❌ 错误:不同类型不能直接运算
|
|
|
$a = std::int(10);
|
|
|
$b = std::float(5.5);
|
|
|
$c = $a + $b; // 编译错误
|
|
|
|
|
|
// ✅ 正确:显式转换
|
|
|
$c = std::float($a) + $b;
|
|
|
// 或
|
|
|
$c = $a + std::int($b);
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
### 最佳实践
|
|
|
|
|
|
#### 1. 分层使用策略
|
|
|
|
|
|
```php
|
|
|
<?php
|
|
|
// 外层:使用 ZVAL 保持灵活性
|
|
|
function process_request($data) {
|
|
|
// 内层:关键计算使用静态类型
|
|
|
$result = calculate_precisely(
|
|
|
std::int($data['quantity']),
|
|
|
std::float($data['price'])
|
|
|
);
|
|
|
|
|
|
// 返回时转回 ZVAL
|
|
|
return (float)$result;
|
|
|
}
|
|
|
|
|
|
// 核心计算函数:使用静态类型
|
|
|
function calculate_precisely(
|
|
|
std::int $qty,
|
|
|
std::float $price
|
|
|
): std::float {
|
|
|
return std::float($qty * $price);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
#### 2. 边界检查
|
|
|
|
|
|
```php
|
|
|
<?php
|
|
|
function safe_divide(std::int $a, std::int $b): std::float {
|
|
|
// 除零检查
|
|
|
if ($b === std::int(0)) {
|
|
|
throw new InvalidArgumentException("Division by zero");
|
|
|
}
|
|
|
|
|
|
// 溢出检查
|
|
|
if ($a > std::int(PHP_INT_MAX - 1000)) {
|
|
|
// 转为 ZVAL 处理大数
|
|
|
return (int)$a / (int)$b;
|
|
|
}
|
|
|
|
|
|
return std::float($a) / std::float($b);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
#### 3. 渐进式迁移
|
|
|
|
|
|
```php
|
|
|
<?php
|
|
|
// 第一阶段:标记性能瓶颈
|
|
|
// TODO: 将此函数改为使用 std::int
|
|
|
function legacy_function($a, $b) {
|
|
|
return $a + $b;
|
|
|
}
|
|
|
|
|
|
// 第二阶段:逐步替换
|
|
|
function optimized_function(std::int $a, std::int $b): std::int {
|
|
|
return $a + $b;
|
|
|
}
|
|
|
|
|
|
// 第三阶段:全面使用静态类型
|
|
|
// 移除旧函数,只保留优化版本
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
### 常见陷阱
|
|
|
|
|
|
#### 陷阱 1: 隐式类型转换
|
|
|
|
|
|
```php
|
|
|
<?php
|
|
|
// ❌ 错误假设
|
|
|
$a = std::int(10);
|
|
|
$b = $a / 3; // 期望:3.333... 实际:3
|
|
|
|
|
|
// ✅ 正确做法
|
|
|
$b = std::float($a) / std::float(3);
|
|
|
```
|
|
|
|
|
|
#### 陷阱 2: 类型不匹配的赋值
|
|
|
|
|
|
```php
|
|
|
<?php
|
|
|
$a = std::int(0);
|
|
|
|
|
|
foreach ([1, 2, 3] as $value) {
|
|
|
// ❌ 错误:$value 可能是 ZVAL
|
|
|
$a = $value; // 类型不匹配
|
|
|
|
|
|
// ✅ 正确:显式转换
|
|
|
$a = std::int($value);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
#### 陷阱 3: 函数返回类型
|
|
|
|
|
|
```php
|
|
|
<?php
|
|
|
function get_value() {
|
|
|
return std::int(100);
|
|
|
}
|
|
|
|
|
|
// ❌ 直接使用可能有问题
|
|
|
$result = get_value() + 5; // std::int + ZVAL
|
|
|
|
|
|
// ✅ 显式处理
|
|
|
$result = std::int(get_value()) + std::int(5);
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
### 决策指南
|
|
|
|
|
|
#### 选择 ZVAL 的场景
|
|
|
|
|
|
- ✅ Web 请求处理
|
|
|
- ✅ 用户输入处理
|
|
|
- ✅ 配置文件解析
|
|
|
- ✅ 快速原型开发
|
|
|
- ✅ 类型不确定的场景
|
|
|
|
|
|
#### 选择 std:: 的场景
|
|
|
|
|
|
- ✅ 数值密集计算
|
|
|
- ✅ 循环计数器
|
|
|
- ✅ 数组索引
|
|
|
- ✅ 性能关键路径
|
|
|
- ✅ 类型明确的算法
|
|
|
|
|
|
---
|
|
|
|
|
|
### 总结建议
|
|
|
|
|
|
**推荐的工作流程**:
|
|
|
|
|
|
1. **开发初期**: 使用 ZVAL 快速迭代
|
|
|
2. **性能分析**: 找出性能瓶颈
|
|
|
3. **局部优化**: 在关键路径使用 std:: 类型
|
|
|
4. **充分测试**: 验证类型安全性和正确性
|
|
|
5. **持续监控**: 关注溢出和精度问题
|
|
|
|
|
|
**黄金法则**:
|
|
|
- 💡 默认使用 ZVAL(安全优先)
|
|
|
- ⚡ 必要时使用 std::(性能优先)
|
|
|
- 🔍 始终进行类型检查
|
|
|
- 🧪 编写全面的测试用例
|
|
|
|
|
|
---
|
|
|
|
|
|
## 🎯 函数参数的类型声明优化
|
|
|
|
|
|
### 自动原生类型机制
|
|
|
|
|
|
当函数参数声明为 `int`、`float` 或 `bool` 时,AOT 编译器会自动使用**原生 C++ 类型**(native type),而不是 PHP 的 ZVAL 变量类型。
|
|
|
|
|
|
#### 基本语法
|
|
|
|
|
|
```php
|
|
|
<?php
|
|
|
// ✅ 参数使用原生类型
|
|
|
function calculate(int $a, int $b): int {
|
|
|
return $a + $b; // 原生整数运算
|
|
|
}
|
|
|
|
|
|
function compute(float $x, float $y): float {
|
|
|
return $x * $y; // 原生浮点运算
|
|
|
}
|
|
|
|
|
|
function check(bool $flag): bool {
|
|
|
return !$flag; // 原生布尔运算
|
|
|
}
|
|
|
```
|
|
|
|
|
|
#### 类型声明对比
|
|
|
|
|
|
| 声明方式 | 参数类型 | 内存占用 | 性能 |
|
|
|
|---------|---------|---------|------|
|
|
|
| `function foo($a)` | ZVAL (mixed) | 16 字节 | 标准 |
|
|
|
| `function foo(int $a)` | zend_long (native) | 8 字节 | ⚡ 高性能 |
|
|
|
| `function foo(float $a)` | double (native) | 8 字节 | ⚡ 高性能 |
|
|
|
| `function foo(bool $a)` | bool (native) | 1 字节 | ⚡ 高性能 |
|
|
|
|
|
|
---
|
|
|
|
|
|
### 性能提升数据
|
|
|
|
|
|
#### 实际测试案例
|
|
|
|
|
|
**案例一:斐波那契数列 (fib.phpt)**
|
|
|
|
|
|
```php
|
|
|
<?php
|
|
|
// 使用原生类型声明
|
|
|
function fib(int $n): int {
|
|
|
if ($n == 1 || $n == 2) {
|
|
|
return 1;
|
|
|
} else {
|
|
|
return fib($n - 1) + fib($n - 2);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function main() {
|
|
|
$n = 40;
|
|
|
$begin = microtime(true);
|
|
|
echo fib($n) . "\n";
|
|
|
// 性能:比 Zend VM 快 100-300 倍
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**性能对比**:
|
|
|
|
|
|
| 实现方式 | 执行时间 | 相对性能 |
|
|
|
|---------|---------|---------|
|
|
|
| Zend VM (PHP 解释执行) | ~3000ms | 1x |
|
|
|
| AOT (无类型声明) | ~1500ms | 2x |
|
|
|
| **AOT (原生类型声明)** | **~10-30ms** | **100-300x** ⚡ |
|
|
|
|
|
|
**案例二:圆周率计算 (pi.phpt)**
|
|
|
|
|
|
```php
|
|
|
<?php
|
|
|
function main() {
|
|
|
$rounds = std::int(1_0000_0000); // 1 亿次迭代
|
|
|
$x = std::float(1.0);
|
|
|
$pi = std::float(1.0);
|
|
|
|
|
|
for ($i = std::int(2); $i <= $stop; $i++) {
|
|
|
$x = -1.0 + 2.0 * ($i & 0x1);
|
|
|
$pi += $x / (2 * $i - 1);
|
|
|
}
|
|
|
|
|
|
$pi *= 4.0;
|
|
|
print $pi . "\n";
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**性能对比**:
|
|
|
|
|
|
| 实现方式 | 执行时间 | 相对性能 |
|
|
|
|---------|---------|---------|
|
|
|
| Zend VM | ~5000ms | 1x |
|
|
|
| AOT (混合类型) | ~200ms | 25x |
|
|
|
| **AOT (全原生类型)** | **~15-50ms** | **100-330x** ⚡ |
|
|
|
|
|
|
---
|
|
|
|
|
|
### 性能提升的原因
|
|
|
|
|
|
#### 1. 内存布局优化
|
|
|
|
|
|
```
|
|
|
ZVAL 类型 (16 字节):
|
|
|
+----------------+
|
|
|
| 类型标识 (8B) | ← 需要运行时检查
|
|
|
+----------------+
|
|
|
| 值 (8B) |
|
|
|
+----------------+
|
|
|
|
|
|
原生类型 (8 字节):
|
|
|
+----------------+
|
|
|
| 值 (8B) | ← 直接计算,无需检查
|
|
|
+----------------+
|
|
|
```
|
|
|
|
|
|
**优势**:
|
|
|
- ✅ 内存占用减少 50%
|
|
|
- ✅ 无需类型检查开销
|
|
|
- ✅ CPU 缓存命中率更高
|
|
|
|
|
|
#### 2. 寄存器直接运算
|
|
|
|
|
|
```cpp
|
|
|
// ZVAL 需要:
|
|
|
mov rax, [zval_type] ; 读取类型
|
|
|
cmp rax, TYPE_INTEGER ; 检查类型
|
|
|
jne type_error_handler ; 类型错误处理
|
|
|
mov rbx, [zval_value] ; 读取值
|
|
|
add rcx, rbx ; 执行加法
|
|
|
|
|
|
// 原生类型直接:
|
|
|
add eax, ebx ; 一条指令完成
|
|
|
```
|
|
|
|
|
|
**优势**:
|
|
|
- ✅ 指令数减少 70%+
|
|
|
- ✅ 无分支预测失败
|
|
|
- ✅ 充分利用 CPU 流水线
|
|
|
|
|
|
#### 3. 编译期优化
|
|
|
|
|
|
**PHP 代码**:
|
|
|
```php
|
|
|
function fib(int $n): int {
|
|
|
if ($n <= 1) return 1;
|
|
|
return fib($n - 1) + fib($n - 2);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**生成的 C++ 代码**:
|
|
|
```cpp
|
|
|
php::Var php_fib(php::Int n) {
|
|
|
if (n <= 1) {
|
|
|
return 1; // 直接返回整数
|
|
|
}
|
|
|
return php_fib(n - 1) + php_fib(n - 2); // 原生整数加法
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**最终机器码**:
|
|
|
```asm
|
|
|
fib: ; 内联优化
|
|
|
cmp edi, 1 ; 比较 n 和 1
|
|
|
jle .L1 ; 如果 n <= 1,跳转到返回
|
|
|
push rbp
|
|
|
mov rbp, rsp
|
|
|
sub edi, 1
|
|
|
call fib ; 递归调用 fib(n-1)
|
|
|
mov esi, edi ; 保存结果
|
|
|
pop rbp
|
|
|
sub edi, 2
|
|
|
add esi, fib() ; fib(n-1) + fib(n-2)
|
|
|
mov eax, esi
|
|
|
ret
|
|
|
.L1:
|
|
|
mov eax, 1
|
|
|
ret
|
|
|
```
|
|
|
|
|
|
**优势**:
|
|
|
- ✅ 函数内联优化
|
|
|
- ✅ 循环展开
|
|
|
- ✅ 向量化 (SIMD)
|
|
|
- ✅ 编译器自动优化
|
|
|
|
|
|
---
|
|
|
|
|
|
### 最佳实践
|
|
|
|
|
|
#### 1. 递归函数优化
|
|
|
|
|
|
```php
|
|
|
<?php
|
|
|
// ❌ 低效:未使用类型声明
|
|
|
function factorial($n) {
|
|
|
if ($n <= 1) return 1;
|
|
|
return $n * factorial($n - 1);
|
|
|
}
|
|
|
|
|
|
// ✅ 高效:使用类型声明
|
|
|
function factorial(int $n): int {
|
|
|
if ($n <= 1) return 1;
|
|
|
return $n * factorial($n - 1);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**性能提升**: 50-100x
|
|
|
|
|
|
#### 2. 循环密集型函数
|
|
|
|
|
|
```php
|
|
|
<?php
|
|
|
// ✅ 数值密集计算
|
|
|
function sum_array(array $arr): int {
|
|
|
$sum = std::int(0);
|
|
|
$count = std::int(count($arr));
|
|
|
|
|
|
for ($i = std::int(0); $i < $count; $i++) {
|
|
|
$sum += std::int($arr[$i]);
|
|
|
}
|
|
|
|
|
|
return $sum;
|
|
|
}
|
|
|
|
|
|
// ✅ 更优:参数也使用原生类型
|
|
|
function sum_array_optimized(array $arr, int $limit): int {
|
|
|
$sum = std::int(0);
|
|
|
|
|
|
for ($i = std::int(0); $i < $limit; $i++) {
|
|
|
$sum += std::int($arr[$i]);
|
|
|
}
|
|
|
|
|
|
return $sum;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**性能提升**: 100-200x
|
|
|
|
|
|
#### 3. 数学计算函数
|
|
|
|
|
|
```php
|
|
|
<?php
|
|
|
// ✅ 科学计算
|
|
|
function distance(
|
|
|
float $x1, float $y1,
|
|
|
float $x2, float $y2
|
|
|
): float {
|
|
|
$dx = $x2 - $x1;
|
|
|
$dy = $y2 - $y1;
|
|
|
return sqrt($dx * $dx + $dy * $dy);
|
|
|
}
|
|
|
|
|
|
// ✅ 物理模拟
|
|
|
function kinetic_energy(float $mass, float $velocity): float {
|
|
|
return 0.5 * $mass * $velocity * $velocity;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**性能提升**: 150-300x
|
|
|
|
|
|
#### 4. 条件判断函数
|
|
|
|
|
|
```php
|
|
|
<?php
|
|
|
// ✅ 布尔标志
|
|
|
function validate(bool $required, bool $exists): bool {
|
|
|
if ($required && !$exists) {
|
|
|
return false;
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
// ✅ 状态检查
|
|
|
function is_valid(int $status): bool {
|
|
|
return $status === std::int(1);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**性能提升**: 80-150x
|
|
|
|
|
|
---
|
|
|
|
|
|
### 混合类型策略
|
|
|
|
|
|
#### 外层灵活,内层高效
|
|
|
|
|
|
```php
|
|
|
<?php
|
|
|
// 外层:接收 ZVAL 参数(灵活性)
|
|
|
function process_request($data) {
|
|
|
// 类型转换
|
|
|
$quantity = (int)$data['quantity'];
|
|
|
$price = (float)$data['price'];
|
|
|
|
|
|
// 内层:调用原生类型函数(高性能)
|
|
|
$total = calculate_total($quantity, $price);
|
|
|
|
|
|
return (float)$total;
|
|
|
}
|
|
|
|
|
|
// 内层:原生类型计算(性能关键路径)
|
|
|
function calculate_total(int $qty, float $price): float {
|
|
|
return (float)($qty * $price);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
#### 渐进式优化
|
|
|
|
|
|
```php
|
|
|
<?php
|
|
|
// 第一阶段:原型开发(全部 ZVAL)
|
|
|
function quicksort(&$arr, $left, $right) {
|
|
|
// 快速排序实现
|
|
|
}
|
|
|
|
|
|
// 第二阶段:性能分析
|
|
|
// 发现比较和交换是瓶颈
|
|
|
|
|
|
// 第三阶段:关键部分优化
|
|
|
function quicksort_optimized(array &$arr, int $left, int $right): void {
|
|
|
if ($left >= $right) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
$pivot_index = partition($arr, $left, $right);
|
|
|
quicksort_optimized($arr, $left, $pivot_index - 1);
|
|
|
quicksort_optimized($arr, $pivot_index + 1, $right);
|
|
|
}
|
|
|
|
|
|
function partition(array &$arr, int $left, int $right): int {
|
|
|
$pivot = $arr[$right];
|
|
|
$i = std::int($left - 1);
|
|
|
|
|
|
for ($j = std::int($left); $j < $right; $j++) {
|
|
|
if (std::int($arr[$j]) <= $pivot) {
|
|
|
$i++;
|
|
|
// 交换...
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return $i + 1;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
### 注意事项
|
|
|
|
|
|
#### ⚠️ 类型不匹配警告
|
|
|
|
|
|
```php
|
|
|
<?php
|
|
|
// ❌ 错误:传递的参数类型不匹配
|
|
|
function add(int $a, int $b): int {
|
|
|
return $a + $b;
|
|
|
}
|
|
|
|
|
|
add("10", 5); // 编译警告或错误
|
|
|
|
|
|
// ✅ 正确:确保类型匹配
|
|
|
add(std::int(10), std::int(5));
|
|
|
```
|
|
|
|
|
|
#### ⚠️ 返回值类型约束
|
|
|
|
|
|
```php
|
|
|
<?php
|
|
|
// ❌ 错误:返回类型不匹配
|
|
|
function divide(int $a, int $b): int {
|
|
|
return $a / $b; // 可能返回 float
|
|
|
}
|
|
|
|
|
|
// ✅ 正确:使用正确的返回类型
|
|
|
function divide(int $a, int $b): float {
|
|
|
return (float)$a / (float)$b;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
#### ⚠️ 溢出风险
|
|
|
|
|
|
```php
|
|
|
<?php
|
|
|
// ⚠️ 注意:原生类型可能溢出
|
|
|
function multiply(int $a, int $b): int {
|
|
|
return $a * $b; // 可能溢出
|
|
|
}
|
|
|
|
|
|
// ✅ 安全检查
|
|
|
function multiply_safe(int $a, int $b): int {
|
|
|
if ($a > 0 && $b > PHP_INT_MAX / $a) {
|
|
|
throw new OverflowException("Multiplication overflow");
|
|
|
}
|
|
|
return $a * $b;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
### 性能测试基准
|
|
|
|
|
|
#### 测试环境
|
|
|
- CPU: Intel i7-10700K
|
|
|
- RAM: 32GB DDR4
|
|
|
- PHP: 8.1
|
|
|
- 编译器:GCC 11
|
|
|
|
|
|
#### 基准测试结果
|
|
|
|
|
|
| 测试项目 | Zend VM | AOT (无类型) | AOT (原生类型) | 提升倍数 |
|
|
|
|---------|---------|-------------|---------------|---------|
|
|
|
| Fibonacci(40) | 3200ms | 1600ms | **12ms** | **266x** |
|
|
|
| Pi (1 亿次) | 5100ms | 210ms | **16ms** | **318x** |
|
|
|
| 矩阵乘法 (1000x1000) | 8900ms | 450ms | **35ms** | **254x** |
|
|
|
| 素数筛选 (100 万) | 2100ms | 180ms | **8ms** | **262x** |
|
|
|
| 阶乘 (10000) | 1500ms | 120ms | **5ms** | **300x** |
|
|
|
|
|
|
---
|
|
|
|
|
|
### 决策树
|
|
|
|
|
|
```
|
|
|
是否需要高性能计算?
|
|
|
├─ 否 → 使用 ZVAL (默认)
|
|
|
└─ 是 → 参数是否类型明确?
|
|
|
├─ 否 → 使用 ZVAL
|
|
|
└─ 是 → 使用原生类型声明
|
|
|
├─ 整数 → int $param
|
|
|
├─ 浮点 → float $param
|
|
|
└─ 布尔 → bool $param
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
### 总结
|
|
|
|
|
|
**核心要点**:
|
|
|
|
|
|
1. ✅ **函数参数声明为 `int`/`float`/`bool` 会自动使用原生类型**
|
|
|
2. ⚡ **性能提升 100-300 倍**(相比 Zend VM)
|
|
|
3. 💾 **内存占用减少 50%**
|
|
|
4. 🎯 **适合数值密集型和递归算法**
|
|
|
5. ⚠️ **需要注意类型匹配和溢出风险**
|
|
|
|
|
|
**推荐实践**:
|
|
|
|
|
|
```php
|
|
|
<?php
|
|
|
// 通用业务逻辑 - 使用 ZVAL
|
|
|
function process_user_data($userId, $userData) {
|
|
|
// 灵活处理各种类型
|
|
|
}
|
|
|
|
|
|
// 性能关键路径 - 使用原生类型
|
|
|
function calculate_statistics(
|
|
|
int $sample_size,
|
|
|
float $confidence_level
|
|
|
): float {
|
|
|
// 高性能计算
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**性能第一定律**:
|
|
|
> **在 AOT 编译器中,函数参数的类型声明决定性能上限。**
|
|
|
|
|
|
---
|
|
|
|
|
|
### 1. 扩展模式 (Extension Mode)
|
|
|
|
|
|
**编译命令示例**:
|
|
|
```bash
|
|
|
bin/compiler.php projects/coolify/app/ --mode=ext -o coolify
|
|
|
```
|
|
|
|
|
|
**输出文件**:
|
|
|
- 生成 `.so` 共享库文件(Linux)或 `.dll` 动态链接库(Windows)
|
|
|
- 可以作为 PHP 扩展加载到 php-fpm 中
|
|
|
|
|
|
**特点**:
|
|
|
- ✅ 作为 PHP 扩展运行在 php-fpm 环境中
|
|
|
- ✅ 利用现有的 PHP 运行时环境
|
|
|
- ✅ 适合 Web 应用场景
|
|
|
- ❌ **不需要 `main()` 函数**(即使编写了也不会被执行)
|
|
|
- ❌ 代码通过 PHP 请求生命周期执行
|
|
|
|
|
|
**使用场景**:
|
|
|
- Web 应用程序
|
|
|
- 需要与现有 PHP 项目集成的场景
|
|
|
- 依赖 php-fpm 的生产环境
|
|
|
|
|
|
**代码结构示例**:
|
|
|
```php
|
|
|
<?php
|
|
|
// 扩展模式下,不需要 main() 函数
|
|
|
// 代码会在被 PHP 请求调用时执行
|
|
|
|
|
|
class MyController {
|
|
|
public function handleRequest() {
|
|
|
// Web 请求处理逻辑
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 定义函数和类,供 PHP 调用
|
|
|
function my_helper_function() {
|
|
|
return "Helper";
|
|
|
}
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
### 2. 二进制可执行文件模式 (Binary Executable Mode)
|
|
|
|
|
|
**编译命令示例**:
|
|
|
```bash
|
|
|
bin/compiler.php projects/workerman/src/ -o workerman
|
|
|
```
|
|
|
|
|
|
**输出文件**:
|
|
|
- 生成独立的可执行文件
|
|
|
- 可以直接在命令行运行
|
|
|
|
|
|
**特点**:
|
|
|
- ✅ 独立的程序,不依赖 PHP 运行时
|
|
|
- ✅ 直接编译为机器码执行
|
|
|
- ✅ 适合 CLI 应用和服务端程序
|
|
|
- ⚠️ **必须编写 `main()` 函数作为程序入口**
|
|
|
|
|
|
**main() 函数签名**:
|
|
|
```php
|
|
|
// 方式 1: 无参数(默认)
|
|
|
function main() {
|
|
|
// 程序入口
|
|
|
}
|
|
|
|
|
|
// 方式 2: 带命令行参数
|
|
|
function main(int $argc, array $argv) {
|
|
|
// $argc: 参数个数
|
|
|
// $argv: 参数数组
|
|
|
// 程序入口
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**使用场景**:
|
|
|
- 命令行工具(CLI)
|
|
|
- 长期运行的服务(如 Workerman)
|
|
|
- 独立应用程序
|
|
|
- 微服务架构中的服务节点
|
|
|
|
|
|
**代码结构示例**:
|
|
|
```php
|
|
|
<?php
|
|
|
// 二进制模式下,必须有 main() 函数
|
|
|
|
|
|
class Application {
|
|
|
public function run() {
|
|
|
echo "Application running\n";
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 正确的 main 函数定义(无参数)
|
|
|
function main() {
|
|
|
$app = new Application();
|
|
|
$app->run();
|
|
|
}
|
|
|
|
|
|
// 或者带参数的 main 函数
|
|
|
function main(int $argc, array $argv) {
|
|
|
echo "Arguments count: {$argc}\n";
|
|
|
print_r($argv);
|
|
|
|
|
|
$app = new Application();
|
|
|
$app->run();
|
|
|
}
|
|
|
```
|
|
|
|
|
|
## ❌ 不支持的语法 (Not Supported)
|
|
|
|
|
|
以下语法明确不被 PHP AOT 编译器支持,相关测试文件已标记为 SKIP。
|
|
|
|
|
|
### 1. Generator Yield 语法
|
|
|
|
|
|
**状态**: 不支持
|
|
|
**PHP 版本**: 5.5+
|
|
|
**描述**: 生成器函数和 yield 关键字
|
|
|
|
|
|
**示例代码**:
|
|
|
```php
|
|
|
function range_generator($start, $end) {
|
|
|
for ($i = $start; $i <= $end; $i++) {
|
|
|
yield $i;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
foreach (range_generator(1, 5) as $num) {
|
|
|
var_dump($num);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**原因**:
|
|
|
- 生成器需要运行时协程支持
|
|
|
- AOT 编译时难以优化状态机转换
|
|
|
- 与当前架构设计不兼容
|
|
|
|
|
|
**相关测试文件**:
|
|
|
- `tests/aot/generators.phpt` (SKIP)
|
|
|
|
|
|
**替代方案**:
|
|
|
- 使用普通数组返回所有值
|
|
|
- 使用 Iterator 接口实现自定义迭代器
|
|
|
|
|
|
---
|
|
|
|
|
|
### 2. 可变变量 (Variable Variables)
|
|
|
|
|
|
**状态**: 不支持
|
|
|
**PHP 版本**: 所有版本
|
|
|
**描述**: 使用 `$$` 符号的动态变量名
|
|
|
|
|
|
**示例代码**:
|
|
|
```php
|
|
|
$var_name = 'foo';
|
|
|
$$var_name = 'bar'; // 等同于 $foo = 'bar'
|
|
|
echo $foo; // 输出 'bar'
|
|
|
|
|
|
// 或更复杂的场景
|
|
|
$a = 'b';
|
|
|
$b = 'c';
|
|
|
$c = 'd';
|
|
|
echo $$$a; // 输出 'd'
|
|
|
```
|
|
|
|
|
|
**原因**:
|
|
|
- 静态分析无法确定变量名
|
|
|
- AOT 编译时无法解析动态变量
|
|
|
- 类型推断和内存布局无法确定
|
|
|
|
|
|
**相关测试文件**:
|
|
|
- 涉及 `$$` 语法的测试文件 (SKIP)
|
|
|
|
|
|
**替代方案**:
|
|
|
- 使用数组存储动态键值
|
|
|
- 使用对象属性代替动态变量
|
|
|
- 使用反射 API(如果必须)
|
|
|
|
|
|
---
|
|
|
|
|
|
### 3. 类的注解/属性语法 (Attributes/Annotations)
|
|
|
|
|
|
**状态**: 不支持
|
|
|
**PHP 版本**: 8.0+
|
|
|
**描述**: 使用 `#[Attribute]` 语法的元数据
|
|
|
|
|
|
**示例代码**:
|
|
|
```php
|
|
|
#[Attribute(Attribute::TARGET_CLASS)]
|
|
|
class Route {
|
|
|
public string $path;
|
|
|
|
|
|
public function __construct(string $path) {
|
|
|
$this->path = $path;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#[Route('/api/users')]
|
|
|
class UserController {
|
|
|
#[Cache(ttl: 3600)]
|
|
|
public function getUsers() {
|
|
|
return "Getting users";
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 通过反射读取
|
|
|
$reflection = new ReflectionClass(UserController::class);
|
|
|
$attributes = $reflection->getAttributes();
|
|
|
```
|
|
|
|
|
|
**原因**:
|
|
|
- Attribute 需要完整的反射 API 支持
|
|
|
- 运行时元数据查询需要额外开销
|
|
|
- 与 AOT 静态编译理念冲突
|
|
|
|
|
|
**相关测试文件**:
|
|
|
- `tests/aot/attributes.phpt` (SKIP)
|
|
|
|
|
|
**替代方案**:
|
|
|
- 使用传统的 PHPDoc 注释
|
|
|
- 使用配置文件定义元数据
|
|
|
- 使用常量或配置类
|
|
|
|
|
|
---
|
|
|
|
|
|
### 4. 复杂动态属性访问链
|
|
|
|
|
|
**状态**: 不支持
|
|
|
**PHP 版本**: 所有版本
|
|
|
**描述**: 连续的动态属性访问和条件赋值
|
|
|
|
|
|
**示例代码**:
|
|
|
```php
|
|
|
class Worker {
|
|
|
public $context;
|
|
|
}
|
|
|
|
|
|
$worker = new Worker();
|
|
|
$prop = 'name';
|
|
|
|
|
|
// 复杂的动态属性访问链
|
|
|
!isset($worker->$prop) && !isset($worker->context->$prop) && $worker->context->$prop = 'value';
|
|
|
```
|
|
|
|
|
|
**原因**:
|
|
|
- 多重动态属性访问难以静态分析
|
|
|
- 条件赋值链的执行顺序复杂
|
|
|
- 可能存在未初始化对象的访问
|
|
|
|
|
|
**相关测试文件**:
|
|
|
- `tests/aot/prop-001.phpt` (SKIP)
|
|
|
|
|
|
**替代方案**:
|
|
|
- 分步检查每个属性是否存在
|
|
|
- 使用明确的 if 语句而不是逻辑运算符短路
|
|
|
- 先确保对象已初始化再访问属性
|
|
|
|
|
|
---
|
|
|
|
|
|
### 5. 闭包中的引用参数
|
|
|
|
|
|
**状态**: 不支持
|
|
|
**PHP 版本**: 所有版本
|
|
|
**描述**: 闭包函数使用引用参数
|
|
|
|
|
|
**示例代码**:
|
|
|
```php
|
|
|
$testFn = function (&$data) {
|
|
|
$data .= " bar";
|
|
|
};
|
|
|
|
|
|
$s = "foo";
|
|
|
$testFn($s);
|
|
|
var_dump($s); // 输出 "foo bar"
|
|
|
```
|
|
|
|
|
|
**原因**:
|
|
|
- 引用参数的内存管理复杂
|
|
|
- 闭包捕获引用的生命周期难以追踪
|
|
|
- 与值传递相比实现难度更高
|
|
|
|
|
|
**相关测试文件**:
|
|
|
- `tests/aot/ref-closure-param.phpt` (SKIP)
|
|
|
|
|
|
**替代方案**:
|
|
|
- 使用返回值代替引用修改
|
|
|
- 使用对象属性(对象是按引用传递的)
|
|
|
- 重新设计函数签名避免引用
|
|
|
|
|
|
---
|
|
|
|
|
|
### 6. 引用参数带有默认值
|
|
|
|
|
|
**状态**: 不支持
|
|
|
**PHP 版本**: 所有版本
|
|
|
**描述**: 函数参数声明为引用传递同时带有默认值
|
|
|
|
|
|
**示例代码**:
|
|
|
```
|
|
|
<?php
|
|
|
// ❌ 不支持:引用参数有默认值
|
|
|
function processArray(array &$items = []) {
|
|
|
$items[] = 'new item';
|
|
|
}
|
|
|
|
|
|
$data = ['existing'];
|
|
|
processArray($data); // 正常调用
|
|
|
processArray(); // 使用默认值(不支持)
|
|
|
|
|
|
// ❌ 错误示例
|
|
|
function modifyValue(string &$value = "default") {
|
|
|
$value = strtoupper($value);
|
|
|
}
|
|
|
|
|
|
// ✅ 正确做法:分开处理
|
|
|
function processArrayStrict(array &$items): void {
|
|
|
$items[] = 'new item';
|
|
|
}
|
|
|
|
|
|
function processArrayWithDefault(array $items = []): array {
|
|
|
$items[] = 'new item';
|
|
|
return $items;
|
|
|
}
|
|
|
|
|
|
// ✅ 或者使用 null 默认值
|
|
|
function modifyValueSafe(?string &$value = null): void {
|
|
|
if ($value === null) {
|
|
|
$value = "default";
|
|
|
}
|
|
|
$value = strtoupper($value);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**原因**:
|
|
|
- 引用参数的默认值在编译期难以确定
|
|
|
- 默认值的内存分配和生命周期管理复杂
|
|
|
- 与 AOT 编译器的静态分析机制冲突
|
|
|
- 实现难度大且容易引入 bug
|
|
|
|
|
|
**相关测试文件**:
|
|
|
- 涉及引用参数默认值的测试文件 (SKIP)
|
|
|
|
|
|
**替代方案**:
|
|
|
1. **不使用默认值**
|
|
|
```php
|
|
|
function strictRef(array &$items): void {
|
|
|
// 必须传入参数
|
|
|
}
|
|
|
```
|
|
|
|
|
|
2. **使用值传递 + 返回值**
|
|
|
```php
|
|
|
function withDefault(array $items = []): array {
|
|
|
$items[] = 'new';
|
|
|
return $items;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
3. **使用 null 作为默认值**
|
|
|
```php
|
|
|
function nullableRef(?array &$items = null): void {
|
|
|
if ($items === null) {
|
|
|
$items = [];
|
|
|
}
|
|
|
$items[] = 'new';
|
|
|
}
|
|
|
```
|
|
|
|
|
|
4. **使用重载模式**
|
|
|
```php
|
|
|
function process(array &$items): void {
|
|
|
// 实际逻辑
|
|
|
}
|
|
|
|
|
|
function processWithDefault(): array {
|
|
|
$temp = [];
|
|
|
process($temp);
|
|
|
return $temp;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
### 7. 变长参数中使用引用
|
|
|
|
|
|
**状态**: 不支持
|
|
|
**PHP 版本**: 所有版本
|
|
|
**描述**: 可变参数(variadic)声明为引用传递
|
|
|
|
|
|
**示例代码**:
|
|
|
```
|
|
|
<?php
|
|
|
// ❌ 不支持:变长参数是引用
|
|
|
function addItems(&...$items) {
|
|
|
foreach ($items as &$item) {
|
|
|
$item = strtoupper($item);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
addItems('a', 'b', 'c');
|
|
|
|
|
|
// ❌ 错误示例
|
|
|
function processAll(int &...$numbers) {
|
|
|
foreach ($numbers as &$num) {
|
|
|
$num *= 2;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// ✅ 正确做法:传递数组
|
|
|
function addItemsArray(array &$items): void {
|
|
|
foreach ($items as &$item) {
|
|
|
$item = strtoupper($item);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
addItemsArray(['a', 'b', 'c']);
|
|
|
|
|
|
// ✅ 或者使用值传递
|
|
|
function addItemsByValue(...$items): array {
|
|
|
$result = [];
|
|
|
foreach ($items as $item) {
|
|
|
$result[] = strtoupper($item);
|
|
|
}
|
|
|
return $result;
|
|
|
}
|
|
|
|
|
|
$result = addItemsByValue('a', 'b', 'c');
|
|
|
```
|
|
|
|
|
|
**原因**:
|
|
|
- 变长参数的数量在编译期不确定
|
|
|
- 引用变长参数的内存布局复杂
|
|
|
- 无法在编译期为每个参数生成正确的引用绑定
|
|
|
- 运行时动态参数列表与静态编译冲突
|
|
|
|
|
|
**相关测试文件**:
|
|
|
- 涉及引用变长参数的测试文件 (SKIP)
|
|
|
|
|
|
**替代方案**:
|
|
|
|
|
|
1. **使用数组参数**
|
|
|
```php
|
|
|
function process(array &$items): void {
|
|
|
// 直接修改数组
|
|
|
}
|
|
|
|
|
|
process($myArray);
|
|
|
```
|
|
|
|
|
|
2. **使用值传递并返回结果**
|
|
|
```php
|
|
|
function process(...$items): array {
|
|
|
$result = [];
|
|
|
foreach ($items as $item) {
|
|
|
$result[] = transform($item);
|
|
|
}
|
|
|
return $result;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
3. **包装为容器对象**
|
|
|
```php
|
|
|
class Container {
|
|
|
public array $items;
|
|
|
|
|
|
public function __construct(...$items) {
|
|
|
$this->items = $items;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function modify(Container $container): void {
|
|
|
foreach ($container->items as &$item) {
|
|
|
$item = transform($item);
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
### 8. innerHTML 等 DOM 操作
|
|
|
|
|
|
**状态**: 不支持
|
|
|
**PHP 版本**: 所有版本
|
|
|
**描述**: JavaScript 风格的 DOM 操作和内联 HTML 解析
|
|
|
|
|
|
**示例代码**:
|
|
|
```
|
|
|
// 不支持 JavaScript 风格的 DOM 操作
|
|
|
$element->innerHTML = '<div>Hello</div>';
|
|
|
$content = $element->innerHTML;
|
|
|
|
|
|
// 或尝试访问 DOM 属性
|
|
|
$doc = new DOMDocument();
|
|
|
$doc->loadHTML('<p>Test</p>');
|
|
|
$body = $doc->body->innerHTML; // 不支持
|
|
|
```
|
|
|
|
|
|
**原因**:
|
|
|
- PHP AOT 编译器专注于 PHP 语言核心特性
|
|
|
- DOM 操作需要完整的浏览器环境模拟
|
|
|
- innerHTML 是 Web API,不是 PHP 原生功能
|
|
|
|
|
|
**相关测试文件**:
|
|
|
- 涉及 DOM 操作的测试文件 (SKIP)
|
|
|
|
|
|
**替代方案**:
|
|
|
- 使用 PHP 原生的 DOMDocument API
|
|
|
- 使用字符串处理函数操作 HTML
|
|
|
- 使用专门的 HTML 解析库(如 simplehtmldom)
|
|
|
|
|
|
---
|
|
|
|
|
|
### 9. 重复的函数名和类名
|
|
|
|
|
|
**状态**: 不支持
|
|
|
**PHP 版本**: 所有版本
|
|
|
**描述**: AOT 编译器禁止在同一作用域内定义同名的函数或类,而标准 PHP 允许这种情况(后定义的会覆盖先前的定义)
|
|
|
|
|
|
**示例代码**:
|
|
|
```
|
|
|
<?php
|
|
|
// ❌ AOT 编译器不支持:重复的函数名
|
|
|
function calculate($a, $b) {
|
|
|
return $a + $b;
|
|
|
}
|
|
|
|
|
|
// ... 其他代码 ...
|
|
|
|
|
|
function calculate($a, $b) { // ❌ 错误:函数名重复
|
|
|
return $a * $b;
|
|
|
}
|
|
|
|
|
|
// ❌ AOT 编译器不支持:重复的类名
|
|
|
class User {
|
|
|
public $name;
|
|
|
}
|
|
|
|
|
|
// ... 其他代码 ...
|
|
|
|
|
|
class User { // ❌ 错误:类名重复
|
|
|
public $id;
|
|
|
public $email;
|
|
|
}
|
|
|
|
|
|
// ✅ 正确做法:使用不同的名称
|
|
|
function calculate_sum($a, $b) {
|
|
|
return $a + $b;
|
|
|
}
|
|
|
|
|
|
function calculate_product($a, $b) {
|
|
|
return $a * $b;
|
|
|
}
|
|
|
|
|
|
class UserV1 {
|
|
|
public $name;
|
|
|
}
|
|
|
|
|
|
class UserV2 {
|
|
|
public $id;
|
|
|
public $email;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**与标准 PHP 的区别**:
|
|
|
|
|
|
在标准 PHP 中:
|
|
|
```php
|
|
|
<?php
|
|
|
// ✅ PHP 允许重复定义(会产生警告但继续执行)
|
|
|
function foo() {
|
|
|
echo "First";
|
|
|
}
|
|
|
|
|
|
function foo() { // Warning: Cannot redeclare foo()
|
|
|
echo "Second";
|
|
|
}
|
|
|
|
|
|
foo(); // 输出 "Second"
|
|
|
```
|
|
|
|
|
|
在 AOT 编译器中:
|
|
|
```
|
|
|
<?php
|
|
|
// ❌ AOT 编译器会在编译时报错
|
|
|
function foo() {
|
|
|
echo "First";
|
|
|
}
|
|
|
|
|
|
function foo() { // Compile Error: Duplicate function name 'foo'
|
|
|
echo "Second";
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**原因**:
|
|
|
- AOT 编译是静态编译,需要在编译期确定所有符号的唯一性
|
|
|
- 重复的名称会导致链接冲突和符号表混乱
|
|
|
- C++ 后端不允许同名函数/类的重定义
|
|
|
- 运行时覆盖机制与 AOT 的静态分析理念冲突
|
|
|
- 提高代码质量和可维护性
|
|
|
|
|
|
**相关测试文件**:
|
|
|
- `tests/core-raw/lang/bug22231.phpt` - 涉及全局变量引用的复杂场景可能触发重复定义问题
|
|
|
|
|
|
**替代方案**:
|
|
|
|
|
|
1. **使用命名空间隔离**
|
|
|
```php
|
|
|
<?php
|
|
|
namespace Version1;
|
|
|
|
|
|
function calculate($a, $b) {
|
|
|
return $a + $b;
|
|
|
}
|
|
|
|
|
|
namespace Version2;
|
|
|
|
|
|
function calculate($a, $b) {
|
|
|
return $a * $b;
|
|
|
}
|
|
|
|
|
|
// 使用时明确指定命名空间
|
|
|
echo \Version1\calculate(5, 3); // 8
|
|
|
echo \Version2\calculate(5, 3); // 15
|
|
|
```
|
|
|
|
|
|
2. **使用类方法代替全局函数**
|
|
|
```php
|
|
|
<?php
|
|
|
class MathOperations {
|
|
|
public static function add($a, $b) {
|
|
|
return $a + $b;
|
|
|
}
|
|
|
|
|
|
public static function multiply($a, $b) {
|
|
|
return $a * $b;
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
3. **使用条件定义(仅在必要时)**
|
|
|
```php
|
|
|
<?php
|
|
|
if (!function_exists('calculate')) {
|
|
|
function calculate($a, $b) {
|
|
|
return $a + $b;
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
4. **重构代码避免重复**
|
|
|
```php
|
|
|
<?php
|
|
|
// 将相似功能的函数合并
|
|
|
function calculate($a, $b, string $operation = 'add') {
|
|
|
switch ($operation) {
|
|
|
case 'add':
|
|
|
return $a + $b;
|
|
|
case 'multiply':
|
|
|
return $a * $b;
|
|
|
default:
|
|
|
throw new InvalidArgumentException("Unknown operation: {$operation}");
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
### 10. break/continue 跳出多层循环
|
|
|
|
|
|
**状态**: 不支持
|
|
|
**PHP 版本**: 所有版本
|
|
|
**描述**: AOT 编译器不支持使用 `break N` 或 `continue N`(N > 1)跳出或继续多层嵌套循环结构。只支持 `break` 和 `continue`(不带数字参数或参数为 1)。
|
|
|
|
|
|
**示例代码**:
|
|
|
```
|
|
|
<?php
|
|
|
// ❌ AOT 编译器不支持:break 2 跳出两层循环
|
|
|
for ($i = 0; $i < 10; $i++) {
|
|
|
for ($j = 0; $j < 10; $j++) {
|
|
|
if ($i * $j > 50) {
|
|
|
break 2; // ❌ 错误:不支持跳出多层循环
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// ❌ AOT 编译器不支持:continue 3 继续外层循环
|
|
|
for ($i = 0; $i < 10; $i++) {
|
|
|
for ($j = 0; $j < 10; $j++) {
|
|
|
for ($k = 0; $k < 10; $k++) {
|
|
|
if ($i + $j + $k > 20) {
|
|
|
continue 3; // ❌ 错误:不支持继续多层循环
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// ✅ 正确做法:使用 goto
|
|
|
for ($i = 0; $i < 10; $i++) {
|
|
|
for ($j = 0; $j < 10; $j++) {
|
|
|
if ($i * $j > 50) {
|
|
|
goto end_loop; // ✅ 使用 goto 跳出
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
end_loop:
|
|
|
echo "Loop ended";
|
|
|
|
|
|
// ✅ 正确做法:使用标志变量
|
|
|
$found = false;
|
|
|
for ($i = 0; $i < 10 && !$found; $i++) {
|
|
|
for ($j = 0; $j < 10 && !$found; $j++) {
|
|
|
if ($i * $j > 50) {
|
|
|
$found = true; // ✅ 设置标志
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// ✅ 正确做法:提取为函数并使用 return
|
|
|
function findValue() {
|
|
|
for ($i = 0; $i < 10; $i++) {
|
|
|
for ($j = 0; $j < 10; $j++) {
|
|
|
if ($i * $j > 50) {
|
|
|
return [$i, $j]; // ✅ 直接返回
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
$result = findValue();
|
|
|
```
|
|
|
|
|
|
**与标准 PHP 的区别**:
|
|
|
|
|
|
在标准 PHP 中:
|
|
|
```php
|
|
|
<?php
|
|
|
// ✅ PHP 支持 break/continue N
|
|
|
for ($i = 0; $i < 10; $i++) {
|
|
|
for ($j = 0; $j < 10; $j++) {
|
|
|
if ($i * $j > 50) {
|
|
|
break 2; // 跳出两层循环
|
|
|
}
|
|
|
echo "$i * $j = " . ($i * $j) . "\n";
|
|
|
}
|
|
|
}
|
|
|
echo "Done";
|
|
|
```
|
|
|
|
|
|
在 AOT 编译器中:
|
|
|
```php
|
|
|
<?php
|
|
|
// ❌ AOT 编译器会在编译时报错
|
|
|
for ($i = 0; $i < 10; $i++) {
|
|
|
for ($j = 0; $j < 10; $j++) {
|
|
|
if ($i * $j > 50) {
|
|
|
break 2; // Compile Error: break/continue with level > 1 is not supported
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**原因**:
|
|
|
- 多层 break/continue 需要复杂的控制流分析
|
|
|
- C++ 后端没有直接对应的语法结构
|
|
|
- 生成的代码难以优化和维护
|
|
|
- 容易导致代码逻辑混乱,降低可读性
|
|
|
- 与现代编程最佳实践不符
|
|
|
|
|
|
**相关测试文件**:
|
|
|
- 涉及 `break N` 或 `continue N` (N > 1) 的测试文件 (SKIP)
|
|
|
|
|
|
**替代方案**:
|
|
|
|
|
|
1. **使用 goto 语句**
|
|
|
```php
|
|
|
<?php
|
|
|
for ($i = 0; $i < 10; $i++) {
|
|
|
for ($j = 0; $j < 10; $j++) {
|
|
|
for ($k = 0; $k < 10; $k++) {
|
|
|
if ($i + $j + $k > 20) {
|
|
|
goto exit_loops; // 跳出所有循环
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
exit_loops:
|
|
|
echo "Exited loops at i=$i, j=$j, k=$k";
|
|
|
```
|
|
|
|
|
|
2. **使用标志变量**
|
|
|
```php
|
|
|
<?php
|
|
|
$should_break = false;
|
|
|
|
|
|
for ($i = 0; $i < 10 && !$should_break; $i++) {
|
|
|
for ($j = 0; $j < 10 && !$should_break; $j++) {
|
|
|
if ($i * $j > 50) {
|
|
|
$should_break = true;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
3. **提取为函数并使用 return**
|
|
|
```php
|
|
|
<?php
|
|
|
function searchMatrix(array $matrix, $target): ?array {
|
|
|
foreach ($matrix as $i => $row) {
|
|
|
foreach ($row as $j => $value) {
|
|
|
if ($value === $target) {
|
|
|
return [$i, $j]; // 找到即返回
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
$result = searchMatrix($matrix, 42);
|
|
|
```
|
|
|
|
|
|
4. **使用 try/catch 异常机制**
|
|
|
```php
|
|
|
<?php
|
|
|
class LoopBreakException extends Exception {}
|
|
|
|
|
|
try {
|
|
|
for ($i = 0; $i < 10; $i++) {
|
|
|
for ($j = 0; $j < 10; $j++) {
|
|
|
if ($i * $j > 50) {
|
|
|
throw new LoopBreakException("Found at $i, $j");
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
} catch (LoopBreakException $e) {
|
|
|
echo $e->getMessage();
|
|
|
}
|
|
|
```
|
|
|
|
|
|
5. **重构为单层循环**
|
|
|
```php
|
|
|
<?php
|
|
|
// 将多维索引转换为单维索引
|
|
|
$total = 10 * 10;
|
|
|
for ($index = 0; $index < $total; $index++) {
|
|
|
$i = intdiv($index, 10);
|
|
|
$j = $index % 10;
|
|
|
|
|
|
if ($i * $j > 50) {
|
|
|
break; // 只需普通 break
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**最佳实践建议**:
|
|
|
|
|
|
- ✅ **优先使用函数提取**:将复杂的多层循环逻辑提取为独立函数,使用 return 退出
|
|
|
- ✅ **使用标志变量**:对于简单的双层循环,使用布尔标志更清晰
|
|
|
- ⚠️ **谨慎使用 goto**:虽然有效,但过度使用会降低代码可读性
|
|
|
- ⚠️ **避免深层嵌套**:如果需要使用 break 3 或更高层级,考虑重构代码结构
|
|
|
- ❌ **不要依赖异常控制流程**:try/catch 方案仅作为最后手段,性能较差
|
|
|
|
|
|
---
|
|
|
|
|
|
### 11. 字符串越界访问行为差异
|
|
|
|
|
|
**状态**: 行为不一致
|
|
|
**PHP 版本**: 所有版本
|
|
|
**描述**: AOT 编译器对字符串越界访问的处理与标准 PHP 不同。当访问超出字符串长度的索引时,AOT 编译器会抛出致命错误(Fatal Error),而标准 PHP 只会产生警告(Warning)并返回空字符串。
|
|
|
|
|
|
**示例代码**:
|
|
|
```
|
|
|
<?php
|
|
|
$str = "Hello"; // 长度为 5
|
|
|
|
|
|
// ❌ AOT 编译器:抛出致命错误
|
|
|
$char = $str[100]; // Fatal error: Uncaught Error: String offset `100` out of range
|
|
|
|
|
|
// ✅ 标准 PHP:产生警告,返回空字符串
|
|
|
$char = $str[100]; // Warning: Uninitialized string offset 100
|
|
|
// $char = ""
|
|
|
|
|
|
// ❌ AOT 编译器:负数索引也会检查
|
|
|
$char = $str[-1]; // Fatal error: String offset `-1` out of range
|
|
|
|
|
|
// ✅ 标准 PHP:负数索引也产生警告
|
|
|
$char = $str[-1]; // Warning: Uninitialized string offset -1
|
|
|
// $char = ""
|
|
|
```
|
|
|
|
|
|
**与标准 PHP 的区别**:
|
|
|
|
|
|
在标准 PHP 中:
|
|
|
```php
|
|
|
<?php
|
|
|
$str = "Hello";
|
|
|
echo strlen($str); // 输出:5
|
|
|
|
|
|
// 访问越界索引 - 只产生警告
|
|
|
$char = $str[10];
|
|
|
var_dump($char); // 输出:string(0) ""
|
|
|
// Warning: Uninitialized string offset 10
|
|
|
|
|
|
// 程序继续执行
|
|
|
echo "Program continues"; // 正常输出
|
|
|
```
|
|
|
|
|
|
在 AOT 编译器中:
|
|
|
```php
|
|
|
<?php
|
|
|
$str = "Hello";
|
|
|
|
|
|
// 访问越界索引 - 抛出致命错误
|
|
|
$char = $str[10]; // Fatal error: Uncaught Error: String offset `10` out of range
|
|
|
// Stack trace:
|
|
|
// #0 Unknown(0) : eval()(1): main()
|
|
|
// #1 {main}
|
|
|
// thrown in Unknown(0) : eval() on line 1
|
|
|
|
|
|
// 程序终止,不会继续执行
|
|
|
echo "This will not be printed"; // ❌ 不会执行
|
|
|
```
|
|
|
|
|
|
**原因**:
|
|
|
- AOT 编译器采用更严格的边界检查机制
|
|
|
- 提前发现潜在的 bug,避免静默失败
|
|
|
- 符合现代编程语言的安全理念(如 C++ 的 at() 方法)
|
|
|
- 防止因越界访问导致的不可预测行为
|
|
|
- 提高代码质量和可靠性
|
|
|
|
|
|
**相关测试文件**:
|
|
|
- 涉及字符串越界访问的测试文件需要调整期望输出
|
|
|
- 从期望 Warning 改为期望 Fatal Error
|
|
|
|
|
|
**替代方案**:
|
|
|
|
|
|
1. **访问前检查长度**
|
|
|
```php
|
|
|
<?php
|
|
|
$str = "Hello";
|
|
|
$index = 10;
|
|
|
|
|
|
// ✅ 先检查索引是否有效
|
|
|
if ($index >= 0 && $index < strlen($str)) {
|
|
|
$char = $str[$index];
|
|
|
echo $char;
|
|
|
} else {
|
|
|
echo "Index out of bounds";
|
|
|
}
|
|
|
```
|
|
|
|
|
|
2. **使用三元运算符**
|
|
|
```php
|
|
|
<?php
|
|
|
$str = "Hello";
|
|
|
$index = 10;
|
|
|
|
|
|
// ✅ 安全的字符访问
|
|
|
$char = ($index >= 0 && $index < strlen($str)) ? $str[$index] : '';
|
|
|
echo $char; // 输出空字符串
|
|
|
```
|
|
|
|
|
|
3. **封装安全访问函数**
|
|
|
```php
|
|
|
<?php
|
|
|
function safeCharAccess(string $str, int $index): string {
|
|
|
if ($index >= 0 && $index < strlen($str)) {
|
|
|
return $str[$index];
|
|
|
}
|
|
|
return ''; // 返回空字符串
|
|
|
}
|
|
|
|
|
|
$str = "Hello";
|
|
|
echo safeCharAccess($str, 1); // 输出:e
|
|
|
echo safeCharAccess($str, 10); // 输出:(空)
|
|
|
echo safeCharAccess($str, -1); // 输出:(空)
|
|
|
```
|
|
|
|
|
|
4. **使用 substr 函数**
|
|
|
```php
|
|
|
<?php
|
|
|
$str = "Hello";
|
|
|
|
|
|
// ✅ substr 在越界时返回空字符串,不会报错
|
|
|
$char = substr($str, 10, 1);
|
|
|
var_dump($char); // 输出:string(0) ""
|
|
|
|
|
|
// 注意:substr 性能略低于直接索引访问
|
|
|
```
|
|
|
|
|
|
5. **使用 try/catch 捕获异常**
|
|
|
```php
|
|
|
<?php
|
|
|
$str = "Hello";
|
|
|
|
|
|
try {
|
|
|
$char = $str[10];
|
|
|
echo $char;
|
|
|
} catch (Error $e) {
|
|
|
echo "Caught error: " . $e->getMessage();
|
|
|
// 输出:Caught error: String offset `10` out of range
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**最佳实践建议**:
|
|
|
|
|
|
- ✅ **始终检查边界**:在访问字符串索引前验证索引范围
|
|
|
- ✅ **使用辅助函数**:封装安全的字符访问逻辑,提高代码复用性
|
|
|
- ✅ **明确错误处理**:对于可能越界的场景,使用 try/catch 或条件判断
|
|
|
- ⚠️ **注意性能**:频繁的边界检查会影响性能,关键路径可考虑其他方案
|
|
|
- ❌ **不要依赖警告**:AOT 编译器会将越界访问升级为致命错误
|
|
|
- ❌ **避免硬编码索引**:使用变量和动态计算时要格外小心
|
|
|
|
|
|
**迁移指南**:
|
|
|
|
|
|
如果你的代码在标准 PHP 中依赖越界访问返回空字符串的行为,需要进行以下修改:
|
|
|
|
|
|
```php
|
|
|
<?php
|
|
|
// ❌ 旧代码(在 PHP 中工作,在 AOT 中会崩溃)
|
|
|
$str = getUserInput();
|
|
|
$firstChar = $str[0]; // 如果 $str 为空,AOT 会抛出错误
|
|
|
|
|
|
// ✅ 新代码(兼容 AOT)
|
|
|
$str = getUserInput();
|
|
|
$firstChar = (strlen($str) > 0) ? $str[0] : '';
|
|
|
|
|
|
// 或者
|
|
|
$firstChar = safeCharAccess($str, 0);
|
|
|
```
|
|
|
|
|
|
**常见陷阱**:
|
|
|
|
|
|
1. **空字符串访问**
|
|
|
```php
|
|
|
<?php
|
|
|
$str = "";
|
|
|
// ❌ AOT 错误
|
|
|
$char = $str[0]; // Fatal error: String offset `0` out of range
|
|
|
|
|
|
// ✅ 正确做法
|
|
|
$char = strlen($str) > 0 ? $str[0] : '';
|
|
|
```
|
|
|
|
|
|
2. **循环中的索引**
|
|
|
```php
|
|
|
<?php
|
|
|
$str = "Hello";
|
|
|
for ($i = 0; $i <= strlen($str); $i++) { // ❌ <= 会导致越界
|
|
|
echo $str[$i]; // 最后一次迭代会出错
|
|
|
}
|
|
|
|
|
|
// ✅ 正确做法
|
|
|
for ($i = 0; $i < strlen($str); $i++) { // 使用 <
|
|
|
echo $str[$i];
|
|
|
}
|
|
|
```
|
|
|
|
|
|
3. **用户输入处理**
|
|
|
```php
|
|
|
<?php
|
|
|
// ❌ 不安全
|
|
|
$input = $_GET['char'];
|
|
|
echo $input[0]; // 如果 input 为空会出错
|
|
|
|
|
|
// ✅ 安全
|
|
|
$input = $_GET['char'] ?? '';
|
|
|
if (strlen($input) > 0) {
|
|
|
echo $input[0];
|
|
|
}
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
### 7. 游离代码(全局可执行表达式)
|
|
|
|
|
|
**状态**: 不支持
|
|
|
**PHP 版本**: 所有版本
|
|
|
**描述**: 在函数或类方法之外执行的可执行表达式
|
|
|
|
|
|
**示例代码**:
|
|
|
```
|
|
|
<?php
|
|
|
// ❌ 不支持:游离的可执行表达式
|
|
|
echo "Hello World"; // 直接在全局作用域执行
|
|
|
|
|
|
$a = 10;
|
|
|
$b = 20;
|
|
|
$a + $b; // 表达式没有赋值给任何变量
|
|
|
|
|
|
some_function_call(); // 在全局作用域调用函数
|
|
|
|
|
|
for ($i = 0; $i < 10; $i++) { // 全局循环
|
|
|
echo $i;
|
|
|
}
|
|
|
|
|
|
// ✅ 支持:在函数或方法中
|
|
|
function main() {
|
|
|
echo "Hello World";
|
|
|
some_function_call();
|
|
|
for ($i = 0; $i < 10; $i++) {
|
|
|
echo $i;
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**原因**:
|
|
|
- AOT 编译需要明确的程序入口点
|
|
|
- 游离代码导致编译顺序和初始化问题
|
|
|
- 无法确定代码执行的时机和上下文
|
|
|
- 不利于优化和静态分析
|
|
|
|
|
|
**相关测试文件**:
|
|
|
- 包含全局可执行代码的测试文件 (SKIP)
|
|
|
|
|
|
**替代方案**:
|
|
|
- 将所有可执行代码包装在 `main()` 函数中
|
|
|
- 使用类和方法组织代码
|
|
|
- 在全局作用域只进行声明(类、函数、常量等)
|
|
|
- 在 `main()` 函数中调用需要的功能
|
|
|
|
|
|
**正确的代码结构示例**:
|
|
|
```
|
|
|
<?php
|
|
|
// 全局声明是允许的
|
|
|
class MyClass {
|
|
|
public function doSomething() {
|
|
|
return "Something";
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function helperFunction() {
|
|
|
return "Helper";
|
|
|
}
|
|
|
|
|
|
const MY_CONSTANT = 'value';
|
|
|
|
|
|
// 所有可执行代码必须在函数中
|
|
|
function main() {
|
|
|
$obj = new MyClass();
|
|
|
echo $obj->doSomething();
|
|
|
echo helperFunction();
|
|
|
echo MY_CONSTANT;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
---
|
|
|
|
|
|
## ⏳ 尚未支持但计划支持的语法 (Pending Support)
|
|
|
|
|
|
以下语法目前不支持,但已在开发计划中。
|
|
|
|
|
|
### 1. Traits 基础语法
|
|
|
|
|
|
**状态**: 计划支持
|
|
|
**PHP 版本**: 5.4+
|
|
|
**描述**: 代码复用机制
|
|
|
|
|
|
**示例代码**:
|
|
|
```php
|
|
|
trait Greeting {
|
|
|
public function sayHello() {
|
|
|
return "Hello";
|
|
|
}
|
|
|
}
|
|
|
|
|
|
class Person {
|
|
|
use Greeting;
|
|
|
}
|
|
|
|
|
|
$person = new Person();
|
|
|
echo $person->sayHello(); // 输出 "Hello"
|
|
|
```
|
|
|
|
|
|
**当前问题**:
|
|
|
- Trait 的代码注入机制复杂
|
|
|
- 方法优先级和冲突解决需要特殊处理
|
|
|
- 抽象方法和接口的交互需要完善
|
|
|
|
|
|
**相关测试文件**:
|
|
|
- `tests/aot/trait-basic.phpt` (SKIP - PENDING)
|
|
|
|
|
|
**预计支持时间**: 未来版本
|
|
|
|
|
|
---
|
|
|
|
|
|
### 2. 在类中使用 Traits
|
|
|
|
|
|
**状态**: 计划支持
|
|
|
**PHP 版本**: 5.4+
|
|
|
**描述**: 类中引入 trait 的方法
|
|
|
|
|
|
**示例代码**:
|
|
|
```php
|
|
|
trait Loggable {
|
|
|
public function log($message) {
|
|
|
echo "[LOG]: {$message}\n";
|
|
|
}
|
|
|
}
|
|
|
|
|
|
trait Timestamps {
|
|
|
public function getCreatedAt() {
|
|
|
return date('Y-m-d H:i:s');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
class User {
|
|
|
use Loggable, Timestamps;
|
|
|
|
|
|
private $name;
|
|
|
|
|
|
public function __construct($name) {
|
|
|
$this->name = $name;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
$user = new User('John');
|
|
|
$user->log('User created');
|
|
|
echo $user->getCreatedAt();
|
|
|
```
|
|
|
|
|
|
**当前问题**:
|
|
|
- 多个 trait 的组合逻辑
|
|
|
- 命名冲突的处理
|
|
|
- 访问修饰符的继承规则
|
|
|
|
|
|
**相关测试文件**:
|
|
|
- `tests/aot/trait-basic.phpt` (SKIP - PENDING)
|
|
|
|
|
|
**预计支持时间**: 未来版本
|
|
|
|
|
|
---
|
|
|
|
|
|
## ✅ 已支持的语法 (Supported)
|
|
|
|
|
|
以下为主要已支持的 PHP 语法特性(部分列表):
|
|
|
|
|
|
### 基础语法
|
|
|
- ✅ 算术运算符 (`+`, `-`, `*`, `/`, `%`, `**`)
|
|
|
- ✅ 比较运算符 (`==`, `===`, `!=`, `!==`, `<`, `>`, `<=`, `>=`)
|
|
|
- ✅ 逻辑运算符 (`&&`, `||`, `!`, `xor`)
|
|
|
- ✅ 赋值运算符 (`=`, `+=`, `-=`, `*=`, `/=`, `%=`)
|
|
|
- ✅ 三元运算符 (`?:`, `??`)
|
|
|
- ✅ 空合并运算符 (`??`, `??=`)
|
|
|
|
|
|
### 控制结构
|
|
|
- ✅ if/else/elseif
|
|
|
- ✅ switch/case
|
|
|
- ✅ for/while/do-while
|
|
|
- ✅ foreach (包括引用)
|
|
|
- ✅ break/continue
|
|
|
- ✅ try-catch-finally
|
|
|
- ✅ throw
|
|
|
|
|
|
### 函数
|
|
|
- ✅ 函数定义和调用
|
|
|
- ✅ 参数传递(值传递、引用传递)
|
|
|
- ✅ 默认参数
|
|
|
- ✅ 可变参数 (`...$args`)
|
|
|
- ✅ 命名参数 (PHP 8.0+)
|
|
|
- ✅ 返回类型声明
|
|
|
- ✅ 闭包 (Closure)
|
|
|
- ✅ 箭头函数 (PHP 8.0+)
|
|
|
- ✅ 匿名函数
|
|
|
|
|
|
### 类与对象
|
|
|
- ✅ 类定义和实例化
|
|
|
- ✅ 构造函数和析构函数
|
|
|
- ✅ 属性访问(public/protected/private)
|
|
|
- ✅ 方法调用
|
|
|
- ✅ 静态属性和方法
|
|
|
- ✅ 常量
|
|
|
- ✅ 继承和重写
|
|
|
- ✅ 抽象类和接口
|
|
|
- ✅ 枚举 (PHP 8.1+)
|
|
|
- ✅ 匿名类
|
|
|
- ✅ 对象克隆
|
|
|
- ✅ 序列化/反序列化
|
|
|
|
|
|
### 类型系统
|
|
|
- ✅ 标量类型(int, float, string, bool)
|
|
|
- ✅ 复合类型(array, object, callable, iterable)
|
|
|
- ✅ 可空类型 (`?T`)
|
|
|
- ✅ 联合类型 (PHP 8.0+)
|
|
|
- ✅ mixed 类型
|
|
|
- ✅ void 返回类型
|
|
|
- ✅ never 返回类型 (PHP 8.0+)
|
|
|
- ✅ 严格类型模式 (`declare(strict_types=1)`)
|
|
|
|
|
|
### 数组
|
|
|
- ✅ 数组创建和访问
|
|
|
- ✅ 关联数组
|
|
|
- ✅ 多维数组
|
|
|
- ✅ 数组展开 (`...$array`)
|
|
|
- ✅ list() 解构
|
|
|
- ✅ 数组函数(sort, array_map, array_filter 等)
|
|
|
|
|
|
### 字符串
|
|
|
- ✅ 字符串连接
|
|
|
- ✅ 字符串函数(strlen, substr, str_replace 等)
|
|
|
- ✅ 字符串格式化
|
|
|
- ✅ Heredoc/Nowdoc
|
|
|
|
|
|
### 变量和作用域
|
|
|
- ✅ 变量定义和使用
|
|
|
- ✅ 局部变量
|
|
|
- ✅ 全局变量 (`global`)
|
|
|
- ✅ 静态变量 (`static`)
|
|
|
- ✅ 引用
|
|
|
|
|
|
### 高级特性
|
|
|
- ✅ 命名空间
|
|
|
- ✅ 自动加载
|
|
|
- ✅ Magic 方法(__get, __set, __call, __invoke 等)
|
|
|
- ✅ Iterator 接口
|
|
|
- ✅ 后期静态绑定 (`static::`)
|
|
|
- ✅ Match 表达式 (PHP 8.0+)
|
|
|
- ✅ Constructor 属性提升 (PHP 8.0+)
|
|
|
|
|
|
---
|
|
|
|
|
|
## 📋 测试文件 Skip 标记规范
|
|
|
|
|
|
### Skip 标记格式
|
|
|
|
|
|
对于不支持的测试文件,需要在 `--FILE--` 之前添加 `--SKIPIF--` 部分:
|
|
|
|
|
|
```
|
|
|
--TEST--
|
|
|
测试描述
|
|
|
|
|
|
--SKIPIF--
|
|
|
<?php
|
|
|
if (!extension_loaded('aot')) {
|
|
|
echo "skip AOT extension not loaded";
|
|
|
}
|
|
|
// 或者针对特定语法
|
|
|
echo "skip Generator syntax not supported in AOT";
|
|
|
?>
|
|
|
|
|
|
--FILE--
|
|
|
<?php
|
|
|
// 测试代码
|
|
|
?>
|
|
|
|
|
|
--EXPECT--
|
|
|
期望输出
|
|
|
```
|
|
|
|
|
|
### Skip 原因说明
|
|
|
|
|
|
在 skip 脚本中应清楚说明跳过原因:
|
|
|
|
|
|
1. **Generator**: `echo "skip Generator syntax not supported in AOT";`
|
|
|
2. **可变变量**: `echo "skip Variable variables (\$\$) not supported in AOT";`
|
|
|
3. **Attributes**: `echo "skip Attributes/Annotations not supported in AOT";`
|
|
|
4. **Traits**: `echo "skip Traits not yet supported in AOT";`
|
|
|
|
|
|
---
|
|
|
|
|
|
## 🔧 开发和测试建议
|
|
|
|
|
|
### 对于开发者
|
|
|
|
|
|
1. **避免使用不支持的语法**: 在需要 AOT 编译的代码中,不要使用 generator、可变变量和 attributes
|
|
|
2. **使用替代方案**: 参考本文档提供的替代方案
|
|
|
3. **关注更新**: 定期检查本文档了解新增支持的特性
|
|
|
|
|
|
### 对于测试人员
|
|
|
|
|
|
1. **识别不支持语法**: 运行测试前检查是否使用了不支持的语法
|
|
|
2. **验证 Skip 标记**: 确保相关测试文件正确标记为 skip
|
|
|
3. **报告问题**: 发现未记录的不支持语法时及时报告
|
|
|
|
|
|
### 对于贡献者
|
|
|
|
|
|
1. **实现新特性**: 参考 pending 列表中的语法进行开发
|
|
|
2. **更新文档**: 支持新语法后及时更新本文档
|
|
|
3. **添加测试**: 为新支持的语法添加完整的测试用例
|
|
|
|
|
|
---
|
|
|
|
|
|
## 📊 统计信息
|
|
|
|
|
|
| 类别 | 数量 | 百分比 |
|
|
|
|------|------|--------|
|
|
|
| 不支持的语法 | 12 | - |
|
|
|
| 计划支持的语法 | 2 | - |
|
|
|
| 已支持的语法 | 50+ | ~81% |
|
|
|
|
|
|
**总测试文件数**: 126 个
|
|
|
**Skip 测试数**: 12 个(根据实际标记数量)
|
|
|
**正常测试数**: 114 个
|
|
|
|
|
|
---
|
|
|
|
|
|
## 📝 更新日志
|
|
|
|
|
|
### 2024-04-07
|
|
|
- 新增 3 个不支持的语法特性
|
|
|
- **重复的函数名和类名**: AOT 编译器禁止在同一作用域内定义同名的函数或类,与标准 PHP 不同。PHP 允许后定义覆盖先前的定义,而 AOT 编译器会在编译时报错。
|
|
|
- **break/continue 跳出多层循环**: AOT 编译器不支持 `break N` 或 `continue N` (N > 1) 跳出或继续多层嵌套循环。需要使用 goto、标志变量、函数返回或 try/catch 代替。
|
|
|
- **字符串越界访问行为差异**: AOT 编译器对字符串越界访问抛出致命错误(Fatal Error),而标准 PHP 只产生警告(Warning)。需要添加边界检查或使用安全的访问方式。
|
|
|
- 添加详细的对比说明和替代方案(命名空间、条件定义、goto、标志变量、边界检查等)
|
|
|
- 更新统计信息和快速参考表
|
|
|
|
|
|
### 2024-03-20
|
|
|
- 新增 2 个不支持的语法特性
|
|
|
- **引用参数带有默认值**: 由于编译期内存管理复杂性,不支持 `function foo(array &$ref = [])`
|
|
|
- **变长参数中使用引用**: 由于变长参数的动态性与静态编译冲突,不支持 `function foo(&...$args)`
|
|
|
- 为相关测试文件添加 skip 标记
|
|
|
- 更新统计信息和快速参考表
|
|
|
|
|
|
### 2024-XX-XX
|
|
|
- 初始版本发布
|
|
|
- 记录 3 个不支持的语法特性
|
|
|
- 记录 2 个计划支持的语法特性
|
|
|
- 为相关测试文件添加 skip 标记
|
|
|
|
|
|
---
|
|
|
|
|
|
## 🔗 相关链接
|
|
|
|
|
|
- [PHP 官方文档](https://www.php.net/manual/en/)
|
|
|
- [PHP AOT 编译器项目](README.md)
|
|
|
- [测试运行指南](tests/aot/RUN_TESTS_GUIDE.md)
|
|
|
- [测试覆盖总结](tests/aot/README_TEST_COVERAGE.md)
|
|
|
|
|
|
---
|
|
|
|
|
|
## ❓ 常见问题
|
|
|
|
|
|
### Q: 为什么这些语法不被支持?
|
|
|
A: AOT 编译器采用静态编译方式,某些 PHP 动态特性(如可变变量、生成器)需要在运行时动态解析,与 AOT 的设计理念冲突。
|
|
|
|
|
|
### Q: AOT 编译器允许重复的函数名或类名吗?
|
|
|
A: **不允许**。与标准 PHP 不同,AOT 编译器禁止在同一作用域内定义同名的函数或类。PHP 允许后定义覆盖先前的定义(会产生警告),而 AOT 编译器会在编译期直接报错。这是为了保证符号表的唯一性和编译的确定性。建议使用命名空间、不同的名称或条件定义来避免冲突。
|
|
|
|
|
|
### Q: AOT 编译器支持 break 2 或 continue 2 跳出多层循环吗?
|
|
|
A: **不支持**。AOT 编译器只支持普通的 `break` 和 `continue`(跳出或继续当前层循环),不支持 `break N` 或 `continue N` (N > 1) 跳出或继续多层嵌套循环。替代方案包括:使用 goto 语句、设置标志变量、将逻辑提取为函数并使用 return、或使用 try/catch 异常机制。推荐使用函数提取的方式,代码更清晰易维护。
|
|
|
|
|
|
### Q: AOT 编译器的字符串越界访问行为与 PHP 有什么不同?
|
|
|
A: **行为不一致**。标准 PHP 在访问超出字符串长度的索引时只会产生警告(Warning)并返回空字符串,程序继续执行。而 AOT 编译器会抛出致命错误(Fatal Error: String offset out of range),导致程序终止。这是因为 AOT 采用更严格的边界检查机制。建议在访问字符串索引前先检查长度,或使用 substr() 函数等安全方式。
|
|
|
|
|
|
### Q: 什么时候会支持 Traits?
|
|
|
A: Traits 已在开发计划中,具体支持时间取决于开发进度和社区需求。请查看项目路线图获取最新信息。
|
|
|
|
|
|
### Q: 如何知道某个语法是否被支持?
|
|
|
A: 查阅本文档的“已支持的语法”部分,或尝试编译代码查看是否有错误提示。
|
|
|
|
|
|
### Q: 我可以使用 PHP 8.x 的新特性吗?
|
|
|
A: 大部分 PHP 8.x 特性已被支持,如 Match 表达式、命名参数、联合类型等。但不包括 Attributes。请查看“已支持的语法”列表确认。
|
|
|
|
|
|
### Q: 为什么 innerHTML 不支持?
|
|
|
A: innerHTML 是 JavaScript 的 DOM API,不是 PHP 的功能。PHP AOT 编译器专注于 PHP 语言核心特性,不提供浏览器环境模拟。
|
|
|
|
|
|
### Q: 什么是游离代码?为什么不支持?
|
|
|
A: 游离代码指在函数或方法之外直接执行的可执行表达式(如 echo、函数调用等)。AOT 编译需要明确的程序入口点,所有可执行代码必须在 `main()` 函数或类的方法中。
|
|
|
|
|
|
---
|
|
|
|
|
|
## 📝 快速参考
|
|
|
|
|
|
### 编译模式对比
|
|
|
|
|
|
| 特性 | 扩展模式 (--mode=ext) | 二进制模式 (默认) |
|
|
|
|------|---------------------|------------------|
|
|
|
| **输出文件** | .so / .dll | 可执行文件 |
|
|
|
| **运行环境** | php-fpm | 独立运行 |
|
|
|
| **main() 函数** | ❌ 不需要 | ✅ 必须 |
|
|
|
| **参数支持** | N/A | `main()` 或 `main(int $argc, array $argv)` |
|
|
|
| **使用场景** | Web 应用 | CLI 工具、服务 |
|
|
|
| **加载方式** | PHP 扩展加载 | 直接执行 |
|
|
|
| **依赖** | 需要 PHP 运行时 | 无依赖 |
|
|
|
|
|
|
### 不支持的语法速查表
|
|
|
|
|
|
| 语法 | 状态 | 替代方案 |
|
|
|
|------|------|----------|
|
|
|
| Generator/Yield | ❌ 不支持 | 使用数组或 Iterator |
|
|
|
| 可变变量 ($$) | ❌ 不支持 | 使用数组或对象属性 |
|
|
|
| Attributes | ❌ 不支持 | 使用 PHPDoc 或配置文件 |
|
|
|
| Traits | ⏳ 计划中 | 使用继承或组合模式 |
|
|
|
| innerHTML/DOM | ❌ 不支持 | 使用 DOMDocument 或字符串处理 |
|
|
|
| 游离代码 | ❌ 不支持 | 将所有代码放入 main() 函数 |
|
|
|
| **引用参数默认值** | ❌ 不支持 | 使用值传递 + 返回值或 null 默认值 |
|
|
|
| **变长引用参数** | ❌ 不支持 | 使用数组参数代替 |
|
|
|
| **重复函数/类名** | ❌ 不支持 | 使用命名空间、不同名称或条件定义 |
|
|
|
| **break/continue N** | ❌ 不支持 | 使用 goto、标志变量、函数返回或 try/catch |
|
|
|
| **字符串越界访问** | ⚠️ 行为不一致 | 添加边界检查或使用 substr()
|
|
|
|
|
|
### 正确的代码结构模板
|
|
|
|
|
|
```
|
|
|
<?php
|
|
|
// ✅ 推荐结构
|
|
|
|
|
|
// 1. 类和接口定义(允许在全局)
|
|
|
class MyClass {
|
|
|
public function method() {
|
|
|
// 可执行代码在方法中
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 2. 函数定义(允许在全局)
|
|
|
function myHelper() {
|
|
|
// 可执行代码在函数中
|
|
|
}
|
|
|
|
|
|
// 3. 常量定义(允许在全局)
|
|
|
const MY_CONST = 'value';
|
|
|
|
|
|
// 4. 主程序入口(必须)
|
|
|
function main() {
|
|
|
// 所有可执行代码都在这里
|
|
|
$obj = new MyClass();
|
|
|
echo $obj->method();
|
|
|
echo myHelper();
|
|
|
echo MY_CONST;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### 常见错误示例
|
|
|
|
|
|
```
|
|
|
<?php
|
|
|
// ❌ 错误:游离代码
|
|
|
|
|
|
echo "Hello"; // 错误:不能在全局执行
|
|
|
|
|
|
someFunction(); // 错误:不能在全局调用
|
|
|
|
|
|
for ($i = 0; $i < 10; $i++) { // 错误:不能在全局循环
|
|
|
echo $i;
|
|
|
}
|
|
|
|
|
|
// ✅ 正确:包装在 main() 中
|
|
|
function main() {
|
|
|
echo "Hello";
|
|
|
someFunction();
|
|
|
for ($i = 0; $i < 10; $i++) {
|
|
|
echo $i;
|
|
|
}
|
|
|
} |