test(symfony): 添加AOT编译器Symfony模式测试

pull/14/head
韩天峰 2 months ago
parent db4d92b616
commit 4c806c8e16
  1. 41
      tests/aot/symfony/anonymous-class-service-locator.phpt
  2. 51
      tests/aot/symfony/anonymous-iteratoraggregate-cache.phpt
  3. 27
      tests/aot/symfony/array-spread-coalesce-precedence.phpt
  4. 22
      tests/aot/symfony/arrow-throw-expression.phpt
  5. 44
      tests/aot/symfony/coalesce-assign-union-static-return.phpt
  6. 59
      tests/aot/symfony/enum-match-throw.phpt
  7. 45
      tests/aot/symfony/nullable-first-class-callable.phpt
  8. 28
      tests/aot/symfony/property-hooks-php84.phpt
  9. 39
      tests/aot/symfony/sensitive-promoted-readonly.phpt

@ -0,0 +1,41 @@
--TEST--
Symfony pattern: anonymous class implements interface with static closure services
--FILE--
<?php
interface SymfonyLikeContainer
{
public function get(string $id): mixed;
public function has(string $id): bool;
}
function main(): void
{
$container = new class([
'foo' => static fn (): string => 'FOO',
'bar' => static fn (): string => 'BAR',
]) implements SymfonyLikeContainer {
public function __construct(private array $factories)
{
}
public function get(string $id): mixed
{
return ($this->factories[$id])();
}
public function has(string $id): bool
{
return isset($this->factories[$id]);
}
};
var_dump($container->has('foo'));
var_dump($container->get('foo'));
var_dump($container->has('missing'));
}
?>
--EXPECT--
bool(true)
string(3) "FOO"
bool(false)

@ -0,0 +1,51 @@
--TEST--
Symfony pattern: anonymous IteratorAggregate with cached ??= ArrayObject
--XFAIL--
Known AOT bug: foreach over ArrayObject returned from IteratorAggregate can call ArrayObject::rewind() directly.
--FILE--
<?php
function wrapMiddleware(iterable $handlers): IteratorAggregate
{
if ($handlers instanceof IteratorAggregate) {
return $handlers;
}
if (is_array($handlers)) {
return new ArrayObject($handlers);
}
return new class($handlers) implements IteratorAggregate {
private ArrayObject $cachedIterator;
public function __construct(
private Traversable $middlewareHandlers,
) {
}
public function getIterator(): Traversable
{
return $this->cachedIterator ??= new ArrayObject(iterator_to_array($this->middlewareHandlers, false));
}
};
}
function main(): void
{
$source = new ArrayIterator(['first', 'second']);
$aggregate = wrapMiddleware($source);
foreach ($aggregate as $value) {
var_dump($value);
}
foreach ($aggregate as $value) {
var_dump('cached-'.$value);
}
}
?>
--EXPECT--
string(5) "first"
string(6) "second"
string(12) "cached-first"
string(13) "cached-second"

@ -0,0 +1,27 @@
--TEST--
Symfony pattern: array spread with null coalescing expression
--FILE--
<?php
function mergeVars(array $vars, array $options): array
{
return [...$vars, ...$options['vars'] ?? []];
}
function main(): void
{
var_dump(mergeVars(['a' => 1], ['vars' => ['b' => 2]]));
var_dump(mergeVars(['a' => 1], []));
}
?>
--EXPECT--
array(2) {
["a"]=>
int(1)
["b"]=>
int(2)
}
array(1) {
["a"]=>
int(1)
}

@ -0,0 +1,22 @@
--TEST--
Symfony pattern: arrow function throw expression
--ENV--
USE_ZEND_ALLOC=0
--FILE--
<?php
function main(): void
{
$handler = static fn () => throw new RuntimeException('failed');
try {
$handler();
} catch (Throwable $e) {
var_dump($e::class);
var_dump($e->getMessage());
}
}
?>
--EXPECT--
string(16) "RuntimeException"
string(6) "failed"

@ -0,0 +1,44 @@
--TEST--
Symfony pattern: coalesce assignment inside union typed constructor flow
--XFAIL--
Known AOT bug: writing a private typed property on a cloned object through a variable can use the wrong dynamic property path.
--FILE--
<?php
class SymfonyLikeClock
{
private DateTimeZone $timezone;
public function __construct(DateTimeZone|string|null $timezone = null)
{
$this->timezone = is_string($timezone ??= date_default_timezone_get())
? $this->withTimeZone($timezone)->timezone
: $timezone;
}
public function withTimeZone(DateTimeZone|string $timezone): static
{
$clone = clone $this;
$clone->timezone = is_string($timezone) ? new DateTimeZone($timezone) : $timezone;
return $clone;
}
public function name(): string
{
return $this->timezone->getName();
}
}
function main(): void
{
date_default_timezone_set('UTC');
var_dump((new SymfonyLikeClock())->name());
var_dump((new SymfonyLikeClock('Asia/Shanghai'))->name());
var_dump((new SymfonyLikeClock(new DateTimeZone('Europe/Paris')))->name());
}
?>
--EXPECT--
string(3) "UTC"
string(13) "Asia/Shanghai"
string(12) "Europe/Paris"

