# 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 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 std::int(PHP_INT_MAX - 1000)) { // 转为 ZVAL 处理大数 return (int)$a / (int)$b; } return std::float($a) / std::float($b); } ``` #### 3. 渐进式迁移 ```php = $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 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 **在 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 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 版本**: 所有版本 **描述**: 函数参数声明为引用传递同时带有默认值 **示例代码**: ``` 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 = '
Hello
'; $content = $element->innerHTML; // 或尝试访问 DOM 属性 $doc = new DOMDocument(); $doc->loadHTML('

Test

'); $body = $doc->body->innerHTML; // 不支持 ``` **原因**: - PHP AOT 编译器专注于 PHP 语言核心特性 - DOM 操作需要完整的浏览器环境模拟 - innerHTML 是 Web API,不是 PHP 原生功能 **相关测试文件**: - 涉及 DOM 操作的测试文件 (SKIP) **替代方案**: - 使用 PHP 原生的 DOMDocument API - 使用字符串处理函数操作 HTML - 使用专门的 HTML 解析库(如 simplehtmldom) --- ### 9. 重复的函数名和类名 **状态**: 不支持 **PHP 版本**: 所有版本 **描述**: AOT 编译器禁止在同一作用域内定义同名的函数或类,而标准 PHP 允许这种情况(后定义的会覆盖先前的定义) **示例代码**: ``` 1)跳出或继续多层嵌套循环结构。只支持 `break` 和 `continue`(不带数字参数或参数为 1)。 **示例代码**: ``` 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 50) { break 2; // 跳出两层循环 } echo "$i * $j = " . ($i * $j) . "\n"; } } echo "Done"; ``` 在 AOT 编译器中: ```php 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 20) { goto exit_loops; // 跳出所有循环 } } } } exit_loops: echo "Exited loops at i=$i, j=$j, k=$k"; ``` 2. **使用标志变量** ```php 50) { $should_break = true; } } } ``` 3. **提取为函数并使用 return** ```php $row) { foreach ($row as $j => $value) { if ($value === $target) { return [$i, $j]; // 找到即返回 } } } return null; } $result = searchMatrix($matrix, 42); ``` 4. **使用 try/catch 异常机制** ```php 50) { throw new LoopBreakException("Found at $i, $j"); } } } } catch (LoopBreakException $e) { echo $e->getMessage(); } ``` 5. **重构为单层循环** ```php 50) { break; // 只需普通 break } } ``` **最佳实践建议**: - ✅ **优先使用函数提取**:将复杂的多层循环逻辑提取为独立函数,使用 return 退出 - ✅ **使用标志变量**:对于简单的双层循环,使用布尔标志更清晰 - ⚠️ **谨慎使用 goto**:虽然有效,但过度使用会降低代码可读性 - ⚠️ **避免深层嵌套**:如果需要使用 break 3 或更高层级,考虑重构代码结构 - ❌ **不要依赖异常控制流程**:try/catch 方案仅作为最后手段,性能较差 --- ### 11. 字符串越界访问行为差异 **状态**: 行为不一致 **PHP 版本**: 所有版本 **描述**: AOT 编译器对字符串越界访问的处理与标准 PHP 不同。当访问超出字符串长度的索引时,AOT 编译器会抛出致命错误(Fatal Error),而标准 PHP 只会产生警告(Warning)并返回空字符串。 **示例代码**: ``` = 0 && $index < strlen($str)) { $char = $str[$index]; echo $char; } else { echo "Index out of bounds"; } ``` 2. **使用三元运算符** ```php = 0 && $index < strlen($str)) ? $str[$index] : ''; echo $char; // 输出空字符串 ``` 3. **封装安全访问函数** ```php = 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 getMessage(); // 输出:Caught error: String offset `10` out of range } ``` **最佳实践建议**: - ✅ **始终检查边界**:在访问字符串索引前验证索引范围 - ✅ **使用辅助函数**:封装安全的字符访问逻辑,提高代码复用性 - ✅ **明确错误处理**:对于可能越界的场景,使用 try/catch 或条件判断 - ⚠️ **注意性能**:频繁的边界检查会影响性能,关键路径可考虑其他方案 - ❌ **不要依赖警告**:AOT 编译器会将越界访问升级为致命错误 - ❌ **避免硬编码索引**:使用变量和动态计算时要格外小心 **迁移指南**: 如果你的代码在标准 PHP 中依赖越界访问返回空字符串的行为,需要进行以下修改: ```php 0) ? $str[0] : ''; // 或者 $firstChar = safeCharAccess($str, 0); ``` **常见陷阱**: 1. **空字符串访问** ```php 0 ? $str[0] : ''; ``` 2. **循环中的索引** ```php 0) { echo $input[0]; } ``` --- ### 7. 游离代码(全局可执行表达式) **状态**: 不支持 **PHP 版本**: 所有版本 **描述**: 在函数或类方法之外执行的可执行表达式 **示例代码**: ``` 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-- --FILE-- --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() ### 正确的代码结构模板 ``` method(); echo myHelper(); echo MY_CONST; } ``` ### 常见错误示例 ```