- 新增匿名类功能测试文件 tests/aot/anonymous-classes.phpt - 新增数组展开操作符测试文件 tests/aot/array-spread.phpt - 新增箭头函数测试文件 tests/aot/arrow-functions.phpt - 新增属性注解测试文件 tests/aot/attributes.phpt - 新增构造函数属性提升测试文件 tests/aot/constructor-promotion.phpt - 新增生成器测试文件 tests/aot/generators.phpt - 新增 trait 基础功能测试文件 tests/aot/trait-basic.phpt - 更新测试运行脚本支持 main 函数自动调用 - 新增 AOT 编译器语法支持文档 docs/UNSUPPORTED_SYNTAX.md - 记录 ZVAL 与原生类型性能对比和使用建议 - 文档化扩展模式和二进制可执行文件模式差异 - 详细说明不支持的语法特性包括生成器、可变变量等pull/1/head
parent
1441a7e5be
commit
5b0346a4ad
9 changed files with 2058 additions and 0 deletions
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,171 @@ |
|||||||
|
--TEST-- |
||||||
|
Anonymous Classes - Runtime class definition |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
// Test basic anonymous class |
||||||
|
function test_basic_anonymous() { |
||||||
|
$basic = new class { |
||||||
|
public function greet(): string { |
||||||
|
return "Hello from anonymous class"; |
||||||
|
} |
||||||
|
}; |
||||||
|
var_dump($basic->greet()); |
||||||
|
} |
||||||
|
|
||||||
|
// Test anonymous class with constructor |
||||||
|
class Greeter { |
||||||
|
private string $greeting; |
||||||
|
|
||||||
|
public function __construct(string $greeting = "Hello") { |
||||||
|
$this->greeting = $greeting; |
||||||
|
} |
||||||
|
|
||||||
|
public function getGreeting(): string { |
||||||
|
return $this->greeting; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function test_anonymous_with_constructor() { |
||||||
|
$withConstructor = new class("Hi") extends Greeter { |
||||||
|
public function getGreeting(): string { |
||||||
|
return parent::getGreeting() . " World!"; |
||||||
|
} |
||||||
|
}; |
||||||
|
var_dump($withConstructor->getGreeting()); |
||||||
|
} |
||||||
|
|
||||||
|
// Test anonymous class implementing interface |
||||||
|
interface LoggerInterface { |
||||||
|
public function log(string $message): void; |
||||||
|
public function getLogs(): array; |
||||||
|
} |
||||||
|
|
||||||
|
function test_anonymous_interface() { |
||||||
|
$logger = new class implements LoggerInterface { |
||||||
|
private array $logs = []; |
||||||
|
|
||||||
|
public function log(string $message): void { |
||||||
|
$this->logs[] = date('Y-m-d H:i:s') . " - " . $message; |
||||||
|
} |
||||||
|
|
||||||
|
public function getLogs(): array { |
||||||
|
return $this->logs; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
$logger->log("First message"); |
||||||
|
$logger->log("Second message"); |
||||||
|
var_dump(count($logger->getLogs())); |
||||||
|
} |
||||||
|
|
||||||
|
// Test anonymous class with properties |
||||||
|
function test_anonymous_properties() { |
||||||
|
$config = new class { |
||||||
|
public string $name = "Test"; |
||||||
|
private int $value = 42; |
||||||
|
|
||||||
|
public function getValue(): int { |
||||||
|
return $this->value; |
||||||
|
} |
||||||
|
|
||||||
|
public function setValue(int $value): void { |
||||||
|
$this->value = $value; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
var_dump($config->name); |
||||||
|
var_dump($config->getValue()); |
||||||
|
$config->setValue(100); |
||||||
|
var_dump($config->getValue()); |
||||||
|
} |
||||||
|
|
||||||
|
// Test nested anonymous classes |
||||||
|
function test_nested_anonymous() { |
||||||
|
$outer = new class { |
||||||
|
private object $inner; |
||||||
|
|
||||||
|
public function __construct() { |
||||||
|
$this->inner = new class { |
||||||
|
public function getMessage(): string { |
||||||
|
return "From inner class"; |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
public function getInnerMessage(): string { |
||||||
|
return $this->inner->getMessage(); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
var_dump($outer->getInnerMessage()); |
||||||
|
} |
||||||
|
|
||||||
|
// Test anonymous class in array |
||||||
|
function test_anonymous_array() { |
||||||
|
$classes = [ |
||||||
|
new class { public function getType() { return "A"; } }, |
||||||
|
new class { public function getType() { return "B"; } }, |
||||||
|
new class { public function getType() { return "C"; } }, |
||||||
|
]; |
||||||
|
|
||||||
|
foreach ($classes as $class) { |
||||||
|
echo $class->getType() . "\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Test static method in anonymous class |
||||||
|
function test_anonymous_static() { |
||||||
|
$static = new class { |
||||||
|
private static int $counter = 0; |
||||||
|
|
||||||
|
public static function increment(): int { |
||||||
|
return ++self::$counter; |
||||||
|
} |
||||||
|
|
||||||
|
public static function getCounter(): int { |
||||||
|
return self::$counter; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
var_dump($static::increment()); |
||||||
|
var_dump($static::increment()); |
||||||
|
var_dump($static::getCounter()); |
||||||
|
} |
||||||
|
|
||||||
|
function main() { |
||||||
|
// Test basic anonymous |
||||||
|
test_basic_anonymous(); |
||||||
|
|
||||||
|
// Test anonymous with constructor |
||||||
|
test_anonymous_with_constructor(); |
||||||
|
|
||||||
|
// Test anonymous interface |
||||||
|
test_anonymous_interface(); |
||||||
|
|
||||||
|
// Test anonymous properties |
||||||
|
test_anonymous_properties(); |
||||||
|
|
||||||
|
// Test nested anonymous |
||||||
|
test_nested_anonymous(); |
||||||
|
|
||||||
|
// Test anonymous array |
||||||
|
test_anonymous_array(); |
||||||
|
|
||||||
|
// Test anonymous static |
||||||
|
test_anonymous_static(); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(26) "Hello from anonymous class" |
||||||
|
string(9) "Hi World!" |
||||||
|
int(2) |
||||||
|
string(4) "Test" |
||||||
|
int(42) |
||||||
|
int(100) |
||||||
|
string(16) "From inner class" |
||||||
|
A |
||||||
|
B |
||||||
|
C |
||||||
|
int(1) |
||||||
|
int(2) |
||||||
|
int(2) |
||||||
@ -0,0 +1,195 @@ |
|||||||
|
--TEST-- |
||||||
|
Spread Operator in Arrays - Array unpacking with ... |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
// Test basic array spreading |
||||||
|
function test_basic_spread() { |
||||||
|
$part1 = [1, 2, 3]; |
||||||
|
$part2 = [4, 5, 6]; |
||||||
|
return [...$part1, ...$part2]; |
||||||
|
} |
||||||
|
|
||||||
|
// Test spread with additional elements |
||||||
|
function test_spread_with_elements() { |
||||||
|
$middle = [2, 3, 4]; |
||||||
|
return [1, ...$middle, 5]; |
||||||
|
} |
||||||
|
|
||||||
|
// Test multiple spreads |
||||||
|
function test_multiple_spreads() { |
||||||
|
$a = [1, 2]; |
||||||
|
$b = [3, 4]; |
||||||
|
$c = [5, 6]; |
||||||
|
return [...$a, ...$b, ...$c]; |
||||||
|
} |
||||||
|
|
||||||
|
// Test spread with keys |
||||||
|
function test_spread_with_keys() { |
||||||
|
$arr1 = ['a' => 1, 'b' => 2]; |
||||||
|
$arr2 = ['c' => 3, 'd' => 4]; |
||||||
|
return [...$arr1, ...$arr2]; |
||||||
|
} |
||||||
|
|
||||||
|
// Test spread in middle of array |
||||||
|
function create_user_record($id, $name, $extra = []) { |
||||||
|
return [ |
||||||
|
'id' => $id, |
||||||
|
'name' => $name, |
||||||
|
...$extra, |
||||||
|
'active' => true, |
||||||
|
]; |
||||||
|
} |
||||||
|
|
||||||
|
// Test nested spreading |
||||||
|
function test_nested_spread() { |
||||||
|
$inner = [7, 8]; |
||||||
|
$outer = [1, 2, [...$inner], 9, 10]; |
||||||
|
return $outer; |
||||||
|
} |
||||||
|
|
||||||
|
// Test spread with string keys (overwriting) |
||||||
|
function test_string_key_spread() { |
||||||
|
$defaults = ['status' => 'active', 'role' => 'user']; |
||||||
|
$override = ['role' => 'admin']; |
||||||
|
return [...$defaults, ...$override]; |
||||||
|
} |
||||||
|
|
||||||
|
// Test spread empty arrays |
||||||
|
function test_spread_empty() { |
||||||
|
$empty = []; |
||||||
|
$data = [1, 2, 3]; |
||||||
|
return [...$empty, ...$data, ...$empty]; |
||||||
|
} |
||||||
|
|
||||||
|
function main() { |
||||||
|
// Test basic spread |
||||||
|
var_dump(test_basic_spread()); |
||||||
|
|
||||||
|
// Test spread with elements |
||||||
|
var_dump(test_spread_with_elements()); |
||||||
|
|
||||||
|
// Test multiple spreads |
||||||
|
var_dump(test_multiple_spreads()); |
||||||
|
|
||||||
|
// Test spread with keys |
||||||
|
var_dump(test_spread_with_keys()); |
||||||
|
|
||||||
|
// Test spread in function return |
||||||
|
var_dump(create_user_record(1, 'John', ['email' => 'john@example.com'])); |
||||||
|
|
||||||
|
// Test nested spread (note: this creates a nested array) |
||||||
|
var_dump(test_nested_spread()); |
||||||
|
|
||||||
|
// Test string key spread (last value wins) |
||||||
|
var_dump(test_string_key_spread()); |
||||||
|
|
||||||
|
// Test spread empty arrays |
||||||
|
var_dump(test_spread_empty()); |
||||||
|
|
||||||
|
// Test complex real-world example |
||||||
|
$baseConfig = ['debug' => false, 'timeout' => 30]; |
||||||
|
$envConfig = ['timeout' => 60, 'retries' => 3]; |
||||||
|
$config = [...$baseConfig, ...$envConfig]; |
||||||
|
var_dump($config); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
array(6) { |
||||||
|
[0]=> |
||||||
|
int(1) |
||||||
|
[1]=> |
||||||
|
int(2) |
||||||
|
[2]=> |
||||||
|
int(3) |
||||||
|
[3]=> |
||||||
|
int(4) |
||||||
|
[4]=> |
||||||
|
int(5) |
||||||
|
[5]=> |
||||||
|
int(6) |
||||||
|
} |
||||||
|
array(5) { |
||||||
|
[0]=> |
||||||
|
int(1) |
||||||
|
[1]=> |
||||||
|
int(2) |
||||||
|
[2]=> |
||||||
|
int(3) |
||||||
|
[3]=> |
||||||
|
int(4) |
||||||
|
[4]=> |
||||||
|
int(5) |
||||||
|
} |
||||||
|
array(6) { |
||||||
|
[0]=> |
||||||
|
int(1) |
||||||
|
[1]=> |
||||||
|
int(2) |
||||||
|
[2]=> |
||||||
|
int(3) |
||||||
|
[3]=> |
||||||
|
int(4) |
||||||
|
[4]=> |
||||||
|
int(5) |
||||||
|
[5]=> |
||||||
|
int(6) |
||||||
|
} |
||||||
|
array(4) { |
||||||
|
["a"]=> |
||||||
|
int(1) |
||||||
|
["b"]=> |
||||||
|
int(2) |
||||||
|
["c"]=> |
||||||
|
int(3) |
||||||
|
["d"]=> |
||||||
|
int(4) |
||||||
|
} |
||||||
|
array(4) { |
||||||
|
["id"]=> |
||||||
|
int(1) |
||||||
|
["name"]=> |
||||||
|
string(4) "John" |
||||||
|
["email"]=> |
||||||
|
string(16) "john@example.com" |
||||||
|
["active"]=> |
||||||
|
bool(true) |
||||||
|
} |
||||||
|
array(5) { |
||||||
|
[0]=> |
||||||
|
int(1) |
||||||
|
[1]=> |
||||||
|
int(2) |
||||||
|
[2]=> |
||||||
|
array(2) { |
||||||
|
[0]=> |
||||||
|
int(7) |
||||||
|
[1]=> |
||||||
|
int(8) |
||||||
|
} |
||||||
|
[3]=> |
||||||
|
int(9) |
||||||
|
[4]=> |
||||||
|
int(10) |
||||||
|
} |
||||||
|
array(2) { |
||||||
|
["status"]=> |
||||||
|
string(6) "active" |
||||||
|
["role"]=> |
||||||
|
string(5) "admin" |
||||||
|
} |
||||||
|
array(3) { |
||||||
|
[0]=> |
||||||
|
int(1) |
||||||
|
[1]=> |
||||||
|
int(2) |
||||||
|
[2]=> |
||||||
|
int(3) |
||||||
|
} |
||||||
|
array(3) { |
||||||
|
["debug"]=> |
||||||
|
bool(false) |
||||||
|
["timeout"]=> |
||||||
|
int(60) |
||||||
|
["retries"]=> |
||||||
|
int(3) |
||||||
|
} |
||||||
@ -0,0 +1,201 @@ |
|||||||
|
--TEST-- |
||||||
|
Arrow Functions - PHP 8.1+ short closure syntax |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
// Test basic arrow function |
||||||
|
function test_basic_arrow() { |
||||||
|
$numbers = [1, 2, 3, 4, 5]; |
||||||
|
$doubled = array_map(fn($n) => $n * 2, $numbers); |
||||||
|
return $doubled; |
||||||
|
} |
||||||
|
|
||||||
|
// Test arrow function with multiple parameters |
||||||
|
function test_multi_param_arrow() { |
||||||
|
$pairs = [[1, 2], [3, 4], [5, 6]]; |
||||||
|
$sums = array_map(fn($a, $b) => $a + $b, ...$pairs); |
||||||
|
return $sums; |
||||||
|
} |
||||||
|
|
||||||
|
// Test arrow function capturing variables (by value) |
||||||
|
function test_captured_variable($multiplier) { |
||||||
|
$numbers = [1, 2, 3]; |
||||||
|
$multiplied = array_map(fn($n) => $n * $multiplier, $numbers); |
||||||
|
return $multiplied; |
||||||
|
} |
||||||
|
|
||||||
|
// Test nested arrow functions |
||||||
|
function test_nested_arrow() { |
||||||
|
$numbers = [1, 2, 3, 4]; |
||||||
|
$result = array_map( |
||||||
|
fn($n) => array_reduce([1, 2], fn($carry, $x) => $carry * $x, $n), |
||||||
|
$numbers |
||||||
|
); |
||||||
|
return $result; |
||||||
|
} |
||||||
|
|
||||||
|
// Test arrow function in filter |
||||||
|
function test_filter_arrow() { |
||||||
|
$numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; |
||||||
|
$evens = array_filter($numbers, fn($n) => $n % 2 === 0); |
||||||
|
return array_values($evens); |
||||||
|
} |
||||||
|
|
||||||
|
// Test arrow function in reduce |
||||||
|
function test_reduce_arrow() { |
||||||
|
$numbers = [1, 2, 3, 4, 5]; |
||||||
|
$product = array_reduce($numbers, fn($carry, $n) => $carry * $n, 1); |
||||||
|
return $product; |
||||||
|
} |
||||||
|
|
||||||
|
// Test arrow function returning arrays |
||||||
|
function test_array_return_arrow() { |
||||||
|
$items = [1, 2, 3]; |
||||||
|
$transformed = array_map(fn($item) => [$item, $item * 2], $items); |
||||||
|
return $transformed; |
||||||
|
} |
||||||
|
|
||||||
|
// Test chained arrow function calls |
||||||
|
class Calculator { |
||||||
|
private array $numbers; |
||||||
|
|
||||||
|
public function __construct(array $numbers) { |
||||||
|
$this->numbers = $numbers; |
||||||
|
} |
||||||
|
|
||||||
|
public function transform(callable $callback): self { |
||||||
|
$this->numbers = array_map($callback, $this->numbers); |
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
public function filter(callable $callback): self { |
||||||
|
$this->numbers = array_values(array_filter($this->numbers, $callback)); |
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
public function getNumbers(): array { |
||||||
|
return $this->numbers; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main() { |
||||||
|
// Test basic arrow |
||||||
|
var_dump(test_basic_arrow()); |
||||||
|
|
||||||
|
// Test multi-param arrow |
||||||
|
var_dump(test_multi_param_arrow()); |
||||||
|
|
||||||
|
// Test captured variable |
||||||
|
var_dump(test_captured_variable(10)); |
||||||
|
var_dump(test_captured_variable(100)); |
||||||
|
|
||||||
|
// Test nested arrow |
||||||
|
var_dump(test_nested_arrow()); |
||||||
|
|
||||||
|
// Test filter arrow |
||||||
|
var_dump(test_filter_arrow()); |
||||||
|
|
||||||
|
// Test reduce arrow |
||||||
|
var_dump(test_reduce_arrow()); |
||||||
|
|
||||||
|
// Test array return |
||||||
|
var_dump(test_array_return_arrow()); |
||||||
|
|
||||||
|
// Test chained operations |
||||||
|
$calc = new Calculator([1, 2, 3, 4, 5]); |
||||||
|
$result = $calc |
||||||
|
->transform(fn($n) => $n * 2) |
||||||
|
->filter(fn($n) => $n > 5) |
||||||
|
->transform(fn($n) => $n + 1) |
||||||
|
->getNumbers(); |
||||||
|
var_dump($result); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
array(5) { |
||||||
|
[0]=> |
||||||
|
int(2) |
||||||
|
[1]=> |
||||||
|
int(4) |
||||||
|
[2]=> |
||||||
|
int(6) |
||||||
|
[3]=> |
||||||
|
int(8) |
||||||
|
[4]=> |
||||||
|
int(10) |
||||||
|
} |
||||||
|
array(2) { |
||||||
|
[0]=> |
||||||
|
int(4) |
||||||
|
[1]=> |
||||||
|
int(6) |
||||||
|
} |
||||||
|
array(3) { |
||||||
|
[0]=> |
||||||
|
int(10) |
||||||
|
[1]=> |
||||||
|
int(20) |
||||||
|
[2]=> |
||||||
|
int(30) |
||||||
|
} |
||||||
|
array(3) { |
||||||
|
[0]=> |
||||||
|
int(100) |
||||||
|
[1]=> |
||||||
|
int(200) |
||||||
|
[2]=> |
||||||
|
int(300) |
||||||
|
} |
||||||
|
array(4) { |
||||||
|
[0]=> |
||||||
|
int(2) |
||||||
|
[1]=> |
||||||
|
int(4) |
||||||
|
[2]=> |
||||||
|
int(6) |
||||||
|
[3]=> |
||||||
|
int(8) |
||||||
|
} |
||||||
|
array(5) { |
||||||
|
[0]=> |
||||||
|
int(2) |
||||||
|
[1]=> |
||||||
|
int(4) |
||||||
|
[2]=> |
||||||
|
int(6) |
||||||
|
[3]=> |
||||||
|
int(8) |
||||||
|
[4]=> |
||||||
|
int(10) |
||||||
|
} |
||||||
|
int(120) |
||||||
|
array(3) { |
||||||
|
[0]=> |
||||||
|
array(2) { |
||||||
|
[0]=> |
||||||
|
int(1) |
||||||
|
[1]=> |
||||||
|
int(2) |
||||||
|
} |
||||||
|
[1]=> |
||||||
|
array(2) { |
||||||
|
[0]=> |
||||||
|
int(2) |
||||||
|
[1]=> |
||||||
|
int(4) |
||||||
|
} |
||||||
|
[2]=> |
||||||
|
array(2) { |
||||||
|
[0]=> |
||||||
|
int(3) |
||||||
|
[1]=> |
||||||
|
int(6) |
||||||
|
} |
||||||
|
} |
||||||
|
array(3) { |
||||||
|
[0]=> |
||||||
|
int(7) |
||||||
|
[1]=> |
||||||
|
int(9) |
||||||
|
[2]=> |
||||||
|
int(11) |
||||||
|
} |
||||||
@ -0,0 +1,120 @@ |
|||||||
|
--TEST-- |
||||||
|
Attributes (Annotations) - PHP 8+ metadata syntax |
||||||
|
--SKIPIF-- |
||||||
|
<?php |
||||||
|
echo "skip Attributes/Annotations not supported in AOT"; |
||||||
|
?> |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
// Define attribute classes |
||||||
|
#[Attribute(Attribute::TARGET_CLASS)] |
||||||
|
class Route { |
||||||
|
public string $path; |
||||||
|
public array $methods; |
||||||
|
|
||||||
|
public function __construct(string $path, array $methods = ['GET']) { |
||||||
|
$this->path = $path; |
||||||
|
$this->methods = $methods; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_METHOD)] |
||||||
|
class Cache { |
||||||
|
public int $ttl; |
||||||
|
|
||||||
|
public function __construct(int $ttl = 3600) { |
||||||
|
$this->ttl = $ttl; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_PROPERTY)] |
||||||
|
class Column { |
||||||
|
public string $name; |
||||||
|
public string $type; |
||||||
|
|
||||||
|
public function __construct(string $name, string $type = 'string') { |
||||||
|
$this->name = $name; |
||||||
|
$this->type = $type; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Use attributes |
||||||
|
#[Route('/api/users', ['GET', 'POST'])] |
||||||
|
class UserController { |
||||||
|
#[Column('id', 'int')] |
||||||
|
private int $id; |
||||||
|
|
||||||
|
#[Column('name', 'string')] |
||||||
|
private string $name; |
||||||
|
|
||||||
|
#[Cache(ttl: 1800)] |
||||||
|
public function getUsers() { |
||||||
|
return "Getting users"; |
||||||
|
} |
||||||
|
|
||||||
|
#[Cache(ttl: 3600)] |
||||||
|
public function getUser($id) { |
||||||
|
return "Getting user: " . $id; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[Route('/api/posts')] |
||||||
|
class PostController { |
||||||
|
#[Cache] |
||||||
|
public function getPosts() { |
||||||
|
return "Getting posts"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main() { |
||||||
|
// Test class attributes |
||||||
|
$userController = new ReflectionClass(UserController::class); |
||||||
|
$attributes = $userController->getAttributes(); |
||||||
|
var_dump(count($attributes)); |
||||||
|
|
||||||
|
$routeAttr = $attributes[0]->newInstance(); |
||||||
|
var_dump($routeAttr->path); |
||||||
|
var_dump($routeAttr->methods); |
||||||
|
|
||||||
|
// Test method attributes |
||||||
|
$getMethod = $userController->getMethod('getUsers'); |
||||||
|
$methodAttrs = $getMethod->getAttributes(); |
||||||
|
var_dump(count($methodAttrs)); |
||||||
|
|
||||||
|
$cacheAttr = $methodAttrs[0]->newInstance(); |
||||||
|
var_dump($cacheAttr->ttl); |
||||||
|
|
||||||
|
// Test property attributes |
||||||
|
$idProp = $userController->getProperty('id'); |
||||||
|
$propAttrs = $idProp->getAttributes(); |
||||||
|
var_dump(count($propAttrs)); |
||||||
|
|
||||||
|
$columnAttr = $propAttrs[0]->newInstance(); |
||||||
|
var_dump($columnAttr->name); |
||||||
|
var_dump($columnAttr->type); |
||||||
|
|
||||||
|
// Test another class |
||||||
|
$postController = new ReflectionClass(PostController::class); |
||||||
|
$postAttrs = $postController->getAttributes(); |
||||||
|
var_dump(count($postAttrs)); |
||||||
|
|
||||||
|
$postRoute = $postAttrs[0]->newInstance(); |
||||||
|
var_dump($postRoute->path); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
int(1) |
||||||
|
string(11) "/api/users" |
||||||
|
array(2) { |
||||||
|
[0]=> |
||||||
|
string(3) "GET" |
||||||
|
[1]=> |
||||||
|
string(4) "POST" |
||||||
|
} |
||||||
|
int(1) |
||||||
|
int(1800) |
||||||
|
int(1) |
||||||
|
string(2) "id" |
||||||
|
string(3) "int" |
||||||
|
int(1) |
||||||
|
string(11) "/api/posts" |
||||||
@ -0,0 +1,140 @@ |
|||||||
|
--TEST-- |
||||||
|
Constructor Property Promotion - PHP 8+ concise class syntax |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
// Test basic constructor promotion |
||||||
|
class Point { |
||||||
|
public function __construct( |
||||||
|
public float $x = 0.0, |
||||||
|
public float $y = 0.0 |
||||||
|
) {} |
||||||
|
|
||||||
|
public function distance(): float { |
||||||
|
return sqrt($this->x * $this->x + $this->y * $this->y); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Test with visibility modifiers |
||||||
|
class User { |
||||||
|
public function __construct( |
||||||
|
public string $name, |
||||||
|
private string $email, |
||||||
|
protected int $age = 18 |
||||||
|
) {} |
||||||
|
|
||||||
|
public function getEmail(): string { |
||||||
|
return $this->email; |
||||||
|
} |
||||||
|
|
||||||
|
public function getAge(): int { |
||||||
|
return $this->age; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Test with nullable types |
||||||
|
class Product { |
||||||
|
public function __construct( |
||||||
|
public string $name, |
||||||
|
public float $price, |
||||||
|
public ?string $description = null, |
||||||
|
public int $quantity = 0 |
||||||
|
) {} |
||||||
|
|
||||||
|
public function getDescription(): string { |
||||||
|
return $this->description ?? 'No description'; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Test mixed traditional and promoted |
||||||
|
class Book { |
||||||
|
private static int $count = 0; |
||||||
|
|
||||||
|
public function __construct( |
||||||
|
public string $title, |
||||||
|
public string $author, |
||||||
|
private float $price |
||||||
|
) { |
||||||
|
self::$count++; |
||||||
|
} |
||||||
|
|
||||||
|
public function getPriceWithTax(float $taxRate): float { |
||||||
|
return $this->price * (1 + $taxRate); |
||||||
|
} |
||||||
|
|
||||||
|
public static function getCount(): int { |
||||||
|
return self::$count; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Test readonly properties (PHP 8.1+) |
||||||
|
class Coordinate { |
||||||
|
public function __construct( |
||||||
|
public readonly float $latitude, |
||||||
|
public readonly float $longitude |
||||||
|
) {} |
||||||
|
} |
||||||
|
|
||||||
|
function main() { |
||||||
|
// Test basic promotion |
||||||
|
$point = new Point(3.0, 4.0); |
||||||
|
var_dump($point->x); |
||||||
|
var_dump($point->y); |
||||||
|
var_dump($point->distance()); |
||||||
|
|
||||||
|
$origin = new Point(); |
||||||
|
var_dump($origin->x); |
||||||
|
var_dump($origin->y); |
||||||
|
|
||||||
|
// Test with visibility |
||||||
|
$user = new User('Alice', 'alice@example.com', 25); |
||||||
|
var_dump($user->name); |
||||||
|
var_dump($user->getEmail()); |
||||||
|
var_dump($user->getAge()); |
||||||
|
|
||||||
|
// Test nullable |
||||||
|
$product1 = new Product('Laptop', 999.99, 'High-performance laptop', 10); |
||||||
|
var_dump($product1->name); |
||||||
|
var_dump($product1->price); |
||||||
|
var_dump($product1->getDescription()); |
||||||
|
var_dump($product1->quantity); |
||||||
|
|
||||||
|
$product2 = new Product('Mouse', 29.99); |
||||||
|
var_dump($product2->name); |
||||||
|
var_dump($product2->getDescription()); |
||||||
|
|
||||||
|
// Test mixed |
||||||
|
$book1 = new Book('PHP Guide', 'John Doe', 49.99); |
||||||
|
var_dump($book1->title); |
||||||
|
var_dump($book1->author); |
||||||
|
var_dump($book1->getPriceWithTax(0.1)); |
||||||
|
|
||||||
|
$book2 = new Book('Advanced PHP', 'Jane Smith', 59.99); |
||||||
|
var_dump(Book::getCount()); |
||||||
|
|
||||||
|
// Test readonly |
||||||
|
$coord = new Coordinate(40.7128, -74.0060); |
||||||
|
var_dump($coord->latitude); |
||||||
|
var_dump($coord->longitude); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
float(3) |
||||||
|
float(4) |
||||||
|
float(5) |
||||||
|
float(0) |
||||||
|
float(0) |
||||||
|
string(5) "Alice" |
||||||
|
string(17) "alice@example.com" |
||||||
|
int(25) |
||||||
|
string(6) "Laptop" |
||||||
|
float(999.99) |
||||||
|
string(23) "High-performance laptop" |
||||||
|
int(10) |
||||||
|
string(5) "Mouse" |
||||||
|
string(14) "No description" |
||||||
|
string(9) "PHP Guide" |
||||||
|
string(8) "John Doe" |
||||||
|
float(54.989000000000004) |
||||||
|
int(2) |
||||||
|
float(40.7128) |
||||||
|
float(-74.006) |
||||||
@ -0,0 +1,92 @@ |
|||||||
|
--TEST-- |
||||||
|
Generators - Yield keyword and generator functions |
||||||
|
--SKIPIF-- |
||||||
|
<?php |
||||||
|
echo "skip Generator syntax not supported in AOT"; |
||||||
|
?> |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
// Test basic generator |
||||||
|
function range_generator($start, $end) { |
||||||
|
for ($i = $start; $i <= $end; $i++) { |
||||||
|
yield $i; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Test generator with keys |
||||||
|
function keyed_generator() { |
||||||
|
yield 'a' => 1; |
||||||
|
yield 'b' => 2; |
||||||
|
yield 'c' => 3; |
||||||
|
} |
||||||
|
|
||||||
|
// Test generator sending values |
||||||
|
function echo_generator() { |
||||||
|
while (true) { |
||||||
|
$value = yield; |
||||||
|
echo "Received: " . $value . "\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Test infinite generator |
||||||
|
function infinite_sequence() { |
||||||
|
$i = 1; |
||||||
|
while (true) { |
||||||
|
yield $i++; |
||||||
|
if ($i > 5) break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main() { |
||||||
|
// Test basic generator |
||||||
|
echo "Basic generator:\n"; |
||||||
|
foreach (range_generator(1, 5) as $num) { |
||||||
|
var_dump($num); |
||||||
|
} |
||||||
|
|
||||||
|
// Test keyed generator |
||||||
|
echo "\nKeyed generator:\n"; |
||||||
|
foreach (keyed_generator() as $key => $value) { |
||||||
|
echo $key . ": "; |
||||||
|
var_dump($value); |
||||||
|
} |
||||||
|
|
||||||
|
// Test limited infinite generator |
||||||
|
echo "\nInfinite sequence (limited):\n"; |
||||||
|
foreach (infinite_sequence() as $num) { |
||||||
|
var_dump($num); |
||||||
|
} |
||||||
|
|
||||||
|
// Test generator object |
||||||
|
$gen = range_generator(10, 12); |
||||||
|
var_dump($gen->valid()); |
||||||
|
$gen->rewind(); |
||||||
|
var_dump($gen->valid()); |
||||||
|
var_dump($gen->current()); |
||||||
|
$gen->next(); |
||||||
|
var_dump($gen->current()); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
Basic generator: |
||||||
|
int(1) |
||||||
|
int(2) |
||||||
|
int(3) |
||||||
|
int(4) |
||||||
|
int(5) |
||||||
|
|
||||||
|
Keyed generator: |
||||||
|
a: int(1) |
||||||
|
b: int(2) |
||||||
|
c: int(3) |
||||||
|
|
||||||
|
Infinite sequence (limited): |
||||||
|
int(1) |
||||||
|
int(2) |
||||||
|
int(3) |
||||||
|
int(4) |
||||||
|
int(5) |
||||||
|
bool(true) |
||||||
|
bool(true) |
||||||
|
int(1) |
||||||
|
int(2) |
||||||
@ -0,0 +1,97 @@ |
|||||||
|
--TEST-- |
||||||
|
Traits - Basic functionality and method inheritance |
||||||
|
--SKIPIF-- |
||||||
|
<?php |
||||||
|
echo "skip Traits not yet supported in AOT"; |
||||||
|
?> |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
// Test basic trait usage |
||||||
|
trait Greeting { |
||||||
|
public function sayHello() { |
||||||
|
return "Hello"; |
||||||
|
} |
||||||
|
|
||||||
|
public function sayGoodbye() { |
||||||
|
return "Goodbye"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class Person { |
||||||
|
use Greeting; |
||||||
|
|
||||||
|
private $name; |
||||||
|
|
||||||
|
public function __construct($name) { |
||||||
|
$this->name = $name; |
||||||
|
} |
||||||
|
|
||||||
|
public function getName() { |
||||||
|
return $this->name; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Test trait with abstract methods |
||||||
|
trait Loggable { |
||||||
|
abstract public function getTableName(); |
||||||
|
|
||||||
|
public function log($message) { |
||||||
|
return "[" . $this->getTableName() . "] " . $message; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class User { |
||||||
|
use Loggable; |
||||||
|
|
||||||
|
public function getTableName() { |
||||||
|
return "users"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Test multiple traits |
||||||
|
trait Timestamps { |
||||||
|
public function getCreatedAt() { |
||||||
|
return "2024-01-01 00:00:00"; |
||||||
|
} |
||||||
|
|
||||||
|
public function getUpdatedAt() { |
||||||
|
return "2024-01-02 00:00:00"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class Post { |
||||||
|
use Greeting, Timestamps; |
||||||
|
|
||||||
|
public function getTitle() { |
||||||
|
return "Test Post"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main() { |
||||||
|
// Test basic trait |
||||||
|
$person = new Person("John"); |
||||||
|
var_dump($person->sayHello()); |
||||||
|
var_dump($person->sayGoodbye()); |
||||||
|
var_dump($person->getName()); |
||||||
|
|
||||||
|
// Test trait with abstract method |
||||||
|
$user = new User(); |
||||||
|
var_dump($user->log("User created")); |
||||||
|
|
||||||
|
// Test multiple traits |
||||||
|
$post = new Post(); |
||||||
|
var_dump($post->sayHello()); |
||||||
|
var_dump($post->getTitle()); |
||||||
|
var_dump($post->getCreatedAt()); |
||||||
|
var_dump($post->getUpdatedAt()); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(5) "Hello" |
||||||
|
string(7) "Goodbye" |
||||||
|
string(4) "John" |
||||||
|
string(22) "[users] User created" |
||||||
|
string(5) "Hello" |
||||||
|
string(9) "Test Post" |
||||||
|
string(19) "2024-01-01 00:00:00" |
||||||
|
string(19) "2024-01-02 00:00:00" |
||||||
Loading…
Reference in new issue