parent
db4d92b616
commit
4c806c8e16
9 changed files with 356 additions and 0 deletions
@ -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…
Reference in new issue