From be4835991e2058e644c4bb5ad91386e43c2a5481 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Thu, 9 Jul 2026 12:39:17 +0800 Subject: [PATCH] test(aot): add Symfony framework integration test cases - Add EventDispatcher wrapped listener cache test with first-class callable support - Implement Messenger nullsafe merge unpack functionality test case - Create Runtime options global coalesce assignment test scenario - Add Security badge dynamic class key with coalesce assignment test - Include comprehensive test coverage for Symfony-style PHP patterns - Verify proper handling of nullsafe operators and array unpacking operations --- ...ventdispatcher-wrapped-listener-cache.phpt | 117 ++++++++++++++++++ .../messenger-nullsafe-merge-unpack.phpt | 94 ++++++++++++++ .../runtime-options-global-coalesce.phpt | 51 ++++++++ .../security-badge-dynamic-class-key.phpt | 72 +++++++++++ 4 files changed, 334 insertions(+) create mode 100644 tests/aot/symfony/eventdispatcher-wrapped-listener-cache.phpt create mode 100644 tests/aot/symfony/messenger-nullsafe-merge-unpack.phpt create mode 100644 tests/aot/symfony/runtime-options-global-coalesce.phpt create mode 100644 tests/aot/symfony/security-badge-dynamic-class-key.phpt diff --git a/tests/aot/symfony/eventdispatcher-wrapped-listener-cache.phpt b/tests/aot/symfony/eventdispatcher-wrapped-listener-cache.phpt new file mode 100644 index 00000000..3d9e3fd6 --- /dev/null +++ b/tests/aot/symfony/eventdispatcher-wrapped-listener-cache.phpt @@ -0,0 +1,117 @@ +--TEST-- +Symfony EventDispatcher style wrapped listener with first-class callable and cached info +--FILE-- +listener = $listener; + $this->optimizedListener = $listener instanceof Closure ? $listener : (is_callable($listener) ? $listener(...) : null); + + if (is_array($listener)) { + $this->name = get_debug_type($listener[0]); + $this->pretty = $this->name . '::' . $listener[1]; + } elseif ($listener instanceof Closure) { + $this->pretty = $this->name = 'closure'; + } elseif (is_string($listener)) { + $this->pretty = $this->name = $listener; + } else { + $this->name = get_debug_type($listener); + $this->pretty = $this->name . '::__invoke'; + } + + if (null !== $name) { + $this->name = $name; + } + } + + public function getInfo(string $eventName): array + { + $this->stub ??= $this->pretty . '()'; + + return [ + 'event' => $eventName, + 'priority' => $this->priority ??= $this->dispatcher?->getListenerPriority($eventName, $this->listener), + 'pretty' => $this->pretty, + 'stub' => $this->stub, + ]; + } + + public function __invoke(object $event, string $eventName, SymfonyPriorityDispatcher $dispatcher): void + { + $this->called = true; + $this->priority ??= $dispatcher->getListenerPriority($eventName, $this->listener); + + ($this->optimizedListener ?? $this->listener)($event, $eventName, $dispatcher); + } +} + +class SymfonyWrappedListenerTarget +{ + public array $events = []; + + public function onEvent(object $event, string $eventName): void + { + $this->events[] = $eventName . ':' . $event->name; + } +} + +class SymfonyWrappedListenerDispatcher implements SymfonyPriorityDispatcher +{ + public int $calls = 0; + + public function getListenerPriority(string $eventName, callable|array $listener): int + { + $this->calls++; + + return 32; + } +} + +function main(): void +{ + $target = new SymfonyWrappedListenerTarget(); + $dispatcher = new SymfonyWrappedListenerDispatcher(); + $wrapped = new SymfonyWrappedListenerCase([$target, 'onEvent'], null, $dispatcher); + + var_dump($wrapped->getInfo('kernel.request')); + $wrapped((object) ['name' => 'request'], 'kernel.request', $dispatcher); + var_dump($target->events); + var_dump($wrapped->called); + var_dump($dispatcher->calls); +} +?> +--EXPECTF-- +array(4) { + ["event"]=> + string(14) "kernel.request" + ["priority"]=> + int(32) + ["pretty"]=> + string(%d) "%s::onEvent" + ["stub"]=> + string(%d) "%s::onEvent()" +} +array(1) { + [0]=> + string(22) "kernel.request:request" +} +bool(true) +int(1) diff --git a/tests/aot/symfony/messenger-nullsafe-merge-unpack.phpt b/tests/aot/symfony/messenger-nullsafe-merge-unpack.phpt new file mode 100644 index 00000000..181e5857 --- /dev/null +++ b/tests/aot/symfony/messenger-nullsafe-merge-unpack.phpt @@ -0,0 +1,94 @@ +--TEST-- +Symfony Messenger style nullsafe fallback and with(...array_merge(...array_values())) +--FILE-- +name; + } +} + +class SymfonyReceivedStamp +{ + public function __construct(private string $name) + { + } + + public function getTransportName(): string + { + return $this->name; + } +} + +class SymfonyEnvelopeMergeCase +{ + private array $stamps = []; + + public function with(object ...$stamps): self + { + $clone = clone $this; + foreach ($stamps as $stamp) { + $clone->stamps[$stamp::class][] = $stamp; + } + + return $clone; + } + + public function last(string $class): ?object + { + $stamps = $this->stamps[$class] ?? []; + + return $stamps[count($stamps) - 1] ?? null; + } + + public function all(): array + { + return $this->stamps; + } + + public function count(): int + { + $count = 0; + foreach ($this->stamps as $items) { + $count += count($items); + } + + return $count; + } +} + +function symfony_decode_transport(SymfonyEnvelopeMergeCase $envelope): string +{ + return $envelope->last(SymfonyOriginalReceiverStamp::class)?->getOriginalReceiverName() + ?? $envelope->last(SymfonyReceivedStamp::class)?->getTransportName() + ?? throw new LogicException('A ReceivedStamp is required.'); +} + +function main(): void +{ + $envelope = (new SymfonyEnvelopeMergeCase()) + ->with(new SymfonyReceivedStamp('async'), new SymfonyOriginalReceiverStamp('failed')); + $decoded = new SymfonyEnvelopeMergeCase(); + + $merged = $decoded->with(...array_merge(...array_values($envelope->all()))); + + var_dump(symfony_decode_transport($envelope)); + var_dump($merged->count()); + + try { + symfony_decode_transport(new SymfonyEnvelopeMergeCase()); + } catch (LogicException $e) { + echo $e->getMessage(), "\n"; + } +} +?> +--EXPECT-- +string(6) "failed" +int(2) +A ReceivedStamp is required. diff --git a/tests/aot/symfony/runtime-options-global-coalesce.phpt b/tests/aot/symfony/runtime-options-global-coalesce.phpt new file mode 100644 index 00000000..65aacd60 --- /dev/null +++ b/tests/aot/symfony/runtime-options-global-coalesce.phpt @@ -0,0 +1,51 @@ +--TEST-- +Symfony Runtime style global options and server/env coalesce assignment +--FILE-- +options = $options; + } +} + +function main(): void +{ + unset($_SERVER['APP_ENV'], $_SERVER['APP_DEBUG'], $_SERVER['APP_PROJECT_DIR']); + unset($_ENV['APP_ENV'], $_ENV['APP_DEBUG'], $_ENV['APP_PROJECT_DIR']); + + $runtime = new SymfonyRuntimeOptionsCase(['project_dir' => '/app', 'prod_envs' => ['prod', 'stage']]); + + var_dump($_SERVER['APP_ENV']); + var_dump($_SERVER['APP_DEBUG']); + var_dump($_SERVER['APP_PROJECT_DIR']); + var_dump($_ENV['APP_PROJECT_DIR']); + var_dump($runtime->options['env_var_name']); + var_dump($runtime->options['debug_var_name']); + var_dump($runtime->options['debug']); +} +?> +--EXPECT-- +string(3) "dev" +bool(true) +string(4) "/app" +string(4) "/app" +string(7) "APP_ENV" +string(9) "APP_DEBUG" +bool(true) diff --git a/tests/aot/symfony/security-badge-dynamic-class-key.phpt b/tests/aot/symfony/security-badge-dynamic-class-key.phpt new file mode 100644 index 00000000..df328358 --- /dev/null +++ b/tests/aot/symfony/security-badge-dynamic-class-key.phpt @@ -0,0 +1,72 @@ +--TEST-- +Symfony Security style dynamic $badge::class key with coalesce assignment +--FILE-- +badges[$badgeFqcn] = $badge; + + return $this; + } + + public function hasBadge(string $badgeFqcn): bool + { + return isset($this->badges[$badgeFqcn]); + } + + public function names(): array + { + return array_map(static fn (SymfonyBadgeInterface $badge): string => $badge->name(), $this->badges); + } +} + +function main(): void +{ + $passport = new SymfonyPassportCase(); + $passport + ->addBadge(new SymfonyCsrfBadge()) + ->addBadge(new SymfonyUserBadge(), 'custom.user'); + + var_dump($passport->hasBadge(SymfonyCsrfBadge::class)); + var_dump($passport->hasBadge(SymfonyUserBadge::class)); + var_dump($passport->hasBadge('custom.user')); + var_dump($passport->names()); +} +?> +--EXPECT-- +bool(true) +bool(false) +bool(true) +array(2) { + ["SymfonyCsrfBadge"]=> + string(4) "csrf" + ["custom.user"]=> + string(4) "user" +}