diff --git a/tests/aot/symfony/bluesky-media-associative-destructuring.phpt b/tests/aot/symfony/bluesky-media-associative-destructuring.phpt new file mode 100644 index 00000000..e80cddc9 --- /dev/null +++ b/tests/aot/symfony/bluesky-media-associative-destructuring.phpt @@ -0,0 +1,66 @@ +--TEST-- +Symfony Notifier Bluesky pattern: foreach associative array destructuring +--FILE-- +contentType; + } + + public function getName(): string + { + return $this->name; + } +} + +function describe_media(array $media): array +{ + $uploaded = []; + + foreach ($media as ['file' => $file, 'description' => $description]) { + $uploaded[] = [ + 'alt' => $description, + 'name' => $file->getName(), + 'mimeType' => $file->getContentType(), + ]; + } + + return $uploaded; +} + +function main(): void +{ + var_dump(describe_media([ + ['file' => new MediaFile('first.png', 'image/png'), 'description' => 'First'], + ['description' => 'Second', 'file' => new MediaFile('second.jpg', 'image/jpeg')], + ])); +} +?> +--EXPECT-- +array(2) { + [0]=> + array(3) { + ["alt"]=> + string(5) "First" + ["name"]=> + string(9) "first.png" + ["mimeType"]=> + string(9) "image/png" + } + [1]=> + array(3) { + ["alt"]=> + string(6) "Second" + ["name"]=> + string(10) "second.jpg" + ["mimeType"]=> + string(10) "image/jpeg" + } +} diff --git a/tests/aot/symfony/config-glob-recursive-callback-filter.phpt b/tests/aot/symfony/config-glob-recursive-callback-filter.phpt new file mode 100644 index 00000000..90cdf6e6 --- /dev/null +++ b/tests/aot/symfony/config-glob-recursive-callback-filter.phpt @@ -0,0 +1,74 @@ +--TEST-- +Symfony Config pattern: RecursiveCallbackFilterIterator and iterator_to_array +--FILE-- + !isset($excludedPrefixes[$path = str_replace('\\', '/', $path)]) + && (str_ends_with($path, '.php') || $file->isDir()) + && '.' !== $file->getBasename()[0] + ), + RecursiveIteratorIterator::LEAVES_ONLY + )); + uksort($files, 'strnatcmp'); + + $relative = []; + foreach ($files as $path => $info) { + if ($info->isFile()) { + $relative[] = substr(str_replace('\\', '/', $path), $prefixLen); + } + } + + return $relative; +} + +function main(): void +{ + $root = sys_get_temp_dir().'/aot_symfony_glob_case'; + cleanup_glob_fixture($root); + @mkdir($root.'/src/skip', 0777, true); + @mkdir($root.'/src/.hidden', 0777, true); + file_put_contents($root.'/src/App.php', ' true]; + var_dump(visible_files($root, $excluded)); + cleanup_glob_fixture($root); +} + +function cleanup_glob_fixture(string $root): void +{ + @unlink($root.'/src/.hidden/Hidden.php'); + @rmdir($root.'/src/.hidden'); + @unlink($root.'/src/skip/Ignored.php'); + @rmdir($root.'/src/skip'); + @unlink($root.'/src/App.php'); + @unlink($root.'/src/App.txt'); + @rmdir($root.'/src'); + @rmdir($root); +} +?> +--CLEAN-- + +--EXPECT-- +array(1) { + [0]=> + string(11) "src/App.php" +} diff --git a/tests/aot/symfony/config-loader-resource-message.phpt b/tests/aot/symfony/config-loader-resource-message.phpt new file mode 100644 index 00000000..b8643e52 --- /dev/null +++ b/tests/aot/symfony/config-loader-resource-message.phpt @@ -0,0 +1,86 @@ +--TEST-- +Symfony Config pattern: loader exception resource formatting and bundle hints +--FILE-- + $v) { + $parts[] = sprintf('%s => %s', $k, resource_to_string($v)); + } + + return sprintf('Array(%s)', implode(', ', $parts)); + } + + if (is_resource($var)) { + return sprintf('Resource(%s)', get_resource_type($var)); + } + + if (null === $var) { + return 'null'; + } + + return (string) $var; +} + +function build_loader_message(mixed $resource, ?Throwable $previous = null, ?string $sourceResource = null, ?string $type = null): string +{ + if (!is_string($resource)) { + try { + $resource = json_encode($resource, JSON_THROW_ON_ERROR); + } catch (JsonException) { + $resource = sprintf('resource of type "%s"', get_debug_type($resource)); + } + } + + $message = ''; + if ($previous) { + if (str_ends_with($previous->getMessage(), '.')) { + $message .= sprintf('%s', substr($previous->getMessage(), 0, -1)).' in '; + } else { + $message .= sprintf('%s', $previous->getMessage()).' in '; + } + $message .= $resource.' '; + $message .= null === $sourceResource + ? sprintf('(which is loaded in resource "%s")', $resource) + : sprintf('(which is being imported from "%s")', $sourceResource); + $message .= '.'; + } elseif (null === $sourceResource) { + $message .= sprintf('Cannot load resource "%s".', $resource); + } else { + $message .= sprintf('Cannot import resource "%s" from "%s".', $resource, $sourceResource); + } + + if ('@' === $resource[0]) { + $parts = explode(DIRECTORY_SEPARATOR, $resource); + $bundle = substr($parts[0], 1); + $message .= sprintf(' Make sure the "%s" bundle is registered.', $bundle); + } elseif (null !== $type) { + $message .= sprintf(' Make sure there is a loader supporting the "%s" type.', $type); + } + + return $message; +} + +final class ResourceObject +{ +} + +function main(): void +{ + $handle = fopen('php://memory', 'r'); + var_dump(resource_to_string(['handle' => $handle, 'object' => new ResourceObject(), 'none' => null])); + var_dump(build_loader_message('@DemoBundle/config.yaml', new RuntimeException('broken.'), null)); + var_dump(build_loader_message(['config' => new ResourceObject()], null, 'services.yaml', 'yaml')); +} +?> +--EXPECTF-- +string(%d) "Array(handle => Resource(stream), object => Object(ResourceObject), none => null)" +string(%d) "broken in @DemoBundle/config.yaml (which is loaded in resource "@DemoBundle/config.yaml"). Make sure the "DemoBundle" bundle is registered." +string(%d) "Cannot import resource "{"config":{}}" from "services.yaml". Make sure there is a loader supporting the "yaml" type." diff --git a/tests/aot/symfony/config-resource-iterator-cache.phpt b/tests/aot/symfony/config-resource-iterator-cache.phpt new file mode 100644 index 00000000..ea89fa24 --- /dev/null +++ b/tests/aot/symfony/config-resource-iterator-cache.phpt @@ -0,0 +1,41 @@ +--TEST-- +Symfony Config pattern: cache Traversable as array with iterator_to_array +--FILE-- +resourceCheckers instanceof Traversable) { + return $this->resourceCheckers; + } + + return $this->resourceCheckers = iterator_to_array($this->resourceCheckers); + } +} + +function main(): void +{ + $cache = new ResourceCheckerCache(new ArrayIterator(['php' => true, 'yaml' => false])); + var_dump($cache->all()); + var_dump($cache->all()); +} +?> +--EXPECT-- +array(2) { + ["php"]=> + bool(true) + ["yaml"]=> + bool(false) +} +array(2) { + ["php"]=> + bool(true) + ["yaml"]=> + bool(false) +} diff --git a/tests/aot/symfony/console-input-unparse-match-array-map.phpt b/tests/aot/symfony/console-input-unparse-match-array-map.phpt new file mode 100644 index 00000000..bccae642 --- /dev/null +++ b/tests/aot/symfony/console-input-unparse-match-array-map.phpt @@ -0,0 +1,78 @@ +--TEST-- +Symfony Console pattern: match(true) returns arrays and array_map option expansion +--FILE-- +negatable; + } + + public function acceptValue(): bool + { + return $this->acceptValue; + } + + public function isArray(): bool + { + return $this->array; + } +} + +function unparse_options(array $rawOptions, array $definition): array +{ + $unparsedOptions = []; + + foreach ($rawOptions as $optionName => $parsedOption) { + $option = $definition[$optionName]; + + $unparsedOptions[] = match (true) { + $option->isNegatable() => [sprintf('--%s%s', $parsedOption ? '' : 'no-', $optionName)], + !$option->acceptValue() => [sprintf('--%s', $optionName)], + $option->isArray() => array_map(static fn ($item) => sprintf('--%s=%s', $optionName, $item), $parsedOption), + default => [sprintf('--%s=%s', $optionName, $parsedOption)], + }; + } + + return array_merge(...$unparsedOptions); +} + +function main(): void +{ + $definition = [ + 'ansi' => new InputOption(true, false, false), + 'verbose' => new InputOption(false, false, false), + 'tag' => new InputOption(false, true, true), + 'env' => new InputOption(false, true, false), + ]; + + var_dump(unparse_options([ + 'ansi' => false, + 'verbose' => true, + 'tag' => ['api', 'worker'], + 'env' => 'prod', + ], $definition)); +} +?> +--EXPECT-- +array(5) { + [0]=> + string(9) "--no-ansi" + [1]=> + string(9) "--verbose" + [2]=> + string(9) "--tag=api" + [3]=> + string(12) "--tag=worker" + [4]=> + string(10) "--env=prod" +} diff --git a/tests/aot/symfony/console-tree-node-from-values.phpt b/tests/aot/symfony/console-tree-node-from-values.phpt new file mode 100644 index 00000000..0653cd55 --- /dev/null +++ b/tests/aot/symfony/console-tree-node-from-values.phpt @@ -0,0 +1,66 @@ +--TEST-- +Symfony Console TreeNode pattern: null coalesce assign and recursive iterable values +--FILE-- + $value) { + if (is_iterable($value)) { + $child = new self((string) $key); + self::fromValues($value, $child); + $node->addChild($child); + } elseif ($value instanceof self) { + $node->addChild($value); + } else { + $node->addChild(new self((string) $value)); + } + } + + return $node; + } + + public function addChild(self $child): void + { + $this->children[] = $child; + } + + public function dump(int $level = 0): void + { + echo str_repeat('-', $level), $this->value, "\n"; + + foreach ($this->children as $child) { + $child->dump($level + 1); + } + } +} + +function main(): void +{ + TreeNode::fromValues([ + 'console' => ['input', 'output'], + 'http' => ['request' => ['query', 'headers']], + new TreeNode('custom'), + ])->dump(); +} +?> +--EXPECT-- +root +-console +--input +--output +-http +--request +---query +---headers +-custom diff --git a/tests/aot/symfony/contracts-reflection-type-string-fallback.phpt b/tests/aot/symfony/contracts-reflection-type-string-fallback.phpt new file mode 100644 index 00000000..cfbe1f1e --- /dev/null +++ b/tests/aot/symfony/contracts-reflection-type-string-fallback.phpt @@ -0,0 +1,36 @@ +--TEST-- +Symfony Contracts pattern: ReflectionNamedType getName or ReflectionType string fallback +--FILE-- +getReturnType(); + $type = $returnType instanceof ReflectionNamedType ? $returnType->getName() : (string) $returnType; + $nullable = $returnType !== null && $returnType->allowsNull(); + + return ($nullable ? '?' : '').$type; +} + +function main(): void +{ + var_dump(describe_return_type('named')); + var_dump(describe_return_type('union')); +} +?> +--EXPECT-- +string(7) "?string" +string(16) "?string|int|null" diff --git a/tests/aot/symfony/doctrine-manager-match-assignment-condition.phpt b/tests/aot/symfony/doctrine-manager-match-assignment-condition.phpt new file mode 100644 index 00000000..4e163c92 --- /dev/null +++ b/tests/aot/symfony/doctrine-manager-match-assignment-condition.phpt @@ -0,0 +1,37 @@ +--TEST-- +Symfony Doctrine pattern: match true with assignment condition and dynamic method call +--FILE-- + 'resetCache', + ]; + + public function reset(string $name): string + { + $method = null; + + return match (true) { + !$method = $this->methodMap[$name] ?? null => 'missing', + default => $this->{$method}($name), + }; + } + + private function resetCache(string $name): string + { + return 'reset:'.$name; + } +} + +function main(): void +{ + $container = new ResetContainer(); + var_dump($container->reset('cache')); + var_dump($container->reset('logger')); +} +?> +--EXPECT-- +string(11) "reset:cache" +string(7) "missing" diff --git a/tests/aot/symfony/doctrine-mapentity-clone-defaults.phpt b/tests/aot/symfony/doctrine-mapentity-clone-defaults.phpt new file mode 100644 index 00000000..d35be197 --- /dev/null +++ b/tests/aot/symfony/doctrine-mapentity-clone-defaults.phpt @@ -0,0 +1,57 @@ +--TEST-- +Symfony Doctrine pattern: clone object and fill defaults with coalesce assign +--FILE-- +class ??= class_exists($class ?? '') || interface_exists($class ?? '', false) ? $class : null; + $clone->objectManager ??= $defaults->objectManager; + $clone->mapping ??= $defaults->mapping; + $clone->id ??= $defaults->id; + $clone->stripNull ??= $defaults->stripNull ?? false; + + return $clone; + } +} + +function main(): void +{ + $defaults = new DefaultOptions(EntityValueResolver::class, 'default', ['id' => 'uuid'], ['uuid'], true); + $options = new DefaultOptions(null, null, null, 'slug', null); + $merged = $options->withDefaults($defaults, EntityValueResolver::class); + + var_dump($merged === $options); + var_dump($merged->class); + var_dump($merged->objectManager); + var_dump($merged->mapping); + var_dump($merged->id); + var_dump($merged->stripNull); +} +?> +--EXPECT-- +bool(false) +string(19) "EntityValueResolver" +string(7) "default" +array(1) { + ["id"]=> + string(4) "uuid" +} +string(4) "slug" +bool(true) diff --git a/tests/aot/symfony/errorhandler-coalesce-assign-negated-condition.phpt b/tests/aot/symfony/errorhandler-coalesce-assign-negated-condition.phpt new file mode 100644 index 00000000..74ad73b2 --- /dev/null +++ b/tests/aot/symfony/errorhandler-coalesce-assign-negated-condition.phpt @@ -0,0 +1,25 @@ +--TEST-- +Symfony ErrorHandler pattern: coalesce assign inside negated condition +--FILE-- + 'default')); + var_dump(resolve_handler(static fn () => 'custom', static fn () => 'default')); + var_dump(resolve_handler(null, null)); +} +?> +--EXPECT-- +string(7) "default" +string(6) "custom" +string(4) "none" diff --git a/tests/aot/symfony/errorhandler-stringable-context.phpt b/tests/aot/symfony/errorhandler-stringable-context.phpt new file mode 100644 index 00000000..a81216b7 --- /dev/null +++ b/tests/aot/symfony/errorhandler-stringable-context.phpt @@ -0,0 +1,61 @@ +--TEST-- +Symfony ErrorHandler pattern: Stringable values in destructured log context +--FILE-- +value; + } +} + +function normalize_logs(array $logs): array +{ + $normalized = []; + + foreach ($logs as [$level, $message, $context]) { + foreach ($context as $key => $val) { + if (null === $val || is_scalar($val) || $val instanceof Stringable) { + $context[$key] = (string) $val; + } + } + + $normalized[] = [$level, $message, $context]; + } + + return $normalized; +} + +function main(): void +{ + var_dump(normalize_logs([ + ['info', 'boot', ['request' => new ContextValue('GET /'), 'count' => 2, 'skip' => []]], + ])); +} +?> +--EXPECT-- +array(1) { + [0]=> + array(3) { + [0]=> + string(4) "info" + [1]=> + string(4) "boot" + [2]=> + array(3) { + ["request"]=> + string(5) "GET /" + ["count"]=> + string(1) "2" + ["skip"]=> + array(0) { + } + } + } +} diff --git a/tests/aot/symfony/expressionlanguage-dynamic-call-unpack.phpt b/tests/aot/symfony/expressionlanguage-dynamic-call-unpack.phpt new file mode 100644 index 00000000..34a79b18 --- /dev/null +++ b/tests/aot/symfony/expressionlanguage-dynamic-call-unpack.phpt @@ -0,0 +1,43 @@ +--TEST-- +Symfony ExpressionLanguage pattern: dynamic callable with unpacked evaluated arguments +--FILE-- +values + $values + $functions; + } +} + +function call_expression_method(object $obj, string $method, ArgumentsNode $arguments): string +{ + if (!is_callable($toCall = [$obj, $method])) { + return sprintf('Unable to call method "%s" of object "%s".', $method, get_debug_type($obj)); + } + + return $toCall(...array_values($arguments->evaluate(['ignored' => 'x'], ['tail' => 'c']))); +} + +function main(): void +{ + var_dump(call_expression_method(new ExpressionTarget(), 'join', new ArgumentsNode(['a', 'b']))); + var_dump(call_expression_method(new ExpressionTarget(), 'missing', new ArgumentsNode([]))); +} +?> +--EXPECT-- +string(7) "a:b,c,x" +string(61) "Unable to call method "missing" of object "ExpressionTarget"." diff --git a/tests/aot/symfony/htmlsanitizer-clone-unset-nested-attrs.phpt b/tests/aot/symfony/htmlsanitizer-clone-unset-nested-attrs.phpt new file mode 100644 index 00000000..dce83496 --- /dev/null +++ b/tests/aot/symfony/htmlsanitizer-clone-unset-nested-attrs.phpt @@ -0,0 +1,52 @@ +--TEST-- +Symfony HtmlSanitizer pattern: clone config, unset nested array state, then write attributes +--FILE-- +blockedElements[$element], $clone->droppedElements[$element]); + + $clone->allowedElements[$element] = []; + foreach ((array) $attributes as $allowedAttr) { + $clone->allowedElements[$element][$allowedAttr] = true; + } + + return $clone; + } +} + +function main(): void +{ + $config = new SanitizerConfig(); + $config->blockedElements['a'] = true; + + $next = $config->allowElement('a', ['href', 'title']); + var_dump($config->blockedElements); + var_dump($next->blockedElements); + var_dump($next->allowedElements); +} +?> +--EXPECT-- +array(1) { + ["a"]=> + bool(true) +} +array(0) { +} +array(1) { + ["a"]=> + array(2) { + ["href"]=> + bool(true) + ["title"]=> + bool(true) + } +} diff --git a/tests/aot/symfony/messenger-nullsafe-transport-fallback.phpt b/tests/aot/symfony/messenger-nullsafe-transport-fallback.phpt new file mode 100644 index 00000000..6fe687ca --- /dev/null +++ b/tests/aot/symfony/messenger-nullsafe-transport-fallback.phpt @@ -0,0 +1,70 @@ +--TEST-- +Symfony Messenger pattern: nullsafe transport fallback with throw expression +--FILE-- +originalReceiverName; + } +} + +final class ReceivedStamp +{ + public function __construct(private string $transportName) + { + } + + public function getTransportName(): string + { + return $this->transportName; + } +} + +final class Envelope +{ + public function __construct(private array $stamps) + { + } + + public function last(string $class): ?object + { + foreach (array_reverse($this->stamps) as $stamp) { + if ($stamp instanceof $class) { + return $stamp; + } + } + + return null; + } +} + +function transport_name(Envelope $envelope): string +{ + return $envelope->last(SentToFailureTransportStamp::class)?->getOriginalReceiverName() + ?? $envelope->last(ReceivedStamp::class)?->getTransportName() + ?? throw new LogicException('A ReceivedStamp is required.'); +} + +function main(): void +{ + var_dump(transport_name(new Envelope([new ReceivedStamp('async')]))); + var_dump(transport_name(new Envelope([new ReceivedStamp('async'), new SentToFailureTransportStamp('failed')]))); + + try { + transport_name(new Envelope([])); + } catch (LogicException $e) { + var_dump($e->getMessage()); + } +} +?> +--EXPECT-- +string(5) "async" +string(6) "failed" +string(28) "A ReceivedStamp is required." diff --git a/tests/aot/symfony/notifier-array-key-first-exception.phpt b/tests/aot/symfony/notifier-array-key-first-exception.phpt new file mode 100644 index 00000000..1f0e9e7c --- /dev/null +++ b/tests/aot/symfony/notifier-array-key-first-exception.phpt @@ -0,0 +1,41 @@ +--TEST-- +Symfony Notifier pattern: array_key_first selects wrapped exception +--FILE-- +exceptions; + } +} + +function unwrap_throwable(Throwable $throwable): Throwable +{ + if ($throwable instanceof HandlerFailedException) { + $exceptions = $throwable->getWrappedExceptions(); + $throwable = $exceptions[array_key_first($exceptions)]; + } + + return $throwable; +} + +function main(): void +{ + $throwable = new HandlerFailedException([ + 'mail' => new InvalidArgumentException('bad address'), + 'sms' => new LogicException('not sent'), + ]); + + $unwrapped = unwrap_throwable($throwable); + echo get_class($unwrapped), ': ', $unwrapped->getMessage(), "\n"; +} +?> +--EXPECT-- +InvalidArgumentException: bad address diff --git a/tests/aot/symfony/notifier-options-nullsafe-coalesce-assign.phpt b/tests/aot/symfony/notifier-options-nullsafe-coalesce-assign.phpt new file mode 100644 index 00000000..7c26c792 --- /dev/null +++ b/tests/aot/symfony/notifier-options-nullsafe-coalesce-assign.phpt @@ -0,0 +1,70 @@ +--TEST-- +Symfony Notifier pattern: nullsafe options array with coalesce assign +--FILE-- +options; + } +} + +final class ChatMessage +{ + public function __construct( + private string $subject, + private ?MessageOptions $options = null, + private ?string $recipientId = null, + ) { + } + + public function getOptions(): ?MessageOptions + { + return $this->options; + } + + public function getRecipientId(): ?string + { + return $this->recipientId; + } + + public function getSubject(): string + { + return $this->subject; + } +} + +function normalize_chat_payload(ChatMessage $message, string $defaultChannel): array +{ + $options = $message->getOptions()?->toArray() ?? []; + $options['channel'] ??= $message->getRecipientId() ?: $defaultChannel; + $options['text'] = $message->getSubject(); + + return array_filter($options); +} + +function main(): void +{ + var_dump(normalize_chat_payload(new ChatMessage('deploy', null, null), '#ops')); + var_dump(normalize_chat_payload(new ChatMessage('alert', new MessageOptions(['channel' => '#custom', 'emoji' => ''])), '#ops')); +} +?> +--EXPECT-- +array(2) { + ["channel"]=> + string(4) "#ops" + ["text"]=> + string(6) "deploy" +} +array(2) { + ["channel"]=> + string(7) "#custom" + ["text"]=> + string(5) "alert" +} diff --git a/tests/aot/symfony/objectmapper-weakmap-cache.phpt b/tests/aot/symfony/objectmapper-weakmap-cache.phpt new file mode 100644 index 00000000..6c96c08f --- /dev/null +++ b/tests/aot/symfony/objectmapper-weakmap-cache.phpt @@ -0,0 +1,44 @@ +--TEST-- +Symfony ObjectMapper pattern: WeakMap object cache with array syntax +--FILE-- +name)); +} + +function main(): void +{ + $source = new Source('symfony'); + $objectMap = new WeakMap(); + $first = map_source($source, $objectMap); + $second = map_source($source, $objectMap); + + var_dump($first === $second); + var_dump($first->name); + var_dump(count($objectMap)); +} +?> +--EXPECT-- +bool(true) +string(7) "SYMFONY" +int(1) diff --git a/tests/aot/symfony/routing-enum-requirement-cases.phpt b/tests/aot/symfony/routing-enum-requirement-cases.phpt new file mode 100644 index 00000000..29a5cd5d --- /dev/null +++ b/tests/aot/symfony/routing-enum-requirement-cases.phpt @@ -0,0 +1,43 @@ +--TEST-- +Symfony Routing pattern: enum cases, class coalesce assign, dynamic instanceof +--FILE-- +name, $class); + } + } + + return implode('|', array_map(static fn ($e) => preg_quote($e->value), $cases)); +} + +function main(): void +{ + var_dump(enum_requirement([Locale::EN, Locale::FR])); + var_dump(enum_requirement([Locale::EN, Status::Draft])); +} +?> +--EXPECT-- +string(5) "en|fr" +string(27) "Status::Draft not in Locale" diff --git a/tests/aot/symfony/routing-extra-query-udiff-replace.phpt b/tests/aot/symfony/routing-extra-query-udiff-replace.phpt new file mode 100644 index 00000000..327ebb3e --- /dev/null +++ b/tests/aot/symfony/routing-extra-query-udiff-replace.phpt @@ -0,0 +1,33 @@ +--TEST-- +Symfony Routing pattern: array_udiff_assoc with array_diff_key and array_replace +--FILE-- + $a == $b ? 0 : 1 + ); + + return array_replace($extra, $queryParameters); +} + +function main(): void +{ + $parameters = ['slug' => 'post', 'page' => '1', 'sort' => 'new', 'debug' => false]; + $variables = ['slug' => true]; + $defaults = ['page' => 1, 'sort' => 'old', 'debug' => false]; + $query = ['sort' => 'top', 'filter' => 'all']; + + var_dump(extra_query($parameters, $variables, $defaults, $query)); +} +?> +--EXPECT-- +array(2) { + ["sort"]=> + string(3) "top" + ["filter"]=> + string(3) "all" +} diff --git a/tests/aot/symfony/routing-null-coalesce-assign-comparison.phpt b/tests/aot/symfony/routing-null-coalesce-assign-comparison.phpt new file mode 100644 index 00000000..4049e0ab --- /dev/null +++ b/tests/aot/symfony/routing-null-coalesce-assign-comparison.phpt @@ -0,0 +1,45 @@ +--TEST-- +Symfony Routing pattern: null comparison with coalesce assign route lookup +--FILE-- +routes[$name] ?? null; + } +} + +function resolve_route(RouteCollectionLookup $routes, string $name, ?Route $route = null): string +{ + if (null === $route ??= $routes->get($name)) { + return 'missing'; + } + + return $route->name; +} + +function main(): void +{ + $routes = new RouteCollectionLookup(['home' => new Route('home')]); + var_dump(resolve_route($routes, 'home')); + var_dump(resolve_route($routes, 'missing')); + var_dump(resolve_route($routes, 'missing', new Route('explicit'))); +} +?> +--EXPECT-- +string(4) "home" +string(7) "missing" +string(8) "explicit" diff --git a/tests/aot/symfony/serializer-cache-key-rawurlencode-strtr.phpt b/tests/aot/symfony/serializer-cache-key-rawurlencode-strtr.phpt new file mode 100644 index 00000000..9c0dd280 --- /dev/null +++ b/tests/aot/symfony/serializer-cache-key-rawurlencode-strtr.phpt @@ -0,0 +1,19 @@ +--TEST-- +Symfony Serializer pattern: cache key with rawurlencode and strtr +--FILE-- + +--EXPECT-- +string(50) "Symfony_Component_Serializer_Mapping_ClassMetadata" +string(24) "App_Model_User%20Profile" diff --git a/tests/aot/symfony/serializer-debug-compact-trace.phpt b/tests/aot/symfony/serializer-debug-compact-trace.phpt new file mode 100644 index 00000000..0212c284 --- /dev/null +++ b/tests/aot/symfony/serializer-debug-compact-trace.phpt @@ -0,0 +1,53 @@ +--TEST-- +Symfony Serializer pattern: isset multiple trace keys and compact return +--FILE-- + 'Serializer', 'function' => 'normalize', 'file' => 'TraceableSerializer.php', 'line' => 170], + ['function' => 'main'], + ]; + + var_dump(normalize_trace_frame($trace, 0)); + var_dump(normalize_trace_frame($trace, 1)); +} +?> +--EXPECT-- +array(3) { + ["name"]=> + string(21) "Serializer::normalize" + ["file"]=> + string(23) "TraceableSerializer.php" + ["line"]=> + int(170) +} +array(3) { + ["name"]=> + string(4) "main" + ["file"]=> + NULL + ["line"]=> + NULL +} diff --git a/tests/aot/symfony/serializer-reflection-property-initialized.phpt b/tests/aot/symfony/serializer-reflection-property-initialized.phpt new file mode 100644 index 00000000..644c2cbd --- /dev/null +++ b/tests/aot/symfony/serializer-reflection-property-initialized.phpt @@ -0,0 +1,39 @@ +--TEST-- +Symfony Serializer pattern: ReflectionProperty checks typed property initialization state +--FILE-- +isInitialized($object); + } + + return $initialized; +} + +function main(): void +{ + $payload = new PartialPayload(); + $payload->foo = null; + + var_dump(initialized_properties($payload, ['foo', 'bar', 'nothing'])); +} +?> +--EXPECT-- +array(3) { + ["foo"]=> + bool(true) + ["bar"]=> + bool(true) + ["nothing"]=> + bool(true) +} diff --git a/tests/aot/symfony/serializer-supported-type-collection.phpt b/tests/aot/symfony/serializer-supported-type-collection.phpt new file mode 100644 index 00000000..d5571581 --- /dev/null +++ b/tests/aot/symfony/serializer-supported-type-collection.phpt @@ -0,0 +1,52 @@ +--TEST-- +Symfony Serializer pattern: supported type matching for ClassName array collections +--FILE-- + $isCacheable) { + if (in_array($supportedType, ['*', 'object'], true) + || $class !== $supportedType && ('object' !== $genericType || !is_subclass_of($class, $supportedType)) + && !($doesClassRepresentCollection && str_ends_with($supportedType, '[]') && is_subclass_of(strstr($class, '[]', true), strstr($supportedType, '[]', true))) + ) { + continue; + } + + $matches[$supportedType] = $isCacheable; + } + + return $matches; +} + +function main(): void +{ + var_dump(supports_type(Dog::class, [Animal::class => true, Cat::class => false, '*' => null])); + var_dump(supports_type(Dog::class.'[]', [Animal::class.'[]' => true, Cat::class.'[]' => false, 'object' => null])); +} +?> +--EXPECT-- +array(1) { + ["Animal"]=> + bool(true) +} +array(1) { + ["Animal[]"]=> + bool(true) +} diff --git a/tests/aot/symfony/typecontext-nested-cache-coalesce-object.phpt b/tests/aot/symfony/typecontext-nested-cache-coalesce-object.phpt new file mode 100644 index 00000000..2a1dab05 --- /dev/null +++ b/tests/aot/symfony/typecontext-nested-cache-coalesce-object.phpt @@ -0,0 +1,44 @@ +--TEST-- +Symfony TypeInfo pattern: nested array cache with coalesce assign returns object +--FILE-- +typeContextCache[$declaringClassName][$calledClassName] ??= new TypeContext($calledClassName, $declaringClassName); + } +} + +function main(): void +{ + $factory = new TypeContextFactory(); + $first = $factory->createFromClassName('Child', 'Parent'); + $second = $factory->createFromClassName('Child', 'Parent'); + $third = $factory->createFromClassName('Other'); + + var_dump($first === $second); + var_dump($first->calledClass, $first->declaringClass); + var_dump($third->calledClass, $third->declaringClass); +} +?> +--EXPECT-- +bool(true) +string(5) "Child" +string(6) "Parent" +string(5) "Other" +string(5) "Other"