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
pull/15/head
韩天峰 2 months ago
parent 7711a91dec
commit be4835991e
  1. 117
      tests/aot/symfony/eventdispatcher-wrapped-listener-cache.phpt
  2. 94
      tests/aot/symfony/messenger-nullsafe-merge-unpack.phpt
  3. 51
      tests/aot/symfony/runtime-options-global-coalesce.phpt
  4. 72
      tests/aot/symfony/security-badge-dynamic-class-key.phpt

@ -0,0 +1,117 @@
--TEST--
Symfony EventDispatcher style wrapped listener with first-class callable and cached info
--FILE--
<?php
interface SymfonyPriorityDispatcher
{
public function getListenerPriority(string $eventName, callable|array $listener): int;
}
class SymfonyWrappedListenerCase
{
private callable|array $listener;
private ?Closure $optimizedListener;
private ?int $priority = null;
private mixed $stub = null;
private string $pretty;
private string $name;
public bool $called = false;
public function __construct(
callable|array $listener,
?string $name,
private ?SymfonyPriorityDispatcher $dispatcher = null,
) {
$this->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)

@ -0,0 +1,94 @@
--TEST--
Symfony Messenger style nullsafe fallback and with(...array_merge(...array_values()))
--FILE--
<?php
class SymfonyOriginalReceiverStamp
{
public function __construct(private string $name)
{
}
public function getOriginalReceiverName(): string
{
return $this->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.

@ -0,0 +1,51 @@
--TEST--
Symfony Runtime style global options and server/env coalesce assignment
--FILE--
<?php
class SymfonyRuntimeOptionsCase
{
public array $options;
public function __construct(array $options = [])
{
$envKey = $options['env_var_name'] ??= 'APP_ENV';
$debugKey = $options['debug_var_name'] ??= 'APP_DEBUG';
if (isset($options['project_dir']) && $projectDirVar = $options['project_dir_var'] ?? 'APP_PROJECT_DIR') {
$_SERVER[$projectDirVar] = $_ENV[$projectDirVar] = $options['project_dir'];
}
$_SERVER[$envKey] ??= $_ENV[$envKey] ?? 'dev';
$_SERVER[$debugKey] ??= $_ENV[$debugKey] ?? !in_array($_SERVER[$envKey], (array) ($options['prod_envs'] ?? ['prod']), true);
$options['debug'] ??= '1' === (string) $_SERVER[$debugKey];
$options['disable_dotenv'] = true;
$this->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)

@ -0,0 +1,72 @@
--TEST--
Symfony Security style dynamic $badge::class key with coalesce assignment
--FILE--
<?php
interface SymfonyBadgeInterface
{
public function name(): string;
}
class SymfonyCsrfBadge implements SymfonyBadgeInterface
{
public function name(): string
{
return 'csrf';
}
}
class SymfonyUserBadge implements SymfonyBadgeInterface
{
public function name(): string
{
return 'user';
}
}
class SymfonyPassportCase
{
private array $badges = [];
public function addBadge(SymfonyBadgeInterface $badge, ?string $badgeFqcn = null): static
{
$badgeFqcn ??= $badge::class;
$this->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"
}
Loading…
Cancel
Save