@ -0,0 +1,59 @@
--TEST--
Symfony pattern: enum methods with match arms and throw expression
--FILE--
<?php
enum SymfonyLikeColorMode
{
case Ansi4;
case Ansi8;
case Ansi24;
public function convertFromHexToAnsiColorCode(string $hexColor): string
{
$hexColor = str_replace('#', '', $hexColor);
if (3 === strlen($hexColor)) {
$hexColor = $hexColor[0].$hexColor[0].$hexColor[1].$hexColor[1].$hexColor[2].$hexColor[2];
}
$color = hexdec($hexColor);
$r = ($color >> 16) & 255;
$g = ($color >> 8) & 255;
$b = $color & 255;
return match ($this) {
self::Ansi4 => (string) $this->convertFromRGB($r, $g, $b),
self::Ansi8 => '8;5;'.$this->convertFromRGB($r, $g, $b),
self::Ansi24 => sprintf('8;2;%d;%d;%d', $r, $g, $b),
};
}
public function convertFromRGB(int $r, int $g, int $b): int
{
return match ($this) {
self::Ansi4 => (round($b / 255) << 2) | (round($g / 255) << 1) | round($r / 255),
self::Ansi8 => 16 + 36 * (int) round($r / 255 * 5) + 6 * (int) round($g / 255 * 5) + (int) round($b / 255 * 5),
default => throw new InvalidArgumentException("RGB cannot be converted to {$this->name}."),
};
}
}
function main(): void
{
var_dump(SymfonyLikeColorMode::Ansi4->convertFromHexToAnsiColorCode('#fff'));
var_dump(SymfonyLikeColorMode::Ansi8->convertFromHexToAnsiColorCode('#000'));
var_dump(SymfonyLikeColorMode::Ansi24->convertFromHexToAnsiColorCode('#123456'));
try {
SymfonyLikeColorMode::Ansi24->convertFromRGB(1, 2, 3);
} catch (Throwable $e) {
var_dump($e->getMessage());
}
}
?>
--EXPECT--
string(1) "7"
string(6) "8;5;16"
string(12) "8;2;18;52;86"
string(34) "RGB cannot be converted to Ansi24."

@ -0,0 +1,45 @@
--TEST--
Symfony pattern: nullable callable converted to first-class callable
--XFAIL--
Known AOT bug: first-class callable stored in a typed nullable Closure property can crash during shutdown.
--FILE--
<?php
class SymfonyLikeInput
{
private ?Closure $onEmpty = null;
private array $input = [];
public function onEmpty(?callable $onEmpty = null): void
{
$this->onEmpty = null !== $onEmpty ? $onEmpty(...) : null;
}
public function write(string $value): void
{
$this->input[] = $value;
}
public function next(): ?string
{
if (!$this->input && null !== $onEmpty = $this->onEmpty) {
$this->write($onEmpty($this));
}
return array_shift($this->input);
}
}
function main(): void
{
$stream = new SymfonyLikeInput();
$stream->onEmpty(static fn (SymfonyLikeInput $input): string => 'generated');
var_dump($stream->next());
$stream->onEmpty(null);
var_dump($stream->next());
}
?>
--EXPECT--
string(9) "generated"
NULL

@ -0,0 +1,28 @@
--TEST--
Symfony pattern: PHP 8.4 property hooks
--SKIPIF--
<?php
exit('skip PHP 8.4 property hooks are not supported by the AOT compiler');
?>
--FILE--
<?php
class SymfonyLikeHookedService
{
private array $services = [
'dependency' => 'value',
];
public string $dependency {
get => $this->services['dependency'];
}
}
function main(): void
{
$service = new SymfonyLikeHookedService();
var_dump($service->dependency);
}
?>
--EXPECT--
string(5) "value"

@ -0,0 +1,39 @@
--TEST--
Symfony pattern: SensitiveParameter attribute on promoted readonly constructor parameter
--XFAIL--
Known AOT bug: ReflectionProperty::isPromoted() is false for promoted constructor properties.
--FILE--
<?php
class SymfonyLikeTransport
{
public function __construct(
#[SensitiveParameter] private readonly string $apiKey,
private readonly ?string $region = null,
) {
}
public function describe(): string
{
return ($this->region ?? 'default').':'.strlen($this->apiKey);
}
}
function main(): void
{
$transport = new SymfonyLikeTransport('secret-token', region: 'eu');
var_dump($transport->describe());
$param = new ReflectionParameter([SymfonyLikeTransport::class, '__construct'], 'apiKey');
var_dump($param->getAttributes()[0]->getName());
$property = new ReflectionProperty(SymfonyLikeTransport::class, 'apiKey');
var_dump($property->isPromoted());
var_dump($property->isReadOnly());
}
?>
--EXPECT--
string(5) "eu:12"
string(18) "SensitiveParameter"
bool(true)
bool(true)
Loading…
Cancel
Save