diff --git a/tests/aot/symfony/anonymous-class-service-locator.phpt b/tests/aot/symfony/anonymous-class-service-locator.phpt new file mode 100644 index 00000000..742c9b0c --- /dev/null +++ b/tests/aot/symfony/anonymous-class-service-locator.phpt @@ -0,0 +1,41 @@ +--TEST-- +Symfony pattern: anonymous class implements interface with static closure services +--FILE-- + 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) diff --git a/tests/aot/symfony/anonymous-iteratoraggregate-cache.phpt b/tests/aot/symfony/anonymous-iteratoraggregate-cache.phpt new file mode 100644 index 00000000..167540ef --- /dev/null +++ b/tests/aot/symfony/anonymous-iteratoraggregate-cache.phpt @@ -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-- +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" diff --git a/tests/aot/symfony/array-spread-coalesce-precedence.phpt b/tests/aot/symfony/array-spread-coalesce-precedence.phpt new file mode 100644 index 00000000..bc7a54d5 --- /dev/null +++ b/tests/aot/symfony/array-spread-coalesce-precedence.phpt @@ -0,0 +1,27 @@ +--TEST-- +Symfony pattern: array spread with null coalescing expression +--FILE-- + 1], ['vars' => ['b' => 2]])); + var_dump(mergeVars(['a' => 1], [])); +} +?> +--EXPECT-- +array(2) { + ["a"]=> + int(1) + ["b"]=> + int(2) +} +array(1) { + ["a"]=> + int(1) +} diff --git a/tests/aot/symfony/arrow-throw-expression.phpt b/tests/aot/symfony/arrow-throw-expression.phpt new file mode 100644 index 00000000..cd0b9317 --- /dev/null +++ b/tests/aot/symfony/arrow-throw-expression.phpt @@ -0,0 +1,22 @@ +--TEST-- +Symfony pattern: arrow function throw expression +--ENV-- +USE_ZEND_ALLOC=0 +--FILE-- + throw new RuntimeException('failed'); + + try { + $handler(); + } catch (Throwable $e) { + var_dump($e::class); + var_dump($e->getMessage()); + } +} +?> +--EXPECT-- +string(16) "RuntimeException" +string(6) "failed" diff --git a/tests/aot/symfony/coalesce-assign-union-static-return.phpt b/tests/aot/symfony/coalesce-assign-union-static-return.phpt new file mode 100644 index 00000000..2e4e74b7 --- /dev/null +++ b/tests/aot/symfony/coalesce-assign-union-static-return.phpt @@ -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-- +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" diff --git a/tests/aot/symfony/enum-match-throw.phpt b/tests/aot/symfony/enum-match-throw.phpt new file mode 100644 index 00000000..2b861094 --- /dev/null +++ b/tests/aot/symfony/enum-match-throw.phpt @@ -0,0 +1,59 @@ +--TEST-- +Symfony pattern: enum methods with match arms and throw expression +--FILE-- +> 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." diff --git a/tests/aot/symfony/nullable-first-class-callable.phpt b/tests/aot/symfony/nullable-first-class-callable.phpt new file mode 100644 index 00000000..96dbe5bd --- /dev/null +++ b/tests/aot/symfony/nullable-first-class-callable.phpt @@ -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-- +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 diff --git a/tests/aot/symfony/property-hooks-php84.phpt b/tests/aot/symfony/property-hooks-php84.phpt new file mode 100644 index 00000000..2f8c94df --- /dev/null +++ b/tests/aot/symfony/property-hooks-php84.phpt @@ -0,0 +1,28 @@ +--TEST-- +Symfony pattern: PHP 8.4 property hooks +--SKIPIF-- + +--FILE-- + 'value', + ]; + + public string $dependency { + get => $this->services['dependency']; + } +} + +function main(): void +{ + $service = new SymfonyLikeHookedService(); + var_dump($service->dependency); +} +?> +--EXPECT-- +string(5) "value" diff --git a/tests/aot/symfony/sensitive-promoted-readonly.phpt b/tests/aot/symfony/sensitive-promoted-readonly.phpt new file mode 100644 index 00000000..9be68e80 --- /dev/null +++ b/tests/aot/symfony/sensitive-promoted-readonly.phpt @@ -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-- +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)