+ */
+class DebugBundle extends Bundle
+{
+ /**
+ * @return void
+ */
+ public function boot()
+ {
+ if ($this->container->getParameter('kernel.debug')) {
+ $container = $this->container;
+
+ // This code is here to lazy load the dump stack. This default
+ // configuration is overridden in CLI mode on 'console.command' event.
+ // The dump data collector is used by default, so dump output is sent to
+ // the WDT. In a CLI context, if dump is used too soon, the data collector
+ // will buffer it, and release it at the end of the script.
+ VarDumper::setHandler(function ($var, ?string $label = null) use ($container) {
+ $dumper = $container->get('data_collector.dump');
+ $cloner = $container->get('var_dumper.cloner');
+ $handler = function ($var, ?string $label = null) use ($dumper, $cloner) {
+ $var = $cloner->cloneVar($var);
+ if (null !== $label) {
+ $var = $var->withContext(['label' => $label]);
+ }
+
+ $dumper->dump($var);
+ };
+ VarDumper::setHandler($handler);
+ $handler($var, $label);
+ });
+ }
+ }
+
+ /**
+ * @return void
+ */
+ public function build(ContainerBuilder $container)
+ {
+ parent::build($container);
+
+ $container->addCompilerPass(new DumpDataCollectorPass());
+ }
+
+ /**
+ * @return void
+ */
+ public function registerCommands(Application $application)
+ {
+ // noop
+ }
+}
diff --git a/lib/symfony/debug-bundle/DependencyInjection/Compiler/DumpDataCollectorPass.php b/lib/symfony/debug-bundle/DependencyInjection/Compiler/DumpDataCollectorPass.php
new file mode 100644
index 0000000000..3d0b27380e
--- /dev/null
+++ b/lib/symfony/debug-bundle/DependencyInjection/Compiler/DumpDataCollectorPass.php
@@ -0,0 +1,49 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bundle\DebugBundle\DependencyInjection\Compiler;
+
+use Symfony\Bundle\WebProfilerBundle\EventListener\WebDebugToolbarListener;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * Registers the file link format for the {@link \Symfony\Component\HttpKernel\DataCollector\DumpDataCollector}.
+ *
+ * @author Christian Flothmann
+ */
+class DumpDataCollectorPass implements CompilerPassInterface
+{
+ /**
+ * @return void
+ */
+ public function process(ContainerBuilder $container)
+ {
+ if (!$container->hasDefinition('data_collector.dump')) {
+ return;
+ }
+
+ $definition = $container->getDefinition('data_collector.dump');
+
+ if (!$container->has('.virtual_request_stack')) {
+ $definition->replaceArgument(3, new Reference('request_stack'));
+ }
+
+ if (!$container->hasParameter('web_profiler.debug_toolbar.mode') || WebDebugToolbarListener::DISABLED === $container->getParameter('web_profiler.debug_toolbar.mode')) {
+ $definition->replaceArgument(3, null);
+ }
+
+ if (!$container->hasParameter('kernel.runtime_mode.web')) {
+ $definition->replaceArgument(5, null);
+ }
+ }
+}
diff --git a/lib/symfony/debug-bundle/DependencyInjection/Configuration.php b/lib/symfony/debug-bundle/DependencyInjection/Configuration.php
new file mode 100644
index 0000000000..caf7359690
--- /dev/null
+++ b/lib/symfony/debug-bundle/DependencyInjection/Configuration.php
@@ -0,0 +1,60 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bundle\DebugBundle\DependencyInjection;
+
+use Symfony\Component\Config\Definition\Builder\TreeBuilder;
+use Symfony\Component\Config\Definition\ConfigurationInterface;
+
+/**
+ * DebugExtension configuration structure.
+ *
+ * @author Nicolas Grekas
+ */
+class Configuration implements ConfigurationInterface
+{
+ public function getConfigTreeBuilder(): TreeBuilder
+ {
+ $treeBuilder = new TreeBuilder('debug');
+
+ $rootNode = $treeBuilder->getRootNode();
+ $rootNode->children()
+ ->integerNode('max_items')
+ ->info('Max number of displayed items past the first level, -1 means no limit')
+ ->min(-1)
+ ->defaultValue(2500)
+ ->end()
+ ->integerNode('min_depth')
+ ->info('Minimum tree depth to clone all the items, 1 is default')
+ ->min(0)
+ ->defaultValue(1)
+ ->end()
+ ->integerNode('max_string_length')
+ ->info('Max length of displayed strings, -1 means no limit')
+ ->min(-1)
+ ->defaultValue(-1)
+ ->end()
+ ->scalarNode('dump_destination')
+ ->info('A stream URL where dumps should be written to')
+ ->example('php://stderr, or tcp://%env(VAR_DUMPER_SERVER)% when using the "server:dump" command')
+ ->defaultNull()
+ ->end()
+ ->enumNode('theme')
+ ->info('Changes the color of the dump() output when rendered directly on the templating. "dark" (default) or "light"')
+ ->example('dark')
+ ->values(['dark', 'light'])
+ ->defaultValue('dark')
+ ->end()
+ ;
+
+ return $treeBuilder;
+ }
+}
diff --git a/lib/symfony/debug-bundle/DependencyInjection/DebugExtension.php b/lib/symfony/debug-bundle/DependencyInjection/DebugExtension.php
new file mode 100644
index 0000000000..d00d6111c1
--- /dev/null
+++ b/lib/symfony/debug-bundle/DependencyInjection/DebugExtension.php
@@ -0,0 +1,102 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bundle\DebugBundle\DependencyInjection;
+
+use Symfony\Bridge\Monolog\Command\ServerLogCommand;
+use Symfony\Bundle\DebugBundle\Command\ServerDumpPlaceholderCommand;
+use Symfony\Component\Config\FileLocator;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Extension\Extension;
+use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\VarDumper\Caster\ReflectionCaster;
+
+/**
+ * DebugExtension.
+ *
+ * @author Nicolas Grekas
+ */
+class DebugExtension extends Extension
+{
+ /**
+ * @return void
+ */
+ public function load(array $configs, ContainerBuilder $container)
+ {
+ $configuration = new Configuration();
+ $config = $this->processConfiguration($configuration, $configs);
+
+ $loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
+ $loader->load('services.php');
+
+ $container->getDefinition('var_dumper.cloner')
+ ->addMethodCall('setMaxItems', [$config['max_items']])
+ ->addMethodCall('setMinDepth', [$config['min_depth']])
+ ->addMethodCall('setMaxString', [$config['max_string_length']])
+ ->addMethodCall('addCasters', [ReflectionCaster::UNSET_CLOSURE_FILE_INFO]);
+
+ if ('dark' !== $config['theme']) {
+ $container->getDefinition('var_dumper.html_dumper')
+ ->addMethodCall('setTheme', [$config['theme']]);
+ }
+
+ if (null === $config['dump_destination']) {
+ $container->getDefinition('var_dumper.command.server_dump')
+ ->setClass(ServerDumpPlaceholderCommand::class)
+ ;
+ } elseif (str_starts_with($config['dump_destination'], 'tcp://')) {
+ $container->getDefinition('debug.dump_listener')
+ ->replaceArgument(2, new Reference('var_dumper.server_connection'))
+ ;
+ $container->getDefinition('data_collector.dump')
+ ->replaceArgument(4, new Reference('var_dumper.server_connection'))
+ ;
+ $container->getDefinition('var_dumper.dump_server')
+ ->replaceArgument(0, $config['dump_destination'])
+ ;
+ $container->getDefinition('var_dumper.server_connection')
+ ->replaceArgument(0, $config['dump_destination'])
+ ;
+ } else {
+ $container->getDefinition('var_dumper.cli_dumper')
+ ->replaceArgument(0, $config['dump_destination'])
+ ;
+ $container->getDefinition('data_collector.dump')
+ ->replaceArgument(4, new Reference('var_dumper.cli_dumper'))
+ ;
+ $container->getDefinition('var_dumper.command.server_dump')
+ ->setClass(ServerDumpPlaceholderCommand::class)
+ ;
+ }
+
+ $container->getDefinition('var_dumper.cli_dumper')
+ ->addMethodCall('setDisplayOptions', [[
+ 'fileLinkFormat' => new Reference('debug.file_link_formatter', ContainerBuilder::IGNORE_ON_INVALID_REFERENCE),
+ ]])
+ ;
+
+ if (!class_exists(Command::class) || !class_exists(ServerLogCommand::class)) {
+ $container->removeDefinition('monolog.command.server_log');
+ }
+ }
+
+ public function getXsdValidationBasePath(): string|false
+ {
+ return __DIR__.'/../Resources/config/schema';
+ }
+
+ public function getNamespace(): string
+ {
+ return 'http://symfony.com/schema/dic/debug';
+ }
+}
diff --git a/lib/symfony/debug-bundle/LICENSE b/lib/symfony/debug-bundle/LICENSE
new file mode 100644
index 0000000000..29f72d5e95
--- /dev/null
+++ b/lib/symfony/debug-bundle/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014-present Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/lib/symfony/debug-bundle/README.md b/lib/symfony/debug-bundle/README.md
new file mode 100644
index 0000000000..bed2f5b6d6
--- /dev/null
+++ b/lib/symfony/debug-bundle/README.md
@@ -0,0 +1,13 @@
+DebugBundle
+===========
+
+DebugBundle provides a tight integration of the Symfony VarDumper component and
+the ServerLogCommand from MonologBridge into the Symfony full-stack framework.
+
+Resources
+---------
+
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/lib/symfony/debug-bundle/Resources/config/schema/debug-1.0.xsd b/lib/symfony/debug-bundle/Resources/config/schema/debug-1.0.xsd
new file mode 100644
index 0000000000..3230688602
--- /dev/null
+++ b/lib/symfony/debug-bundle/Resources/config/schema/debug-1.0.xsd
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/symfony/debug-bundle/Resources/config/services.php b/lib/symfony/debug-bundle/Resources/config/services.php
new file mode 100644
index 0000000000..0193f65a27
--- /dev/null
+++ b/lib/symfony/debug-bundle/Resources/config/services.php
@@ -0,0 +1,141 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+use Monolog\Formatter\FormatterInterface;
+use Symfony\Bridge\Monolog\Command\ServerLogCommand;
+use Symfony\Bridge\Monolog\Formatter\ConsoleFormatter;
+use Symfony\Bridge\Twig\Extension\DumpExtension;
+use Symfony\Component\HttpKernel\DataCollector\DumpDataCollector;
+use Symfony\Component\HttpKernel\EventListener\DumpListener;
+use Symfony\Component\VarDumper\Cloner\VarCloner;
+use Symfony\Component\VarDumper\Command\Descriptor\CliDescriptor;
+use Symfony\Component\VarDumper\Command\Descriptor\HtmlDescriptor;
+use Symfony\Component\VarDumper\Command\ServerDumpCommand;
+use Symfony\Component\VarDumper\Dumper\CliDumper;
+use Symfony\Component\VarDumper\Dumper\ContextProvider\CliContextProvider;
+use Symfony\Component\VarDumper\Dumper\ContextProvider\RequestContextProvider;
+use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider;
+use Symfony\Component\VarDumper\Dumper\ContextualizedDumper;
+use Symfony\Component\VarDumper\Dumper\HtmlDumper;
+use Symfony\Component\VarDumper\Server\Connection;
+use Symfony\Component\VarDumper\Server\DumpServer;
+
+return static function (ContainerConfigurator $container) {
+ $container->parameters()
+ ->set('env(VAR_DUMPER_SERVER)', '127.0.0.1:9912')
+ ;
+
+ $container->services()
+
+ ->set('twig.extension.dump', DumpExtension::class)
+ ->args([
+ service('var_dumper.cloner'),
+ service('var_dumper.html_dumper'),
+ ])
+ ->tag('twig.extension')
+
+ ->set('data_collector.dump', DumpDataCollector::class)
+ ->public()
+ ->args([
+ service('debug.stopwatch')->ignoreOnInvalid(),
+ service('debug.file_link_formatter')->ignoreOnInvalid(),
+ param('kernel.charset'),
+ service('.virtual_request_stack'),
+ null, // var_dumper.cli_dumper or var_dumper.server_connection when debug.dump_destination is set
+ param('kernel.runtime_mode.web'),
+ ])
+ ->tag('data_collector', [
+ 'id' => 'dump',
+ 'template' => '@Debug/Profiler/dump.html.twig',
+ 'priority' => 240,
+ ])
+
+ ->set('debug.dump_listener', DumpListener::class)
+ ->args([
+ service('var_dumper.cloner'),
+ service('var_dumper.cli_dumper'),
+ null,
+ ])
+ ->tag('kernel.event_subscriber')
+
+ ->set('var_dumper.cloner', VarCloner::class)
+ ->public()
+
+ ->set('var_dumper.cli_dumper', CliDumper::class)
+ ->args([
+ null, // debug.dump_destination,
+ param('kernel.charset'),
+ 0, // flags
+ ])
+
+ ->set('var_dumper.contextualized_cli_dumper', ContextualizedDumper::class)
+ ->decorate('var_dumper.cli_dumper')
+ ->args([
+ service('var_dumper.contextualized_cli_dumper.inner'),
+ [
+ 'source' => inline_service(SourceContextProvider::class)->args([
+ param('kernel.charset'),
+ param('kernel.project_dir'),
+ service('debug.file_link_formatter')->nullOnInvalid(),
+ ]),
+ ],
+ ])
+
+ ->set('var_dumper.html_dumper', HtmlDumper::class)
+ ->args([
+ null,
+ param('kernel.charset'),
+ 0, // flags
+ ])
+ ->call('setDisplayOptions', [
+ ['fileLinkFormat' => service('debug.file_link_formatter')->ignoreOnInvalid()],
+ ])
+
+ ->set('var_dumper.server_connection', Connection::class)
+ ->args([
+ '', // server host
+ [
+ 'source' => inline_service(SourceContextProvider::class)->args([
+ param('kernel.charset'),
+ param('kernel.project_dir'),
+ service('debug.file_link_formatter')->nullOnInvalid(),
+ ]),
+ 'request' => inline_service(RequestContextProvider::class)->args([service('request_stack')]),
+ 'cli' => inline_service(CliContextProvider::class),
+ ],
+ ])
+
+ ->set('var_dumper.dump_server', DumpServer::class)
+ ->args([
+ '', // server host
+ service('logger')->nullOnInvalid(),
+ ])
+ ->tag('monolog.logger', ['channel' => 'debug'])
+
+ ->set('var_dumper.command.server_dump', ServerDumpCommand::class)
+ ->args([
+ service('var_dumper.dump_server'),
+ [
+ 'cli' => inline_service(CliDescriptor::class)->args([service('var_dumper.contextualized_cli_dumper.inner')]),
+ 'html' => inline_service(HtmlDescriptor::class)->args([service('var_dumper.html_dumper')]),
+ ],
+ ])
+ ->tag('console.command')
+
+ ->set('monolog.command.server_log', ServerLogCommand::class)
+ ;
+
+ if (class_exists(ConsoleFormatter::class) && interface_exists(FormatterInterface::class)) {
+ $container->services()->get('monolog.command.server_log')->tag('console.command');
+ }
+};
diff --git a/lib/symfony/debug-bundle/Resources/views/Profiler/dump.html.twig b/lib/symfony/debug-bundle/Resources/views/Profiler/dump.html.twig
new file mode 100644
index 0000000000..e98e524748
--- /dev/null
+++ b/lib/symfony/debug-bundle/Resources/views/Profiler/dump.html.twig
@@ -0,0 +1,83 @@
+{% extends '@WebProfiler/Profiler/layout.html.twig' %}
+
+{% block toolbar %}
+ {% if collector.dumpsCount %}
+ {% set icon %}
+ {{ source('@Debug/Profiler/icon.svg') }}
+ {{ collector.dumpsCount }}
+ {% endset %}
+
+ {% set text %}
+ {% for dump in collector.getDumps('html') %}
+
+
+ {% if dump.label is defined and '' != dump.label %}
+ {{ dump.label }} in
+ {% endif %}
+ {% if dump.file %}
+ {% set link = dump.file|file_link(dump.line) %}
+ {% if link %}
+ {{ dump.name }}
+ {% else %}
+ {{ dump.name }}
+ {% endif %}
+ {% else %}
+ {{ dump.name }}
+ {% endif %}
+
+ line {{ dump.line }}
+
+ {{ dump.data|raw }}
+
+ {% endfor %}
+{% endblock %}
diff --git a/lib/symfony/debug-bundle/Resources/views/Profiler/icon.svg b/lib/symfony/debug-bundle/Resources/views/Profiler/icon.svg
new file mode 100644
index 0000000000..9ba3a7d94d
--- /dev/null
+++ b/lib/symfony/debug-bundle/Resources/views/Profiler/icon.svg
@@ -0,0 +1,9 @@
+
diff --git a/lib/symfony/debug-bundle/composer.json b/lib/symfony/debug-bundle/composer.json
new file mode 100644
index 0000000000..1d058228fe
--- /dev/null
+++ b/lib/symfony/debug-bundle/composer.json
@@ -0,0 +1,41 @@
+{
+ "name": "symfony/debug-bundle",
+ "type": "symfony-bundle",
+ "description": "Provides a tight integration of the Symfony VarDumper component and the ServerLogCommand from MonologBridge into the Symfony full-stack framework",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=8.1",
+ "ext-xml": "*",
+ "symfony/dependency-injection": "^5.4|^6.0|^7.0",
+ "symfony/http-kernel": "^5.4|^6.0|^7.0",
+ "symfony/twig-bridge": "^5.4|^6.0|^7.0",
+ "symfony/var-dumper": "^5.4|^6.0|^7.0"
+ },
+ "require-dev": {
+ "symfony/config": "^5.4|^6.0|^7.0",
+ "symfony/web-profiler-bundle": "^5.4|^6.0|^7.0"
+ },
+ "conflict": {
+ "symfony/config": "<5.4",
+ "symfony/dependency-injection": "<5.4"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Bundle\\DebugBundle\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev"
+}
diff --git a/lib/symfony/dependency-injection/Dumper/PhpDumper.php b/lib/symfony/dependency-injection/Dumper/PhpDumper.php
index 1f9f4d31f8..2fe6a6f0e0 100644
--- a/lib/symfony/dependency-injection/Dumper/PhpDumper.php
+++ b/lib/symfony/dependency-injection/Dumper/PhpDumper.php
@@ -1295,16 +1295,9 @@ EOF;
}
}
- if (Container::class !== $this->baseClass) {
- $r = $this->container->getReflectionClass($this->baseClass, false);
- if (null !== $r
- && (null !== $constructor = $r->getConstructor())
- && 0 === $constructor->getNumberOfRequiredParameters()
- && Container::class !== $constructor->getDeclaringClass()->name
- ) {
- $code .= " parent::__construct();\n";
- $code .= " \$this->parameterBag = null;\n\n";
- }
+ if ($this->needsUnsetParameterBag()) {
+ $code .= " parent::__construct();\n";
+ $code .= " unset(\$this->parameterBag);\n\n";
}
if ($this->container->getParameterBag()->all()) {
@@ -1582,9 +1575,22 @@ EOF;
return $code ? \sprintf("\n \$this->privates['service_container'] = static function (\$container) {%s\n };\n", $code) : '';
}
+ private function needsUnsetParameterBag(): bool
+ {
+ if (Container::class === $this->baseClass) {
+ return false;
+ }
+ $r = $this->container->getReflectionClass($this->baseClass, false);
+
+ return null !== $r
+ && (null !== $constructor = $r->getConstructor())
+ && 0 === $constructor->getNumberOfRequiredParameters()
+ && Container::class !== $constructor->getDeclaringClass()->name;
+ }
+
private function addDefaultParametersMethod(): string
{
- if (!$this->container->getParameterBag()->all()) {
+ if (!$this->container->getParameterBag()->all() && !$this->needsUnsetParameterBag()) {
return '';
}
diff --git a/lib/symfony/dependency-injection/Loader/Configurator/AbstractConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/AbstractConfigurator.php
index d96eacb6dc..f1a1bc07b7 100644
--- a/lib/symfony/dependency-injection/Loader/Configurator/AbstractConfigurator.php
+++ b/lib/symfony/dependency-injection/Loader/Configurator/AbstractConfigurator.php
@@ -77,6 +77,18 @@ abstract class AbstractConfigurator
$value = (self::$valuePreProcessor)($value, $allowServices);
}
+ if ($value instanceof ParamConfigurator) {
+ return (string) $value;
+ }
+
+ if (\is_scalar($value ?? '') || $value instanceof \UnitEnum) {
+ return $value;
+ }
+
+ if (!$allowServices) {
+ throw new InvalidArgumentException(\sprintf('Cannot use values of type "%s" in service configuration files.', get_debug_type($value)));
+ }
+
if ($value instanceof ReferenceConfigurator) {
$reference = new Reference($value->id, $value->invalidBehavior);
@@ -90,29 +102,18 @@ abstract class AbstractConfigurator
return $def;
}
- if ($value instanceof ParamConfigurator) {
- return (string) $value;
- }
-
if ($value instanceof self) {
throw new InvalidArgumentException(\sprintf('"%s()" can be used only at the root of service configuration files.', $value::FACTORY));
}
switch (true) {
- case null === $value:
- case \is_scalar($value):
- case $value instanceof \UnitEnum:
- return $value;
-
case $value instanceof ArgumentInterface:
case $value instanceof Definition:
case $value instanceof Expression:
case $value instanceof Parameter:
case $value instanceof AbstractArgument:
case $value instanceof Reference:
- if ($allowServices) {
- return $value;
- }
+ return $value;
}
throw new InvalidArgumentException(\sprintf('Cannot use values of type "%s" in service configuration files.', get_debug_type($value)));
diff --git a/lib/symfony/dotenv/Dotenv.php b/lib/symfony/dotenv/Dotenv.php
index 2d726285f4..fa19650543 100644
--- a/lib/symfony/dotenv/Dotenv.php
+++ b/lib/symfony/dotenv/Dotenv.php
@@ -14,6 +14,7 @@ namespace Symfony\Component\Dotenv;
use Symfony\Component\Dotenv\Exception\FormatException;
use Symfony\Component\Dotenv\Exception\FormatExceptionContext;
use Symfony\Component\Dotenv\Exception\PathException;
+use Symfony\Component\Dotenv\Exception\VariableCircularReferenceException;
use Symfony\Component\Process\Exception\ExceptionInterface as ProcessException;
use Symfony\Component\Process\Process;
@@ -81,6 +82,7 @@ final class Dotenv
public function load(string $path, string ...$extraPaths): void
{
$this->doLoad(false, \func_get_args());
+ $this->resolveLoadedVars();
}
/**
@@ -100,33 +102,42 @@ final class Dotenv
*/
public function loadEnv(string $path, ?string $envKey = null, string $defaultEnv = 'dev', array $testEnvs = ['test'], bool $overrideExistingVars = false): void
{
- $k = $envKey ?? $this->envKey;
+ try {
+ $k = $envKey ?? $this->envKey;
- if (is_file($path) || !is_file($p = "$path.dist")) {
- $this->doLoad($overrideExistingVars, [$path]);
- } else {
- $this->doLoad($overrideExistingVars, [$p]);
- }
+ if (is_file($path) || !is_file($p = "$path.dist")) {
+ $this->doLoad($overrideExistingVars, [$path]);
+ } else {
+ $this->doLoad($overrideExistingVars, [$p]);
+ }
- if (null === $env = $_SERVER[$k] ?? $_ENV[$k] ?? null) {
- $this->populate([$k => $env = $defaultEnv], $overrideExistingVars);
- }
+ if (null === $env = $_SERVER[$k] ?? $_ENV[$k] ?? null) {
+ $this->populate([$k => $env = $defaultEnv], $overrideExistingVars);
+ } elseif (str_contains($env, '$') || str_contains($env, "\x00") || str_contains($env, '\\')) {
+ $env = $this->resolveEnvKey($env, $k);
+ }
- if (!\in_array($env, $testEnvs, true) && is_file($p = "$path.local")) {
- $this->doLoad($overrideExistingVars, [$p]);
- $env = $_SERVER[$k] ?? $_ENV[$k] ?? $env;
- }
+ if (!\in_array($env, $testEnvs, true) && is_file($p = "$path.local")) {
+ $this->doLoad($overrideExistingVars, [$p]);
+ $env = $_SERVER[$k] ?? $_ENV[$k] ?? $env;
+ if (str_contains($env, '$') || str_contains($env, "\x00") || str_contains($env, '\\')) {
+ $env = $this->resolveEnvKey($env, $k);
+ }
+ }
- if ('local' === $env) {
- return;
- }
+ if ('local' === $env) {
+ return;
+ }
- if (is_file($p = "$path.$env")) {
- $this->doLoad($overrideExistingVars, [$p]);
- }
+ if (is_file($p = "$path.$env")) {
+ $this->doLoad($overrideExistingVars, [$p]);
+ }
- if (is_file($p = "$path.$env.local")) {
- $this->doLoad($overrideExistingVars, [$p]);
+ if (is_file($p = "$path.$env.local")) {
+ $this->doLoad($overrideExistingVars, [$p]);
+ }
+ } finally {
+ $this->resolveLoadedVars();
}
}
@@ -168,6 +179,7 @@ final class Dotenv
public function overload(string $path, string ...$extraPaths): void
{
$this->doLoad(true, \func_get_args());
+ $this->resolveLoadedVars();
}
/**
@@ -236,6 +248,48 @@ final class Dotenv
$this->values = [];
$name = '';
+ $loadedVars = array_flip(explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? $_ENV['SYMFONY_DOTENV_VARS'] ?? ''));
+ unset($loadedVars['']);
+
+ $this->skipEmptyLines();
+
+ while ($this->cursor < $this->end) {
+ switch ($state) {
+ case self::STATE_VARNAME:
+ $name = $this->lexVarname();
+ $state = self::STATE_VALUE;
+ break;
+
+ case self::STATE_VALUE:
+ $this->values[$name] = $this->resolveValue($this->lexValue(), $loadedVars);
+ $state = self::STATE_VARNAME;
+ break;
+ }
+ }
+
+ if (self::STATE_VALUE === $state) {
+ $this->values[$name] = '';
+ }
+
+ try {
+ return $this->values;
+ } finally {
+ $this->values = [];
+ unset($this->path, $this->cursor, $this->lineno, $this->data, $this->end);
+ }
+ }
+
+ private function parseRaw(string $data, string $path = '.env'): array
+ {
+ $this->path = $path;
+ $this->data = str_replace(["\r\n", "\r"], "\n", $data);
+ $this->lineno = 1;
+ $this->cursor = 0;
+ $this->end = \strlen($this->data);
+ $state = self::STATE_VARNAME;
+ $this->values = [];
+ $name = '';
+
$this->skipEmptyLines();
while ($this->cursor < $this->end) {
@@ -260,10 +314,22 @@ final class Dotenv
return $this->values;
} finally {
$this->values = [];
- unset($this->path, $this->cursor, $this->lineno, $this->data, $this->end);
}
}
+ /**
+ * Resolves a raw value by expanding commands, variables, backslash escapes,
+ * and restoring literal $ markers.
+ */
+ private function resolveValue(string $value, array $loadedVars): string
+ {
+ $resolved = $this->resolveCommands($value, $loadedVars);
+ $resolved = $this->resolveVariables($resolved, $loadedVars);
+ $resolved = str_replace('\\\\', '\\', $resolved);
+
+ return str_replace("\x00", '$', $resolved);
+ }
+
private function lexVarname(): string
{
// var name + optional export
@@ -305,8 +371,6 @@ final class Dotenv
throw $this->createFormatException('Whitespace are not supported before the value');
}
- $loadedVars = array_flip(explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? $_ENV['SYMFONY_DOTENV_VARS'] ?? ''));
- unset($loadedVars['']);
$v = '';
do {
@@ -321,7 +385,10 @@ final class Dotenv
}
} while ("'" !== $this->data[$this->cursor + $len]);
- $v .= substr($this->data, 1 + $this->cursor, $len - 1);
+ // In single-quoted strings, $ is literal and \ has no special meaning.
+ // Double backslashes so they survive the unescape in resolveValue(),
+ // and mark $ as \x00 so it's not treated as a variable reference.
+ $v .= str_replace(['\\', '$'], ['\\\\', "\x00"], substr($this->data, 1 + $this->cursor, $len - 1));
$this->cursor += 1 + $len;
} elseif ('"' === $this->data[$this->cursor]) {
$value = '';
@@ -340,11 +407,8 @@ final class Dotenv
}
++$this->cursor;
$value = str_replace(['\\"', '\r', '\n'], ['"', "\r", "\n"], $value);
- $resolvedValue = $value;
- $resolvedValue = $this->resolveCommands($resolvedValue, $loadedVars);
- $resolvedValue = $this->resolveVariables($resolvedValue, $loadedVars);
- $resolvedValue = str_replace('\\\\', '\\', $resolvedValue);
- $v .= $resolvedValue;
+ // Mark escaped $ (\$) as \x00 so it's treated as literal
+ $v .= $this->protectEscapedDollars($value);
} else {
$value = '';
$prevChr = $this->data[$this->cursor - 1];
@@ -363,12 +427,9 @@ final class Dotenv
++$this->cursor;
}
$value = rtrim($value);
- $resolvedValue = $value;
- $resolvedValue = $this->resolveCommands($resolvedValue, $loadedVars);
- $resolvedValue = $this->resolveVariables($resolvedValue, $loadedVars);
- $resolvedValue = str_replace('\\\\', '\\', $resolvedValue);
+ $resolvedValue = $this->protectEscapedDollars($value);
- if ($resolvedValue === $value && preg_match('/\s+/', $value)) {
+ if ($resolvedValue === $value && preg_match('/\s+/', $value) && !str_contains($value, '$')) {
throw $this->createFormatException('A value containing spaces must be surrounded by quotes');
}
@@ -385,6 +446,26 @@ final class Dotenv
return $v;
}
+ /**
+ * Converts \$ (escaped dollar) to \x00 (literal marker), handling
+ * even/odd backslash counts correctly: \$ → \x00, \\$ → \\$ (unchanged).
+ */
+ private function protectEscapedDollars(string $value): string
+ {
+ if (!str_contains($value, '$')) {
+ return $value;
+ }
+
+ return preg_replace_callback('/\\\\+\$/', static function ($m) {
+ $bs = substr($m[0], 0, -1);
+ if (1 === \strlen($bs) % 2) {
+ return substr($bs, 0, -1)."\x00";
+ }
+
+ return $m[0];
+ }, $value);
+ }
+
private function lexNestedExpression(): string
{
++$this->cursor;
@@ -559,7 +640,148 @@ final class Dotenv
throw new FormatException('Loading files starting with a byte-order-mark (BOM) is not supported.', new FormatExceptionContext($data, $path, 1, 0));
}
- $this->populate($this->parse($data, $path), $overrideExistingVars);
+ if (str_contains($data, "\0")) {
+ throw new FormatException('Loading files containing NUL bytes is not supported.', new FormatExceptionContext($data, $path, 1, 0));
+ }
+
+ $this->populate($this->parseRaw($data, $path), $overrideExistingVars);
}
}
+
+ /**
+ * Eagerly resolves a raw env key value so that loadEnv() can determine
+ * which additional .env files to load before full deferred resolution.
+ */
+ private function resolveEnvKey(string $value, string $name): string
+ {
+ $loadedVars = array_flip(explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? $_ENV['SYMFONY_DOTENV_VARS'] ?? ''));
+ unset($loadedVars['']);
+
+ // Save and clear own value so self-referencing defaults work
+ $envBackup = $_ENV[$name] ?? null;
+ $serverBackup = $_SERVER[$name] ?? null;
+ unset($_ENV[$name], $_SERVER[$name]);
+ if ($this->usePutenv) {
+ $getenvBackup = (string) getenv($name);
+ putenv($name);
+ }
+
+ $this->values = [];
+ $this->path = '';
+ $this->data = '';
+ $this->lineno = 0;
+ $this->cursor = 0;
+ $this->end = 0;
+
+ $resolved = $this->resolveCommands($value, $loadedVars);
+ $resolved = $this->resolveVariables($resolved, $loadedVars);
+ $resolved = str_replace(["\x00", '\\\\'], ['$', '\\'], $resolved);
+
+ if (null !== $envBackup) {
+ $_ENV[$name] = $envBackup;
+ }
+ if (null !== $serverBackup) {
+ $_SERVER[$name] = $serverBackup;
+ }
+ if ($this->usePutenv) {
+ putenv("$name=$getenvBackup");
+ }
+
+ $this->values = [];
+
+ return $resolved;
+ }
+
+ private function resolveLoadedVars(): void
+ {
+ $loadedVars = array_flip(explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? $_ENV['SYMFONY_DOTENV_VARS'] ?? ''));
+ unset($loadedVars['']);
+
+ $this->values = [];
+ $this->path = '';
+ $this->data = '';
+ $this->lineno = 0;
+ $this->cursor = 0;
+ $this->end = 0;
+
+ // Detect variables that were originally defined as self-referencing
+ // (e.g. MY_VAR="${MY_VAR:-default}") so their own raw value is hidden
+ // during resolution, allowing the default to trigger correctly.
+ $selfReferencingVars = [];
+ foreach ($loadedVars as $name => $_) {
+ if ('SYMFONY_DOTENV_VARS' === $name) {
+ continue;
+ }
+ $value = $_ENV[$name] ?? '';
+ if (str_contains($value, '$') && preg_match('/\$\{?'.preg_quote($name, '/').'(?![A-Za-z0-9_])/', $value)) {
+ $selfReferencingVars[$name] = true;
+ }
+ }
+
+ for ($pass = 0; $pass < 5; ++$pass) {
+ $resolved = [];
+ foreach ($loadedVars as $name => $_) {
+ if ('SYMFONY_DOTENV_VARS' === $name) {
+ continue;
+ }
+ if (!str_contains($value = $_ENV[$name] ?? '', '$')) {
+ continue;
+ }
+
+ if (isset($selfReferencingVars[$name])) {
+ $envBackup = $_ENV[$name] ?? null;
+ $serverBackup = $_SERVER[$name] ?? null;
+ unset($_ENV[$name], $_SERVER[$name]);
+ if ($this->usePutenv) {
+ $getenvBackup = $this->usePutenv ? (string) getenv($name) : null;
+ putenv($name);
+ }
+ }
+
+ $resolvedValue = $this->resolveCommands($value, $loadedVars);
+ $resolvedValue = $this->resolveVariables($resolvedValue, $loadedVars);
+
+ if (isset($selfReferencingVars[$name])) {
+ if (null !== $envBackup) {
+ $_ENV[$name] = $envBackup;
+ }
+ if (null !== $serverBackup) {
+ $_SERVER[$name] = $serverBackup;
+ }
+ if ($this->usePutenv) {
+ putenv("$name=$getenvBackup");
+ }
+ }
+
+ if ($value !== $resolvedValue) {
+ $resolved[$name] = $resolvedValue;
+ }
+ }
+ if (!$resolved) {
+ break;
+ }
+ $this->populate($resolved, true);
+ }
+ if (5 === $pass && $resolved) {
+ throw new VariableCircularReferenceException('Too many levels of variable indirection in env vars: '.implode(', ', array_keys($resolved)).'.');
+ }
+
+ // Restore literal $ signs and unescape backslashes
+ $restored = [];
+ foreach ($loadedVars as $name => $_) {
+ if ('SYMFONY_DOTENV_VARS' === $name) {
+ continue;
+ }
+ $value = $_ENV[$name] ?? '';
+ if ($value !== $newValue = str_replace(["\x00", '\\\\'], ['$', '\\'], $value)) {
+ $restored[$name] = $newValue;
+ }
+ }
+ if ($restored) {
+ $this->populate($restored, true);
+ }
+
+ $this->values = [];
+ unset($this->path, $this->data, $this->lineno, $this->cursor, $this->end);
+ }
}
diff --git a/lib/symfony/dotenv/Exception/VariableCircularReferenceException.php b/lib/symfony/dotenv/Exception/VariableCircularReferenceException.php
new file mode 100644
index 0000000000..9ad659cb01
--- /dev/null
+++ b/lib/symfony/dotenv/Exception/VariableCircularReferenceException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Dotenv\Exception;
+
+/**
+ * Thrown when there are too many levels of variable indirection in env vars.
+ *
+ * @author Pascal CESCON
+ */
+final class VariableCircularReferenceException extends \LogicException implements ExceptionInterface
+{
+}
diff --git a/lib/symfony/error-handler/DebugClassLoader.php b/lib/symfony/error-handler/DebugClassLoader.php
index 078f8af56a..f0c120c3f7 100644
--- a/lib/symfony/error-handler/DebugClassLoader.php
+++ b/lib/symfony/error-handler/DebugClassLoader.php
@@ -127,6 +127,21 @@ class DebugClassLoader
private static array $internalMethods = [];
private static array $annotatedParameters = [];
private static array $darwinCache = ['/' => ['/', []]];
+ /**
+ * @var array>
+ *
+ * Maps an interface FQCN (or an abstract class accumulating entries from its interfaces) to the list of
+ * "@method" annotations declared on it. For interfaces, the entry is populated directly by parsing the
+ * annotations from the interface's docblock. For abstract classes, the information from all implemented
+ * interfaces is merged together, so that the check can later be applied to the first concrete subclass.
+ *
+ * Each entry is a tuple of:
+ * [0] string $interface - FQCN of the interface that carries the "@method" annotation
+ * [1] bool $static - whether the method is declared static
+ * [2] string $returnType - return type from the annotation, or '' if absent
+ * [3] string $name - method name plus its parameter signature, e.g. "foo($arg, int $n)"
+ * [4] string|null $description - description text (period-normalised), or null if absent
+ */
private static array $method = [];
private static array $returnTypes = [];
private static array $methodTraits = [];
@@ -398,6 +413,14 @@ class DebugClassLoader
if ($refl->isInterface() && isset($doc['method'])) {
foreach ($doc['method'] as $name => [$static, $returnType, $signature, $description]) {
+ if ($refl->hasMethod($static ? '__callStatic' : '__call')) {
+ // When the interface has "virtual" @method declarations but at the same time contains a __call/__callStatic magic method,
+ // do not trigger a deprecation notice. This is to address special use cases like in Predis' ClientInterface where the
+ // "@method" annotations never intend to actually add the method to the interface, but are used to document the "virtual"
+ // API provided by the interface through the technical implementation of magic calls. This might cause false negatives
+ // (missing notices) in the case that such interfaces are later amended with actual (real) methods.
+ continue;
+ }
self::$method[$class][] = [$class, $static, $returnType, $name.$signature, $description];
if ('' !== $returnType) {
@@ -420,6 +443,15 @@ class DebugClassLoader
}
}
+ // When the parent is a concrete class, we will trigger deprecation notices to make it aware that it needs
+ // to add the new methods announced with @method. The parent will have to provide all those methods.
+ // For child classes this means they will not need to deal with @method coming from any of the interfaces
+ // the parent implements.
+ // Put those interfaces that we can ignore into $parentInterfaces.
+ // The ternary makes use of the fact that abstract parent classes will accumulate the methods in self::$method,
+ // so !isset(self::$method[$parent]) indicates a concrete parent class.
+ $parentInterfaces = ($parent && !isset(self::$method[$parent])) ? class_implements($parent, false) : [];
+
// Detect if the parent is annotated
foreach ($parentAndOwnInterfaces + class_uses($class, false) as $use) {
if (!isset(self::$checkedClasses[$use])) {
@@ -435,13 +467,15 @@ class DebugClassLoader
$deprecations[] = \sprintf('The "%s" %s is considered internal%s It may change without further notice. You should not use it from "%s".', $use, class_exists($use, false) ? 'class' : (interface_exists($use, false) ? 'interface' : 'trait'), self::$internal[$use], $className);
}
if (isset(self::$method[$use])) {
- if ($refl->isAbstract()) {
+ if ($refl->isAbstract() || $refl->isInterface()) {
+ // Abstract classes and interfaces inherit @method from interfaces they
+ // implement directly or through inheritance.
if (isset(self::$method[$class])) {
self::$method[$class] = array_merge(self::$method[$class], self::$method[$use]);
} else {
self::$method[$class] = self::$method[$use];
}
- } elseif (!$refl->isInterface()) {
+ } else {
if (!strncmp($vendor, str_replace('_', '\\', $use), $vendorLen)
&& str_starts_with($className, 'Symfony\\')
&& (!class_exists(InstalledVersions::class)
@@ -450,14 +484,14 @@ class DebugClassLoader
// skip "same vendor" @method deprecations for Symfony\* classes unless symfony/symfony is being tested
continue;
}
- $hasCall = $refl->hasMethod('__call');
- $hasStaticCall = $refl->hasMethod('__callStatic');
foreach (self::$method[$use] as [$interface, $static, $returnType, $name, $description]) {
- if ($static ? $hasStaticCall : $hasCall) {
+ if (isset($parentInterfaces[$interface])) {
+ // The @method annotation comes from an interface that has already been implemented by a concrete parent class,
+ // so we can ignore it here.
continue;
}
$realName = substr($name, 0, strpos($name, '('));
- if (!$refl->hasMethod($realName) || !($methodRefl = $refl->getMethod($realName))->isPublic() || ($static && !$methodRefl->isStatic()) || (!$static && $methodRefl->isStatic())) {
+ if (!$refl->hasMethod($realName) || !($methodRefl = $refl->getMethod($realName))->isPublic() || ($static xor $methodRefl->isStatic())) {
$deprecations[] = \sprintf('Class "%s" should implement method "%s::%s%s"%s', $className, ($static ? 'static ' : '').$interface, $name, $returnType ? ': '.$returnType : '', null === $description ? '.' : ': '.$description);
}
}
diff --git a/lib/symfony/error-handler/Resources/bin/extract-tentative-return-types.php b/lib/symfony/error-handler/Resources/bin/extract-tentative-return-types.php
old mode 100644
new mode 100755
diff --git a/lib/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php b/lib/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php
index 2faea04638..12ebb1e674 100644
--- a/lib/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php
+++ b/lib/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php
@@ -40,6 +40,9 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
private EventDispatcherInterface $dispatcher;
private array $wrappedListeners = [];
private array $orphanedEvents = [];
+ private array $dispatchDepth = [];
+ private array $calledListenerInfos = [];
+ private array $calledOriginalListeners = [];
private ?RequestStack $requestStack;
private string $currentRequestHash = '';
@@ -155,20 +158,20 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
public function getCalledListeners(?Request $request = null): array
{
- if (null === $this->callStack) {
+ if (!$this->calledListenerInfos) {
return [];
}
$hash = $request ? spl_object_hash($request) : null;
$called = [];
- foreach ($this->callStack as $listener) {
- [$eventName, $requestHash] = $this->callStack->getInfo();
+
+ foreach ($this->calledListenerInfos as $requestHash => $infos) {
if (null === $hash || $hash === $requestHash) {
- $called[] = $listener->getInfo($eventName);
+ $called[] = $infos;
}
}
- return $called;
+ return $called ? array_merge(...$called) : [];
}
public function getNotCalledListeners(?Request $request = null): array
@@ -185,16 +188,14 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
$hash = $request ? spl_object_hash($request) : null;
$calledListeners = [];
- if (null !== $this->callStack) {
- foreach ($this->callStack as $calledListener) {
- [, $requestHash] = $this->callStack->getInfo();
-
- if (null === $hash || $hash === $requestHash) {
- $calledListeners[] = $calledListener->getWrappedListener();
- }
+ foreach ($this->calledOriginalListeners as $requestHash => $eventListeners) {
+ if (null === $hash || $hash === $requestHash) {
+ $calledListeners[] = array_merge(...array_values($eventListeners));
}
}
+ $calledListeners = $calledListeners ? array_merge(...$calledListeners) : [];
+
$notCalled = [];
foreach ($allListeners as $eventName => $listeners) {
@@ -234,6 +235,9 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
$this->callStack = null;
$this->orphanedEvents = [];
$this->currentRequestHash = '';
+ $this->dispatchDepth = [];
+ $this->calledListenerInfos = [];
+ $this->calledOriginalListeners = [];
}
/**
@@ -267,6 +271,8 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
private function preProcess(string $eventName): void
{
+ $this->dispatchDepth[$eventName] = ($this->dispatchDepth[$eventName] ?? 0) + 1;
+
if (!$this->dispatcher->hasListeners($eventName)) {
$this->orphanedEvents[$this->currentRequestHash][] = $eventName;
@@ -285,6 +291,8 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
private function postProcess(string $eventName): void
{
+ --$this->dispatchDepth[$eventName];
+
unset($this->wrappedListeners[$eventName]);
$skipped = false;
foreach ($this->dispatcher->getListeners($eventName) as $listener) {
@@ -302,10 +310,16 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
if ($listener->wasCalled()) {
$this->logger?->debug('Notified event "{event}" to listener "{listener}".', $context);
- } else {
- unset($this->callStack[$listener]);
+
+ $original = $listener->getWrappedListener();
+ if (!\in_array($original, $this->calledOriginalListeners[$this->currentRequestHash][$eventName] ?? [], true)) {
+ $this->calledOriginalListeners[$this->currentRequestHash][$eventName][] = $original;
+ $this->calledListenerInfos[$this->currentRequestHash][] = $listener->getInfo($eventName);
+ }
}
+ unset($this->callStack[$listener]);
+
if (null !== $this->logger && $skipped) {
$this->logger->debug('Listener "{listener}" was not called for event "{event}".', $context);
}
@@ -316,6 +330,28 @@ class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterfa
$skipped = true;
}
}
+
+ if (0 < $this->dispatchDepth[$eventName]) {
+ return;
+ }
+
+ // Clean up stale callStack entries left by nested same-event dispatches
+ $stale = [];
+ foreach ($this->callStack as $listener) {
+ if ($this->callStack->getInfo()[0] === $eventName) {
+ $stale[] = $listener;
+ }
+ }
+ foreach ($stale as $listener) {
+ if ($listener->wasCalled()) {
+ $original = $listener->getWrappedListener();
+ if (!\in_array($original, $this->calledOriginalListeners[$this->currentRequestHash][$eventName] ?? [], true)) {
+ $this->calledOriginalListeners[$this->currentRequestHash][$eventName][] = $original;
+ $this->calledListenerInfos[$this->currentRequestHash][] = $listener->getInfo($eventName);
+ }
+ }
+ unset($this->callStack[$listener]);
+ }
}
private function sortNotCalledListeners(array $a, array $b): int
diff --git a/lib/symfony/form/Extension/Validator/ValidatorExtension.php b/lib/symfony/form/Extension/Validator/ValidatorExtension.php
index bfad8074fc..95effe760d 100644
--- a/lib/symfony/form/Extension/Validator/ValidatorExtension.php
+++ b/lib/symfony/form/Extension/Validator/ValidatorExtension.php
@@ -39,17 +39,23 @@ class ValidatorExtension extends AbstractExtension
/** @var ClassMetadata $metadata */
$metadata = $validator->getMetadataFor(\Symfony\Component\Form\Form::class);
+ $this->validator = $validator;
+ $this->formRenderer = $formRenderer;
+ $this->translator = $translator;
+
// Register the form constraints in the validator programmatically.
// This functionality is required when using the Form component without
// the DIC, where the XML file is loaded automatically. Thus the following
// code must be kept synchronized with validation.xml
+ foreach ($metadata->getConstraints() as $constraint) {
+ if ($constraint instanceof Form) {
+ return;
+ }
+ }
+
$metadata->addConstraint(new Form());
$metadata->addConstraint(new Traverse(false));
-
- $this->validator = $validator;
- $this->formRenderer = $formRenderer;
- $this->translator = $translator;
}
public function loadTypeGuesser(): ?FormTypeGuesserInterface
diff --git a/lib/symfony/framework-bundle/DependencyInjection/FrameworkExtension.php b/lib/symfony/framework-bundle/DependencyInjection/FrameworkExtension.php
index 20810098a8..ec962ff981 100644
--- a/lib/symfony/framework-bundle/DependencyInjection/FrameworkExtension.php
+++ b/lib/symfony/framework-bundle/DependencyInjection/FrameworkExtension.php
@@ -1233,8 +1233,7 @@ class FrameworkExtension extends Extension
$container->setParameter('request_listener.https_port', $config['https_port']);
if (null !== $config['default_uri']) {
- $container->getDefinition('router.request_context')
- ->replaceArgument(0, $config['default_uri']);
+ $container->setParameter('router.request_context.base_url', $config['default_uri']);
}
if ($this->isInitializedConfigEnabled('annotations') && (new \ReflectionClass(AttributeClassLoader::class))->hasProperty('reader')) {
@@ -1265,6 +1264,7 @@ class FrameworkExtension extends Extension
}
$container->setParameter('session.storage.options', $options);
+ $container->setParameter('session.metadata.cookie_lifetime', $options['cookie_lifetime'] ?? null);
// session handler (the internal callback registered with PHP session management)
if (null === $config['handler_id']) {
diff --git a/lib/symfony/framework-bundle/Resources/config/session.php b/lib/symfony/framework-bundle/Resources/config/session.php
index 2e481359aa..649c5cd716 100644
--- a/lib/symfony/framework-bundle/Resources/config/session.php
+++ b/lib/symfony/framework-bundle/Resources/config/session.php
@@ -43,6 +43,7 @@ return static function (ContainerConfigurator $container) {
->args([
param('session.metadata.storage_key'),
param('session.metadata.update_threshold'),
+ param('session.metadata.cookie_lifetime'),
]),
false,
])
@@ -53,6 +54,7 @@ return static function (ContainerConfigurator $container) {
->args([
param('session.metadata.storage_key'),
param('session.metadata.update_threshold'),
+ param('session.metadata.cookie_lifetime'),
]),
false,
])
@@ -64,6 +66,7 @@ return static function (ContainerConfigurator $container) {
->args([
param('session.metadata.storage_key'),
param('session.metadata.update_threshold'),
+ param('session.metadata.cookie_lifetime'),
]),
])
diff --git a/lib/symfony/framework-bundle/Test/KernelTestCase.php b/lib/symfony/framework-bundle/Test/KernelTestCase.php
index 5a6384b5e0..2b49083fa3 100644
--- a/lib/symfony/framework-bundle/Test/KernelTestCase.php
+++ b/lib/symfony/framework-bundle/Test/KernelTestCase.php
@@ -12,10 +12,12 @@
namespace Symfony\Bundle\FrameworkBundle\Test;
use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Resource\SelfCheckingResourceChecker;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\Filesystem\Filesystem;
+use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Contracts\Service\ResetInterface;
@@ -38,6 +40,8 @@ abstract class KernelTestCase extends TestCase
protected static $booted = false;
+ private static bool $kernelHasBeenRebooted = false;
+
protected function tearDown(): void
{
static::ensureKernelShutdown();
@@ -88,6 +92,7 @@ abstract class KernelTestCase extends TestCase
// reboot a fresh one.
if ($kernel->getContainer()->initialized('cache_warmer')) {
static::ensureKernelShutdown();
+ self::$kernelHasBeenRebooted = true;
$kernel = static::createKernel($options);
$kernel->boot();
@@ -161,6 +166,20 @@ abstract class KernelTestCase extends TestCase
static::$kernel->shutdown();
static::$booted = false;
+ if (self::$kernelHasBeenRebooted) {
+ self::$kernelHasBeenRebooted = false;
+ try {
+ (new \ReflectionProperty(Kernel::class, 'freshCache'))->setValue(null, []);
+ } catch (\ReflectionException) {
+ // ignore if the property doesn't exist
+ }
+ try {
+ (new \ReflectionProperty(SelfCheckingResourceChecker::class, 'cache'))->setValue(null, []);
+ } catch (\ReflectionException) {
+ // ignore if the property doesn't exist
+ }
+ }
+
if ($container instanceof ResetInterface) {
$container->reset();
}
diff --git a/lib/symfony/http-foundation/Session/Storage/MetadataBag.php b/lib/symfony/http-foundation/Session/Storage/MetadataBag.php
index 5bb4cfbc7b..5192d14c1e 100644
--- a/lib/symfony/http-foundation/Session/Storage/MetadataBag.php
+++ b/lib/symfony/http-foundation/Session/Storage/MetadataBag.php
@@ -41,14 +41,18 @@ class MetadataBag implements SessionBagInterface
private int $updateThreshold;
+ private ?int $cookieLifetime;
+
/**
- * @param string $storageKey The key used to store bag in the session
- * @param int $updateThreshold The time to wait between two UPDATED updates
+ * @param string $storageKey The key used to store bag in the session
+ * @param int $updateThreshold The time to wait between two UPDATED updates
+ * @param int|null $cookieLifetime The configured cookie lifetime; null to read from php.ini
*/
- public function __construct(string $storageKey = '_sf2_meta', int $updateThreshold = 0)
+ public function __construct(string $storageKey = '_sf2_meta', int $updateThreshold = 0, ?int $cookieLifetime = null)
{
$this->storageKey = $storageKey;
$this->updateThreshold = $updateThreshold;
+ $this->cookieLifetime = $cookieLifetime;
}
/**
@@ -143,6 +147,6 @@ class MetadataBag implements SessionBagInterface
{
$timeStamp = time();
$this->meta[self::CREATED] = $this->meta[self::UPDATED] = $this->lastUsed = $timeStamp;
- $this->meta[self::LIFETIME] = $lifetime ?? (int) \ini_get('session.cookie_lifetime');
+ $this->meta[self::LIFETIME] = $lifetime ?? $this->cookieLifetime ?? (int) \ini_get('session.cookie_lifetime');
}
}
diff --git a/lib/symfony/http-kernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php b/lib/symfony/http-kernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php
index e3a092e7b5..4931369208 100644
--- a/lib/symfony/http-kernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php
+++ b/lib/symfony/http-kernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php
@@ -105,7 +105,7 @@ class RequestPayloadValueResolver implements ValueResolverInterface, EventSubscr
try {
$payload = $this->$payloadMapper($request, $type, $argument);
} catch (PartialDenormalizationException $e) {
- $trans = $this->translator ? $this->translator->trans(...) : fn ($m, $p) => strtr($m, $p);
+ $trans = $this->translator ? $this->translator->trans(...) : static fn ($m, $p) => strtr($m, $p);
foreach ($e->getErrors() as $error) {
$parameters = [];
$template = 'This value was of an unexpected type.';
@@ -187,7 +187,7 @@ class RequestPayloadValueResolver implements ValueResolverInterface, EventSubscr
}
if (\is_array($data)) {
- return $this->serializer->denormalize($data, $type, 'csv', $attribute->serializationContext + self::CONTEXT_DENORMALIZE);
+ return $this->serializer->denormalize($data, $type, self::hasNonStringScalar($data) ? $format : 'csv', $attribute->serializationContext + self::CONTEXT_DENORMALIZE);
}
if ('form' === $format) {
@@ -202,4 +202,21 @@ class RequestPayloadValueResolver implements ValueResolverInterface, EventSubscr
throw new HttpException(Response::HTTP_BAD_REQUEST, \sprintf('Request payload contains invalid "%s" data.', $format), $e);
}
}
+
+ private static function hasNonStringScalar(array $data): bool
+ {
+ $stack = [$data];
+
+ while ($stack) {
+ foreach (array_pop($stack) as $v) {
+ if (\is_array($v)) {
+ $stack[] = $v;
+ } elseif (!\is_string($v)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
}
diff --git a/lib/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php b/lib/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php
index 5be3f650ea..cc58079eb8 100644
--- a/lib/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php
+++ b/lib/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php
@@ -171,6 +171,7 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface
}
if ($autowireAttributes) {
+ $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
$attribute = $autowireAttributes[0]->newInstance();
$value = $parameterBag->resolveValue($attribute->value);
diff --git a/lib/symfony/http-kernel/EventListener/LocaleListener.php b/lib/symfony/http-kernel/EventListener/LocaleListener.php
index a3aa30f09a..875551b5f5 100644
--- a/lib/symfony/http-kernel/EventListener/LocaleListener.php
+++ b/lib/symfony/http-kernel/EventListener/LocaleListener.php
@@ -47,6 +47,7 @@ class LocaleListener implements EventSubscriberInterface
public function setDefaultLocale(KernelEvent $event): void
{
$event->getRequest()->setDefaultLocale($this->defaultLocale);
+ $this->setRouterLocale($this->defaultLocale);
}
public function onKernelRequest(RequestEvent $event): void
@@ -54,14 +55,12 @@ class LocaleListener implements EventSubscriberInterface
$request = $event->getRequest();
$this->setLocale($request);
- $this->setRouterContext($request);
+ $this->setRouterLocale($request->getLocale());
}
public function onKernelFinishRequest(FinishRequestEvent $event): void
{
- if (null !== $parentRequest = $this->requestStack->getParentRequest()) {
- $this->setRouterContext($parentRequest);
- }
+ $this->setRouterLocale($this->requestStack->getParentRequest()?->getLocale() ?? $this->defaultLocale);
}
private function setLocale(Request $request): void
@@ -76,9 +75,9 @@ class LocaleListener implements EventSubscriberInterface
}
}
- private function setRouterContext(Request $request): void
+ private function setRouterLocale(string $locale): void
{
- $this->router?->getContext()->setParameter('_locale', $request->getLocale());
+ $this->router?->getContext()->setParameter('_locale', $locale);
}
public static function getSubscribedEvents(): array
diff --git a/lib/symfony/http-kernel/Kernel.php b/lib/symfony/http-kernel/Kernel.php
index cb148d4d24..2c279f1a8f 100644
--- a/lib/symfony/http-kernel/Kernel.php
+++ b/lib/symfony/http-kernel/Kernel.php
@@ -77,11 +77,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
*/
private static array $freshCache = [];
- public const VERSION = '6.4.34';
- public const VERSION_ID = 60434;
+ public const VERSION = '6.4.36';
+ public const VERSION_ID = 60436;
public const MAJOR_VERSION = 6;
public const MINOR_VERSION = 4;
- public const RELEASE_VERSION = 34;
+ public const RELEASE_VERSION = 36;
public const EXTRA_VERSION = '';
public const END_OF_MAINTENANCE = '11/2026';
diff --git a/lib/symfony/mime/FileBinaryMimeTypeGuesser.php b/lib/symfony/mime/FileBinaryMimeTypeGuesser.php
index 465f061a08..f290f9a3ad 100644
--- a/lib/symfony/mime/FileBinaryMimeTypeGuesser.php
+++ b/lib/symfony/mime/FileBinaryMimeTypeGuesser.php
@@ -44,15 +44,11 @@ class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface
return $supported;
}
- if ('\\' === \DIRECTORY_SEPARATOR || !\function_exists('passthru') || !\function_exists('escapeshellarg')) {
+ if ('\\' === \DIRECTORY_SEPARATOR || !\function_exists('shell_exec') || !\function_exists('escapeshellarg')) {
return $supported = false;
}
- ob_start();
- passthru('command -v file', $exitStatus);
- $binPath = trim(ob_get_clean());
-
- return $supported = 0 === $exitStatus && '' !== $binPath;
+ return $supported = '' !== trim(shell_exec('command -v file') ?: '');
}
public function guessMimeType(string $path): ?string
@@ -65,17 +61,8 @@ class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface
throw new LogicException(\sprintf('The "%s" guesser is not supported.', __CLASS__));
}
- ob_start();
-
// need to use --mime instead of -i. see #6641
- passthru(\sprintf($this->cmd, escapeshellarg((str_starts_with($path, '-') ? './' : '').$path)), $return);
- if ($return > 0) {
- ob_end_clean();
-
- return null;
- }
-
- $type = trim(ob_get_clean());
+ $type = trim(shell_exec(\sprintf($this->cmd, escapeshellarg((str_starts_with($path, '-') ? './' : '').$path))) ?: '');
if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-\+\.]+)#i', $type, $match)) {
// it's not a type, but an error message
diff --git a/lib/symfony/mime/Part/DataPart.php b/lib/symfony/mime/Part/DataPart.php
index f550f31fcd..e3d0fcfae2 100644
--- a/lib/symfony/mime/Part/DataPart.php
+++ b/lib/symfony/mime/Part/DataPart.php
@@ -141,7 +141,7 @@ class DataPart extends TextPart
}
$this->_headers = $this->getHeaders();
- return ['_headers', '_parent', 'filename', 'mediaType'];
+ return ['_headers', '_parent', 'filename', 'mediaType', 'cid'];
}
/**
diff --git a/lib/symfony/polyfill-ctype/Ctype.php b/lib/symfony/polyfill-ctype/Ctype.php
index ba75a2c95f..e26cc02211 100644
--- a/lib/symfony/polyfill-ctype/Ctype.php
+++ b/lib/symfony/polyfill-ctype/Ctype.php
@@ -211,6 +211,10 @@ final class Ctype
*/
private static function convert_int_to_char_for_ctype($int, $function)
{
+ if (\PHP_VERSION_ID >= 80100 && !\is_string($int)) {
+ @trigger_error($function.'(): Argument of type '.get_debug_type($int).' will be interpreted as string in the future', \E_USER_DEPRECATED);
+ }
+
if (!\is_int($int)) {
return $int;
}
@@ -219,10 +223,6 @@ final class Ctype
return (string) $int;
}
- if (\PHP_VERSION_ID >= 80100) {
- @trigger_error($function.'(): Argument of type int will be interpreted as string in the future', \E_USER_DEPRECATED);
- }
-
if ($int < 0) {
$int += 256;
}
diff --git a/lib/symfony/polyfill-intl-grapheme/Grapheme.php b/lib/symfony/polyfill-intl-grapheme/Grapheme.php
index f9e9e57413..51efbf163c 100644
--- a/lib/symfony/polyfill-intl-grapheme/Grapheme.php
+++ b/lib/symfony/polyfill-intl-grapheme/Grapheme.php
@@ -27,6 +27,7 @@ namespace Symfony\Polyfill\Intl\Grapheme;
* - grapheme_strstr - Returns part of haystack string from the first occurrence of needle to the end of haystack
* - grapheme_substr - Return part of a string
* - grapheme_str_split - Splits a string into an array of individual or chunks of graphemes
+ * - grapheme_levenshtein - Calculate the grapheme-unit Levenshtein distance between two strings
*
* @author Nicolas Grekas
*
@@ -51,7 +52,7 @@ final class Grapheme
if (!\is_scalar($s)) {
$hasError = false;
- set_error_handler(function () use (&$hasError) { $hasError = true; });
+ set_error_handler(static function () use (&$hasError) { $hasError = true; });
$next = substr($s, $start);
restore_error_handler();
if ($hasError) {
@@ -223,6 +224,54 @@ final class Grapheme
return $chunks;
}
+ public static function grapheme_levenshtein($s1, $s2, $insertion_cost = 1, $replacement_cost = 1, $deletion_cost = 1)
+ {
+ if (!preg_match('//u', $s1) || !preg_match('//u', $s2)) {
+ return false;
+ }
+
+ if (0 > $insertion_cost || 0 > $replacement_cost || 0 > $deletion_cost) {
+ if (80000 > \PHP_VERSION_ID) {
+ return false;
+ }
+
+ throw new \ValueError('grapheme_levenshtein(): Argument #3 ($insertion_cost), #4 ($replacement_cost), and #5 ($deletion_cost) must be greater than or equal to 0');
+ }
+
+ preg_match_all('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', $s1, $s1);
+ preg_match_all('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', $s2, $s2);
+
+ $s1 = $s1[0];
+ $s2 = $s2[0];
+ $l1 = \count($s1);
+ $l2 = \count($s2);
+
+ if (0 === $l1) {
+ return $l2 * $insertion_cost;
+ }
+ if (0 === $l2) {
+ return $l1 * $deletion_cost;
+ }
+
+ $dp = array_fill(0, $l1 + 1, array_fill(0, $l2 + 1, 0));
+
+ for ($i = 1; $i <= $l1; ++$i) {
+ $dp[$i][0] = $dp[$i - 1][0] + $deletion_cost;
+ }
+ for ($j = 1; $j <= $l2; ++$j) {
+ $dp[0][$j] = $dp[0][$j - 1] + $insertion_cost;
+ }
+
+ for ($i = 1; $i <= $l1; ++$i) {
+ for ($j = 1; $j <= $l2; ++$j) {
+ $cost = ($s1[$i - 1] === $s2[$j - 1]) ? 0 : $replacement_cost;
+ $dp[$i][$j] = min($dp[$i - 1][$j] + $deletion_cost, $dp[$i][$j - 1] + $insertion_cost, $dp[$i - 1][$j - 1] + $cost);
+ }
+ }
+
+ return $dp[$l1][$l2];
+ }
+
private static function grapheme_position($s, $needle, $offset, $mode)
{
$needle = (string) $needle;
diff --git a/lib/symfony/polyfill-intl-grapheme/bootstrap.php b/lib/symfony/polyfill-intl-grapheme/bootstrap.php
index 374dbd3a7e..8926c3b0f4 100644
--- a/lib/symfony/polyfill-intl-grapheme/bootstrap.php
+++ b/lib/symfony/polyfill-intl-grapheme/bootstrap.php
@@ -53,5 +53,8 @@ if (!function_exists('grapheme_substr')) {
function grapheme_substr($string, $offset, $length = null) { return p\Grapheme::grapheme_substr($string, $offset, $length); }
}
if (!function_exists('grapheme_str_split')) {
- function grapheme_str_split($string, $length = 1) { return p\Grapheme::grapheme_str_split($string, $length); }
+ function grapheme_str_split(string $string, int $length = 1) { return p\Grapheme::grapheme_str_split($string, $length); }
+}
+if (!function_exists('grapheme_levenshtein')) {
+ function grapheme_levenshtein(string $string1, string $string2, int $insertion_cost = 1, int $replacement_cost = 1, int $deletion_cost = 1, string $locale = '') { return p\Php85::grapheme_levenshtein($string1, $string2, $insertion_cost, $replacement_cost, $deletion_cost); }
}
diff --git a/lib/symfony/polyfill-intl-grapheme/bootstrap80.php b/lib/symfony/polyfill-intl-grapheme/bootstrap80.php
index d711755307..acf046215e 100644
--- a/lib/symfony/polyfill-intl-grapheme/bootstrap80.php
+++ b/lib/symfony/polyfill-intl-grapheme/bootstrap80.php
@@ -14,6 +14,9 @@ use Symfony\Polyfill\Intl\Grapheme as p;
if (!function_exists('grapheme_str_split')) {
function grapheme_str_split(string $string, int $length = 1): array|false { return p\Grapheme::grapheme_str_split($string, $length); }
}
+if (!function_exists('grapheme_levenshtein')) {
+ function grapheme_levenshtein(string $string1, string $string2, int $insertion_cost = 1, int $replacement_cost = 1, int $deletion_cost = 1, string $locale = ''): int|false { return p\Grapheme::grapheme_levenshtein($string1, $string2, $insertion_cost, $replacement_cost, $deletion_cost); }
+}
if (extension_loaded('intl')) {
return;
diff --git a/lib/symfony/polyfill-intl-icu/Collator.php b/lib/symfony/polyfill-intl-icu/Collator.php
index 2f952cdf53..5055bc4b08 100644
--- a/lib/symfony/polyfill-intl-icu/Collator.php
+++ b/lib/symfony/polyfill-intl-icu/Collator.php
@@ -118,17 +118,15 @@ abstract class Collator
}
/**
- * Not supported. Compare two Unicode strings.
+ * Compare two Unicode strings.
*
* @return int|false
*
* @see https://php.net/collator.compare
- *
- * @throws MethodNotImplementedException
*/
public function compare(string $string1, string $string2)
{
- throw new MethodNotImplementedException(__METHOD__);
+ return strcasecmp($string1, $string2) ?: $string2 <=> $string1;
}
/**
diff --git a/lib/symfony/polyfill-intl-icu/DateFormat/FullTransformer.php b/lib/symfony/polyfill-intl-icu/DateFormat/FullTransformer.php
index 02d071da57..ee871182a7 100644
--- a/lib/symfony/polyfill-intl-icu/DateFormat/FullTransformer.php
+++ b/lib/symfony/polyfill-intl-icu/DateFormat/FullTransformer.php
@@ -104,7 +104,7 @@ class FullTransformer
// handle unimplemented characters
if (false !== strpos($this->notImplementedChars, $dateChars[0])) {
- throw new NotImplementedException(sprintf('Unimplemented date character "%s" in format "%s".', $dateChars[0], $this->pattern));
+ throw new NotImplementedException(\sprintf('Unimplemented date character "%s" in format "%s".', $dateChars[0], $this->pattern));
}
return '';
@@ -212,7 +212,7 @@ class FullTransformer
{
$specialCharsArray = str_split($specialChars);
- $specialCharsMatch = implode('|', array_map(function ($char) {
+ $specialCharsMatch = implode('|', array_map(static function ($char) {
return $char.'+';
}, $specialCharsArray));
diff --git a/lib/symfony/polyfill-intl-icu/DateFormat/MonthTransformer.php b/lib/symfony/polyfill-intl-icu/DateFormat/MonthTransformer.php
index 6712ed2827..c54025b77a 100644
--- a/lib/symfony/polyfill-intl-icu/DateFormat/MonthTransformer.php
+++ b/lib/symfony/polyfill-intl-icu/DateFormat/MonthTransformer.php
@@ -53,7 +53,7 @@ class MonthTransformer extends Transformer
public function __construct()
{
if (0 === \count(self::$shortMonths)) {
- self::$shortMonths = array_map(function ($month) {
+ self::$shortMonths = array_map(static function ($month) {
return substr($month, 0, 3);
}, self::$months);
diff --git a/lib/symfony/polyfill-intl-icu/DateFormat/QuarterTransformer.php b/lib/symfony/polyfill-intl-icu/DateFormat/QuarterTransformer.php
index a549deeda2..303e66d61d 100644
--- a/lib/symfony/polyfill-intl-icu/DateFormat/QuarterTransformer.php
+++ b/lib/symfony/polyfill-intl-icu/DateFormat/QuarterTransformer.php
@@ -39,9 +39,9 @@ class QuarterTransformer extends Transformer
$map = [1 => '1st quarter', 2 => '2nd quarter', 3 => '3rd quarter', 4 => '4th quarter'];
return $map[$quarter];
- } else {
- return $quarter;
}
+
+ return $quarter;
}
}
diff --git a/lib/symfony/polyfill-intl-icu/DateFormat/TimezoneTransformer.php b/lib/symfony/polyfill-intl-icu/DateFormat/TimezoneTransformer.php
index bab7a96f8b..d9fb64d03f 100644
--- a/lib/symfony/polyfill-intl-icu/DateFormat/TimezoneTransformer.php
+++ b/lib/symfony/polyfill-intl-icu/DateFormat/TimezoneTransformer.php
@@ -55,7 +55,7 @@ class TimezoneTransformer extends Transformer
return $dateTime->format('\G\M\TP');
}
- return sprintf('GMT%s%d', $offset >= 0 ? '+' : '', $offset / 100);
+ return \sprintf('GMT%s%d', $offset >= 0 ? '+' : '', $offset / 100);
}
public function getReverseMatchingRegExp(int $length): string
@@ -97,12 +97,12 @@ class TimezoneTransformer extends Transformer
$signal = '-' === $matches['signal'] ? '+' : '-';
if (0 < $minutes) {
- throw new NotImplementedException(sprintf('It is not possible to use a GMT time zone with minutes offset different than zero (0). GMT time zone tried: "%s".', $formattedTimeZone));
+ throw new NotImplementedException(\sprintf('It is not possible to use a GMT time zone with minutes offset different than zero (0). GMT time zone tried: "%s".', $formattedTimeZone));
}
return 'Etc/GMT'.(0 !== $hours ? $signal.$hours : '');
}
- throw new \InvalidArgumentException(sprintf('The GMT time zone "%s" does not match with the supported formats GMT[+-]HH:MM or GMT[+-]HHMM.', $formattedTimeZone));
+ throw new \InvalidArgumentException(\sprintf('The GMT time zone "%s" does not match with the supported formats GMT[+-]HH:MM or GMT[+-]HHMM.', $formattedTimeZone));
}
}
diff --git a/lib/symfony/polyfill-intl-icu/Exception/MethodArgumentNotImplementedException.php b/lib/symfony/polyfill-intl-icu/Exception/MethodArgumentNotImplementedException.php
index db120a340f..22cd5847af 100644
--- a/lib/symfony/polyfill-intl-icu/Exception/MethodArgumentNotImplementedException.php
+++ b/lib/symfony/polyfill-intl-icu/Exception/MethodArgumentNotImplementedException.php
@@ -22,7 +22,7 @@ class MethodArgumentNotImplementedException extends NotImplementedException
*/
public function __construct(string $methodName, string $argName)
{
- $message = sprintf('The %s() method\'s argument $%s behavior is not implemented.', $methodName, $argName);
+ $message = \sprintf('The %s() method\'s argument $%s behavior is not implemented.', $methodName, $argName);
parent::__construct($message);
}
}
diff --git a/lib/symfony/polyfill-intl-icu/Exception/MethodArgumentValueNotImplementedException.php b/lib/symfony/polyfill-intl-icu/Exception/MethodArgumentValueNotImplementedException.php
index bd9204234e..d1f0876b85 100644
--- a/lib/symfony/polyfill-intl-icu/Exception/MethodArgumentValueNotImplementedException.php
+++ b/lib/symfony/polyfill-intl-icu/Exception/MethodArgumentValueNotImplementedException.php
@@ -24,7 +24,7 @@ class MethodArgumentValueNotImplementedException extends NotImplementedException
*/
public function __construct(string $methodName, string $argName, $argValue, string $additionalMessage = '')
{
- $message = sprintf(
+ $message = \sprintf(
'The %s() method\'s argument $%s value %s behavior is not implemented.%s',
$methodName,
$argName,
diff --git a/lib/symfony/polyfill-intl-icu/Exception/MethodNotImplementedException.php b/lib/symfony/polyfill-intl-icu/Exception/MethodNotImplementedException.php
index 9e1a43985e..494bf7a0f7 100644
--- a/lib/symfony/polyfill-intl-icu/Exception/MethodNotImplementedException.php
+++ b/lib/symfony/polyfill-intl-icu/Exception/MethodNotImplementedException.php
@@ -21,6 +21,6 @@ class MethodNotImplementedException extends NotImplementedException
*/
public function __construct(string $methodName)
{
- parent::__construct(sprintf('The %s() is not implemented.', $methodName));
+ parent::__construct(\sprintf('The %s() is not implemented.', $methodName));
}
}
diff --git a/lib/symfony/polyfill-intl-icu/Icu.php b/lib/symfony/polyfill-intl-icu/Icu.php
index b9590f43d8..702d1b83d7 100644
--- a/lib/symfony/polyfill-intl-icu/Icu.php
+++ b/lib/symfony/polyfill-intl-icu/Icu.php
@@ -108,10 +108,10 @@ abstract class Icu
public static function setError(int $code, string $message = '')
{
if (!isset(self::$errorCodes[$code])) {
- throw new \InvalidArgumentException(sprintf('No such error code: "%s".', $code));
+ throw new \InvalidArgumentException(\sprintf('No such error code: "%s".', $code));
}
- self::$errorMessage = $message ? sprintf('%s: %s', $message, self::$errorCodes[$code]) : self::$errorCodes[$code];
+ self::$errorMessage = $message ? \sprintf('%s: %s', $message, self::$errorCodes[$code]) : self::$errorCodes[$code];
self::$errorCode = $code;
}
}
diff --git a/lib/symfony/polyfill-intl-icu/IntlDateFormatter.php b/lib/symfony/polyfill-intl-icu/IntlDateFormatter.php
index b2674f906e..750f69b008 100644
--- a/lib/symfony/polyfill-intl-icu/IntlDateFormatter.php
+++ b/lib/symfony/polyfill-intl-icu/IntlDateFormatter.php
@@ -225,7 +225,7 @@ abstract class IntlDateFormatter
// behave like the intl extension
$argumentError = null;
if (!\is_int($datetime) && !$datetime instanceof \DateTimeInterface) {
- $argumentError = sprintf('datefmt_format: string \'%s\' is not numeric, which would be required for it to be a valid date', $datetime);
+ $argumentError = \sprintf('datefmt_format: string \'%s\' is not numeric, which would be required for it to be a valid date', $datetime);
}
if (null !== $argumentError) {
diff --git a/lib/symfony/polyfill-intl-icu/IntlListFormatter.php b/lib/symfony/polyfill-intl-icu/IntlListFormatter.php
new file mode 100644
index 0000000000..9dc7ca91fe
--- /dev/null
+++ b/lib/symfony/polyfill-intl-icu/IntlListFormatter.php
@@ -0,0 +1,169 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Polyfill\Intl\Icu;
+
+/**
+ * @author Ayesh Karunaratne
+ *
+ * @internal
+ */
+class IntlListFormatter
+{
+ public const TYPE_AND = 0;
+ public const TYPE_OR = 1;
+ public const TYPE_UNITS = 2;
+
+ public const WIDTH_WIDE = 0;
+ public const WIDTH_SHORT = 1;
+ public const WIDTH_NARROW = 2;
+
+ private $type;
+ private $width;
+
+ private const TYPE_MAP = [
+ self::TYPE_AND => 'standard',
+ self::TYPE_OR => 'or',
+ self::TYPE_UNITS => 'unit',
+ ];
+
+ private const WIDTH_MAP = [
+ self::WIDTH_WIDE => '',
+ self::WIDTH_SHORT => '-short',
+ self::WIDTH_NARROW => '-narrow',
+ ];
+
+ private const EN_LIST_PATTERNS = [
+ 'listPattern-type-standard' => [
+ 'start' => '{0}, {1}',
+ 'middle' => '{0}, {1}',
+ 'end' => '{0}, and {1}',
+ 2 => '{0} and {1}',
+ ],
+ 'listPattern-type-or' => [
+ 'start' => '{0}, {1}',
+ 'middle' => '{0}, {1}',
+ 'end' => '{0}, or {1}',
+ 2 => '{0} or {1}',
+ ],
+ 'listPattern-type-or-narrow' => [
+ 'start' => '{0}, {1}',
+ 'middle' => '{0}, {1}',
+ 'end' => '{0}, or {1}',
+ 2 => '{0} or {1}',
+ ],
+ 'listPattern-type-or-short' => [
+ 'start' => '{0}, {1}',
+ 'middle' => '{0}, {1}',
+ 'end' => '{0}, or {1}',
+ 2 => '{0} or {1}',
+ ],
+ 'listPattern-type-standard-narrow' => [
+ 'start' => '{0}, {1}',
+ 'middle' => '{0}, {1}',
+ 'end' => '{0}, {1}',
+ 2 => '{0}, {1}',
+ ],
+ 'listPattern-type-standard-short' => [
+ 'start' => '{0}, {1}',
+ 'middle' => '{0}, {1}',
+ 'end' => '{0}, & {1}',
+ 2 => '{0} & {1}',
+ ],
+ 'listPattern-type-unit' => [
+ 'start' => '{0}, {1}',
+ 'middle' => '{0}, {1}',
+ 'end' => '{0}, {1}',
+ 2 => '{0}, {1}',
+ ],
+ 'listPattern-type-unit-narrow' => [
+ 'start' => '{0} {1}',
+ 'middle' => '{0} {1}',
+ 'end' => '{0} {1}',
+ 2 => '{0} {1}',
+ ],
+ 'listPattern-type-unit-short' => [
+ 'start' => '{0}, {1}',
+ 'middle' => '{0}, {1}',
+ 'end' => '{0}, {1}',
+ 2 => '{0}, {1}',
+ ],
+ ];
+
+ public function __construct(string $locale, int $type = self::TYPE_AND, int $width = self::WIDTH_WIDE)
+ {
+ if ('en' !== $locale && 0 !== strpos($locale, 'en')) {
+ if (80000 > \PHP_VERSION_ID) {
+ throw new \InvalidArgumentException('Invalid locale, only "en" and "en-*" locales are supported.');
+ }
+
+ throw new \ValueError('Invalid locale, only "en" and "en-*" locales are supported.');
+ }
+
+ if (!isset(self::TYPE_MAP[$type])) {
+ if (80000 > \PHP_VERSION_ID) {
+ throw new \InvalidArgumentException('Argument #2 ($type) must be one of IntlListFormatter::TYPE_AND, IntlListFormatter::TYPE_OR, or IntlListFormatter::TYPE_UNITS.');
+ }
+
+ throw new \ValueError('Argument #2 ($type) must be one of IntlListFormatter::TYPE_AND, IntlListFormatter::TYPE_OR, or IntlListFormatter::TYPE_UNITS.');
+ }
+
+ if (!isset(self::WIDTH_MAP[$width])) {
+ if (80000 > \PHP_VERSION_ID) {
+ throw new \InvalidArgumentException('Argument #3 ($width) must be one of IntlListFormatter::WIDTH_WIDE, IntlListFormatter::WIDTH_SHORT, or IntlListFormatter::WIDTH_NARROW.');
+ }
+
+ throw new \ValueError('Argument #3 ($width) must be one of IntlListFormatter::WIDTH_WIDE, IntlListFormatter::WIDTH_SHORT, or IntlListFormatter::WIDTH_NARROW.');
+ }
+
+ $this->type = $type;
+ $this->width = $width;
+ }
+
+ public function format(array $strings): string
+ {
+ $count = \count($strings);
+
+ if (0 === $count) {
+ return '';
+ }
+
+ $strings = array_values($strings);
+
+ if (1 === $count) {
+ return (string) $strings[0];
+ }
+
+ $pattern = self::EN_LIST_PATTERNS['listPattern-type-'.self::TYPE_MAP[$this->type].self::WIDTH_MAP[$this->width]];
+
+ if (2 === $count) {
+ return strtr($pattern[2], ['{0}' => (string) $strings[0], '{1}' => (string) $strings[1]]);
+ }
+
+ $result = strtr($pattern['start'], ['{0}' => (string) $strings[0], '{1}' => (string) $strings[1]]);
+
+ for ($i = 2; $i < $count - 1; ++$i) {
+ $result = strtr($pattern['middle'], ['{0}' => $result, '{1}' => (string) $strings[$i]]);
+ }
+
+ return strtr($pattern['end'], ['{0}' => $result, '{1}' => (string) $strings[$count - 1]]);
+ }
+
+ public function getErrorCode(): int
+ {
+ return 0;
+ }
+
+ public function getErrorMessage(): string
+ {
+ return '';
+ }
+}
diff --git a/lib/symfony/polyfill-intl-icu/Locale.php b/lib/symfony/polyfill-intl-icu/Locale.php
index f449fd5dff..516c10b643 100644
--- a/lib/symfony/polyfill-intl-icu/Locale.php
+++ b/lib/symfony/polyfill-intl-icu/Locale.php
@@ -41,6 +41,28 @@ abstract class Locale
public const GRANDFATHERED_LANG_TAG = 'grandfathered';
public const PRIVATE_TAG = 'private';
+ private const RTL_SCRIPTS = [
+ 'Adlm' => true, 'Arab' => true, 'Armi' => true, 'Hebr' => true,
+ 'Mand' => true, 'Mani' => true, 'Mend' => true, 'Nkoo' => true,
+ 'Orkh' => true, 'Phnx' => true, 'Rohg' => true, 'Samr' => true,
+ 'Syrc' => true, 'Thaa' => true, 'Yezi' => true,
+ ];
+
+ private const LANG_TO_SCRIPT = [
+ 'ar' => 'Arab',
+ 'ckb' => 'Arab',
+ 'dv' => 'Thaa',
+ 'fa' => 'Arab',
+ 'he' => 'Hebr',
+ 'ku' => 'Arab',
+ 'nqo' => 'Nkoo',
+ 'ps' => 'Arab',
+ 'sd' => 'Arab',
+ 'ug' => 'Arab',
+ 'ur' => 'Arab',
+ 'yi' => 'Hebr',
+ ];
+
/**
* Not supported. Returns the best available locale based on HTTP "Accept-Language" header according to RFC 2616.
*
@@ -307,4 +329,22 @@ abstract class Locale
return true;
}
+
+ public static function isRightToLeft(string $locale): bool
+ {
+ if ('' === $locale) {
+ return false;
+ }
+
+ $parts = preg_split('/[_-]/', $locale);
+ $language = strtolower($parts[0]);
+
+ foreach ($parts as $part) {
+ if (4 === \strlen($part) && ctype_alpha($part)) {
+ return isset(self::RTL_SCRIPTS[ucfirst(strtolower($part))]);
+ }
+ }
+
+ return isset(self::LANG_TO_SCRIPT[$language]) && isset(self::RTL_SCRIPTS[self::LANG_TO_SCRIPT[$language]]);
+ }
}
diff --git a/lib/symfony/polyfill-intl-icu/NumberFormatter.php b/lib/symfony/polyfill-intl-icu/NumberFormatter.php
index cec375db73..9c8193d19c 100644
--- a/lib/symfony/polyfill-intl-icu/NumberFormatter.php
+++ b/lib/symfony/polyfill-intl-icu/NumberFormatter.php
@@ -261,7 +261,7 @@ abstract class NumberFormatter
}
if (!\in_array($style, self::$supportedStyles)) {
- $message = sprintf('The available styles are: %s.', implode(', ', array_keys(self::$supportedStyles)));
+ $message = \sprintf('The available styles are: %s.', implode(', ', array_keys(self::$supportedStyles)));
throw new MethodArgumentValueNotImplementedException(__METHOD__, 'style', $style, $message);
}
@@ -352,7 +352,7 @@ abstract class NumberFormatter
// The original NumberFormatter does not support this format type
if (self::TYPE_CURRENCY === $type) {
if (\PHP_VERSION_ID >= 80000) {
- throw new \ValueError(sprintf('The format type must be a NumberFormatter::TYPE_* constant (%s given).', $type));
+ throw new \ValueError(\sprintf('The format type must be a NumberFormatter::TYPE_* constant (%s given).', $type));
}
trigger_error(__METHOD__.'(): Unsupported format type '.$type, \E_USER_WARNING);
@@ -361,7 +361,7 @@ abstract class NumberFormatter
}
if (self::CURRENCY === $this->style) {
- throw new NotImplementedException(sprintf('"%s()" method does not support the formatting of currencies (instance with CURRENCY style). "%s".', __METHOD__, NotImplementedException::INTL_INSTALL_MESSAGE));
+ throw new NotImplementedException(\sprintf('"%s()" method does not support the formatting of currencies (instance with CURRENCY style). "%s".', __METHOD__, NotImplementedException::INTL_INSTALL_MESSAGE));
}
// Only the default type is supported.
@@ -496,7 +496,7 @@ abstract class NumberFormatter
{
if (self::TYPE_DEFAULT === $type || self::TYPE_CURRENCY === $type) {
if (\PHP_VERSION_ID >= 80000) {
- throw new \ValueError(sprintf('The format type must be a NumberFormatter::TYPE_* constant (%d given).', $type));
+ throw new \ValueError(\sprintf('The format type must be a NumberFormatter::TYPE_* constant (%d given).', $type));
}
trigger_error(__METHOD__.'(): Unsupported format type '.$type, \E_USER_WARNING);
@@ -553,7 +553,7 @@ abstract class NumberFormatter
public function setAttribute(int $attribute, $value)
{
if (!\in_array($attribute, self::$supportedAttributes)) {
- $message = sprintf(
+ $message = \sprintf(
'The available attributes are: %s',
implode(', ', array_keys(self::$supportedAttributes))
);
@@ -562,7 +562,7 @@ abstract class NumberFormatter
}
if (self::$supportedAttributes['ROUNDING_MODE'] === $attribute && $this->isInvalidRoundingMode($value)) {
- $message = sprintf(
+ $message = \sprintf(
'The supported values for ROUNDING_MODE are: %s',
implode(', ', array_keys(self::$roundingModes))
);
diff --git a/lib/symfony/polyfill-intl-icu/README.md b/lib/symfony/polyfill-intl-icu/README.md
index b7faedc5d2..0ac69a00b9 100644
--- a/lib/symfony/polyfill-intl-icu/README.md
+++ b/lib/symfony/polyfill-intl-icu/README.md
@@ -13,6 +13,7 @@ It is limited to the "en" locale and to:
- [`NumberFormatter`](https://php.net/NumberFormatter)
- [`Locale`](https://php.net/Locale)
- [`IntlDateFormatter`](https://php.net/IntlDateFormatter)
+- [`IntlListFormatter`](https://php.net/IntlListFormatter)
More information can be found in the
[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md).
diff --git a/lib/symfony/polyfill-intl-icu/Resources/stubs/IntlListFormatter.php b/lib/symfony/polyfill-intl-icu/Resources/stubs/IntlListFormatter.php
new file mode 100644
index 0000000000..ba7807203d
--- /dev/null
+++ b/lib/symfony/polyfill-intl-icu/Resources/stubs/IntlListFormatter.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Polyfill\Intl\Icu\IntlListFormatter as IntlListFormatterPolyfill;
+
+/**
+ * Stub implementation for the IntlListFormatter class of the intl extension.
+ *
+ * @author Ayesh Karunaratne
+ */
+final class IntlListFormatter extends IntlListFormatterPolyfill
+{
+}
diff --git a/lib/symfony/polyfill-mbstring/Mbstring.php b/lib/symfony/polyfill-mbstring/Mbstring.php
index 31e36a368f..7f256360bd 100644
--- a/lib/symfony/polyfill-mbstring/Mbstring.php
+++ b/lib/symfony/polyfill-mbstring/Mbstring.php
@@ -133,7 +133,7 @@ final class Mbstring
public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars)
{
$ok = true;
- array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) {
+ array_walk_recursive($vars, static function (&$v) use (&$ok, $toEncoding, $fromEncoding) {
if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) {
$ok = false;
}
@@ -194,7 +194,7 @@ final class Mbstring
$convmap[$i + 1] += $convmap[$i + 2];
}
- $s = preg_replace_callback('/(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) {
+ $s = preg_replace_callback('/(?:0*([0-9]+)|x0*([0-9a-fA-F]+))'.(\PHP_VERSION_ID >= 80200 ? '' : '(?!&)').';?/', static function (array $m) use ($cnt, $convmap) {
$c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1];
for ($i = 0; $i < $cnt; $i += 4) {
if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) {
@@ -268,7 +268,7 @@ final class Mbstring
for ($j = 0; $j < $cnt; $j += 4) {
if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) {
$cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3];
- $result .= $is_hex ? sprintf('%X;', $cOffset) : ''.$cOffset.';';
+ $result .= $is_hex ? \sprintf('%X;', $cOffset) : ''.$cOffset.';';
continue 2;
}
}
@@ -382,7 +382,7 @@ final class Mbstring
return false;
}
- throw new \ValueError(sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding));
+ throw new \ValueError(\sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding));
}
public static function mb_language($lang = null)
@@ -403,7 +403,7 @@ final class Mbstring
return false;
}
- throw new \ValueError(sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang));
+ throw new \ValueError(\sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang));
}
public static function mb_list_encodings()
@@ -834,19 +834,32 @@ final class Mbstring
return $code;
}
- public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null): string
+ /** @return string|false */
+ public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null)
{
if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) {
+ if (\PHP_VERSION_ID < 80000) {
+ trigger_error('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH', \E_USER_WARNING);
+
+ return false;
+ }
+
throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH');
}
if (null === $encoding) {
$encoding = self::mb_internal_encoding();
- } else {
- self::assertEncoding($encoding, 'mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given');
+ } elseif (!self::assertEncoding($encoding, 'mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given')) {
+ return false;
}
if (self::mb_strlen($pad_string, $encoding) <= 0) {
+ if (\PHP_VERSION_ID < 80000) {
+ trigger_error('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string', \E_USER_WARNING);
+
+ return false;
+ }
+
throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string');
}
@@ -869,12 +882,13 @@ final class Mbstring
}
}
- public static function mb_ucfirst(string $string, ?string $encoding = null): string
+ /** @return string|false */
+ public static function mb_ucfirst(string $string, ?string $encoding = null)
{
if (null === $encoding) {
$encoding = self::mb_internal_encoding();
- } else {
- self::assertEncoding($encoding, 'mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given');
+ } elseif (!self::assertEncoding($encoding, 'mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given')) {
+ return false;
}
$firstChar = mb_substr($string, 0, 1, $encoding);
@@ -883,12 +897,13 @@ final class Mbstring
return $firstChar.mb_substr($string, 1, null, $encoding);
}
- public static function mb_lcfirst(string $string, ?string $encoding = null): string
+ /** @return string|false */
+ public static function mb_lcfirst(string $string, ?string $encoding = null)
{
if (null === $encoding) {
$encoding = self::mb_internal_encoding();
- } else {
- self::assertEncoding($encoding, 'mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given');
+ } elseif (!self::assertEncoding($encoding, 'mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given')) {
+ return false;
}
$firstChar = mb_substr($string, 0, 1, $encoding);
@@ -968,30 +983,42 @@ final class Mbstring
return 'UTF-8';
}
+ if ('UTF-32' === $encoding) {
+ return 'UTF-32BE';
+ }
+
+ if ('UTF-16' === $encoding) {
+ return 'UTF-16BE';
+ }
+
return $encoding;
}
- public static function mb_trim(string $string, ?string $characters = null, ?string $encoding = null): string
+ /** @return string|false */
+ public static function mb_trim(string $string, ?string $characters = null, ?string $encoding = null)
{
return self::mb_internal_trim('{^[%s]+|[%1$s]+$}Du', $string, $characters, $encoding, __FUNCTION__);
}
- public static function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null): string
+ /** @return string|false */
+ public static function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null)
{
return self::mb_internal_trim('{^[%s]+}Du', $string, $characters, $encoding, __FUNCTION__);
}
- public static function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null): string
+ /** @return string|false */
+ public static function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null)
{
return self::mb_internal_trim('{[%s]+$}Du', $string, $characters, $encoding, __FUNCTION__);
}
- private static function mb_internal_trim(string $regex, string $string, ?string $characters, ?string $encoding, string $function): string
+ /** @return string|false */
+ private static function mb_internal_trim(string $regex, string $string, ?string $characters, ?string $encoding, string $function)
{
if (null === $encoding) {
$encoding = self::mb_internal_encoding();
- } else {
- self::assertEncoding($encoding, $function.'(): Argument #3 ($encoding) must be a valid encoding, "%s" given');
+ } elseif (!self::assertEncoding($encoding, $function.'(): Argument #3 ($encoding) must be a valid encoding, "%s" given')) {
+ return false;
}
if ('' === $characters) {
@@ -1020,7 +1047,7 @@ final class Mbstring
$characters = preg_quote($characters);
}
- $string = preg_replace(sprintf($regex, $characters), '', $string);
+ $string = preg_replace(\sprintf($regex, $characters), '', $string);
if (null === $encoding) {
return $string;
@@ -1029,17 +1056,22 @@ final class Mbstring
return iconv('UTF-8', $encoding.'//IGNORE', $string);
}
- private static function assertEncoding(string $encoding, string $errorFormat): void
+ private static function assertEncoding(string $encoding, string $errorFormat): bool
{
try {
$validEncoding = @self::mb_check_encoding('', $encoding);
} catch (\ValueError $e) {
- throw new \ValueError(sprintf($errorFormat, $encoding));
+ throw new \ValueError(\sprintf($errorFormat, $encoding));
}
- // BC for PHP 7.3 and lower
if (!$validEncoding) {
- throw new \ValueError(sprintf($errorFormat, $encoding));
+ if (80000 > \PHP_VERSION_ID) {
+ trigger_error(\sprintf($errorFormat, $encoding), \E_USER_WARNING);
+ } else {
+ throw new \ValueError(\sprintf($errorFormat, $encoding));
+ }
}
+
+ return $validEncoding;
}
}
diff --git a/lib/symfony/polyfill-mbstring/bootstrap.php b/lib/symfony/polyfill-mbstring/bootstrap.php
index ff51ae0796..df3d9f3d4f 100644
--- a/lib/symfony/polyfill-mbstring/bootstrap.php
+++ b/lib/symfony/polyfill-mbstring/bootstrap.php
@@ -133,30 +133,29 @@ if (!function_exists('mb_str_split')) {
}
if (!function_exists('mb_str_pad')) {
- function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
+ function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null) { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
}
if (!function_exists('mb_ucfirst')) {
- function mb_ucfirst(string $string, ?string $encoding = null): string { return p\Mbstring::mb_ucfirst($string, $encoding); }
+ function mb_ucfirst(string $string, ?string $encoding = null) { return p\Mbstring::mb_ucfirst($string, $encoding); }
}
if (!function_exists('mb_lcfirst')) {
- function mb_lcfirst(string $string, ?string $encoding = null): string { return p\Mbstring::mb_lcfirst($string, $encoding); }
+ function mb_lcfirst(string $string, ?string $encoding = null) { return p\Mbstring::mb_lcfirst($string, $encoding); }
}
if (!function_exists('mb_trim')) {
- function mb_trim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_trim($string, $characters, $encoding); }
+ function mb_trim(string $string, ?string $characters = null, ?string $encoding = null) { return p\Mbstring::mb_trim($string, $characters, $encoding); }
}
if (!function_exists('mb_ltrim')) {
- function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_ltrim($string, $characters, $encoding); }
+ function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null) { return p\Mbstring::mb_ltrim($string, $characters, $encoding); }
}
if (!function_exists('mb_rtrim')) {
- function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_rtrim($string, $characters, $encoding); }
+ function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null) { return p\Mbstring::mb_rtrim($string, $characters, $encoding); }
}
-
if (extension_loaded('mbstring')) {
return;
}
diff --git a/lib/symfony/polyfill-php83/Php83.php b/lib/symfony/polyfill-php83/Php83.php
index 8b7ee4c700..03409fb0c2 100644
--- a/lib/symfony/polyfill-php83/Php83.php
+++ b/lib/symfony/polyfill-php83/Php83.php
@@ -32,7 +32,7 @@ final class Php83
}
if ($depth > self::JSON_MAX_DEPTH) {
- throw new \ValueError(sprintf('json_validate(): Argument #2 ($depth) must be less than %d', self::JSON_MAX_DEPTH));
+ throw new \ValueError(\sprintf('json_validate(): Argument #2 ($depth) must be less than %d', self::JSON_MAX_DEPTH));
}
json_decode($json, true, $depth, $flags);
@@ -40,7 +40,8 @@ final class Php83
return \JSON_ERROR_NONE === json_last_error();
}
- public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null): string
+ /** @return string|false */
+ public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null)
{
if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) {
throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH');
@@ -50,19 +51,27 @@ final class Php83
$encoding = mb_internal_encoding();
}
+ $errorToTrigger = null;
try {
- $validEncoding = @mb_check_encoding('', $encoding);
+ if (!@mb_check_encoding('', $encoding)) {
+ $errorToTrigger = \sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding);
+ }
} catch (\ValueError $e) {
- throw new \ValueError(sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding));
- }
-
- // BC for PHP 7.3 and lower
- if (!$validEncoding) {
- throw new \ValueError(sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding));
+ $errorToTrigger = \sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding);
}
if (mb_strlen($pad_string, $encoding) <= 0) {
- throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string');
+ $errorToTrigger = 'mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string';
+ }
+
+ if (null !== $errorToTrigger) {
+ if (80000 > \PHP_VERSION_ID) {
+ trigger_error($errorToTrigger, \E_USER_WARNING);
+
+ return false;
+ }
+
+ throw new \ValueError($errorToTrigger);
}
$paddingRequired = $length - mb_strlen($string, $encoding);
@@ -135,7 +144,7 @@ final class Php83
}
if (preg_match('/\A(?:0[aA0]?|[aA])\z/', $string)) {
- throw new \ValueError(sprintf('str_decrement(): Argument #1 ($string) "%s" is out of decrement range', $string));
+ throw new \ValueError(\sprintf('str_decrement(): Argument #1 ($string) "%s" is out of decrement range', $string));
}
if (!\in_array(substr($string, -1), ['A', 'a', '0'], true)) {
diff --git a/lib/symfony/polyfill-php83/bootstrap.php b/lib/symfony/polyfill-php83/bootstrap.php
index a92799cb38..8d721a29ca 100644
--- a/lib/symfony/polyfill-php83/bootstrap.php
+++ b/lib/symfony/polyfill-php83/bootstrap.php
@@ -19,12 +19,6 @@ if (!function_exists('json_validate')) {
function json_validate(string $json, int $depth = 512, int $flags = 0): bool { return p\Php83::json_validate($json, $depth, $flags); }
}
-if (extension_loaded('mbstring')) {
- if (!function_exists('mb_str_pad')) {
- function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Php83::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
- }
-}
-
if (!function_exists('stream_context_set_options')) {
function stream_context_set_options($context, array $options): bool { return stream_context_set_option($context, $options); }
}
@@ -37,8 +31,14 @@ if (!function_exists('str_decrement')) {
function str_decrement(string $string): string { return p\Php83::str_decrement($string); }
}
-if (\PHP_VERSION_ID >= 80100) {
- return require __DIR__.'/bootstrap81.php';
+if (\PHP_VERSION_ID >= 80000) {
+ return require __DIR__.'/bootstrap80.php';
+}
+
+if (extension_loaded('mbstring')) {
+ if (!function_exists('mb_str_pad')) {
+ function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null) { return p\Php83::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
+ }
}
if (!function_exists('ldap_exop_sync') && function_exists('ldap_exop')) {
diff --git a/lib/symfony/polyfill-php83/bootstrap80.php b/lib/symfony/polyfill-php83/bootstrap80.php
new file mode 100644
index 0000000000..9b812d0244
--- /dev/null
+++ b/lib/symfony/polyfill-php83/bootstrap80.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Polyfill\Php83 as p;
+
+if (extension_loaded('mbstring')) {
+ if (!function_exists('mb_str_pad')) {
+ function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Php83::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
+ }
+}
+
+if (\PHP_VERSION_ID >= 80100) {
+ return require __DIR__.'/bootstrap81.php';
+}
+
+if (!function_exists('ldap_exop_sync') && function_exists('ldap_exop')) {
+ function ldap_exop_sync($ldap, string $request_oid, ?string $request_data = null, ?array $controls = null, &$response_data = null, &$response_oid = null): bool { return ldap_exop($ldap, $request_oid, $request_data, $controls, $response_data, $response_oid); }
+}
+
+if (!function_exists('ldap_connect_wallet') && function_exists('ldap_connect')) {
+ function ldap_connect_wallet(?string $uri, string $wallet, string $password, int $auth_mode = \GSLC_SSL_NO_AUTH) { return ldap_connect($uri, $wallet, $password, $auth_mode); }
+}
diff --git a/lib/symfony/runtime/CHANGELOG.md b/lib/symfony/runtime/CHANGELOG.md
new file mode 100644
index 0000000000..1a608b4cf7
--- /dev/null
+++ b/lib/symfony/runtime/CHANGELOG.md
@@ -0,0 +1,19 @@
+CHANGELOG
+=========
+
+6.4
+---
+
+ * Add argument `bool $debug = false` to `HttpKernelRunner::__construct()`
+
+5.4
+---
+
+ * The component is not experimental anymore
+ * Add options "env_var_name" and "debug_var_name" to `GenericRuntime` and `SymfonyRuntime`
+ * Add option "dotenv_overload" to `SymfonyRuntime`
+
+5.3.0
+-----
+
+ * Add the component
diff --git a/lib/symfony/runtime/GenericRuntime.php b/lib/symfony/runtime/GenericRuntime.php
new file mode 100644
index 0000000000..b432630d13
--- /dev/null
+++ b/lib/symfony/runtime/GenericRuntime.php
@@ -0,0 +1,223 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Runtime;
+
+use Symfony\Component\Runtime\Internal\BasicErrorHandler;
+use Symfony\Component\Runtime\Resolver\ClosureResolver;
+use Symfony\Component\Runtime\Resolver\DebugClosureResolver;
+use Symfony\Component\Runtime\Runner\ClosureRunner;
+
+// Help opcache.preload discover always-needed symbols
+class_exists(ClosureResolver::class);
+
+/**
+ * A runtime to do bare-metal PHP without using superglobals.
+ *
+ * It supports the following options:
+ * - "debug" toggles displaying errors and defaults
+ * to the "APP_DEBUG" environment variable;
+ * - "runtimes" maps types to a GenericRuntime implementation
+ * that knows how to deal with each of them;
+ * - "error_handler" defines the class to use to handle PHP errors;
+ * - "env_var_name" and "debug_var_name" define the name of the env
+ * vars that hold the Symfony env and the debug flag respectively.
+ *
+ * The app-callable can declare arguments among either:
+ * - "array $context" to get a local array similar to $_SERVER;
+ * - "array $argv" to get the command line arguments when running on the CLI;
+ * - "array $request" to get a local array with keys "query", "body", "files" and
+ * "session", which map to $_GET, $_POST, $FILES and &$_SESSION respectively.
+ *
+ * It should return a Closure():int|string|null or an instance of RunnerInterface.
+ *
+ * In debug mode, the runtime registers a strict error handler
+ * that throws exceptions when a PHP warning/notice is raised.
+ *
+ * @author Nicolas Grekas
+ */
+class GenericRuntime implements RuntimeInterface
+{
+ protected $options;
+
+ /**
+ * @param array {
+ * debug?: ?bool,
+ * runtimes?: ?array,
+ * error_handler?: string|false,
+ * env_var_name?: string,
+ * debug_var_name?: string,
+ * } $options
+ */
+ public function __construct(array $options = [])
+ {
+ $options['env_var_name'] ??= 'APP_ENV';
+ $debugKey = $options['debug_var_name'] ??= 'APP_DEBUG';
+
+ $debug = $options['debug'] ?? $_SERVER[$debugKey] ?? $_ENV[$debugKey] ?? true;
+
+ if (!\is_bool($debug)) {
+ $debug = filter_var($debug, \FILTER_VALIDATE_BOOL);
+ }
+
+ if ($debug) {
+ umask(0000);
+ $_SERVER[$debugKey] = $_ENV[$debugKey] = '1';
+ } else {
+ $_SERVER[$debugKey] = $_ENV[$debugKey] = '0';
+ }
+
+ if (false !== $errorHandler = ($options['error_handler'] ?? BasicErrorHandler::class)) {
+ $errorHandler::register($debug);
+ $options['error_handler'] = false;
+ }
+
+ $this->options = $options;
+ }
+
+ public function getResolver(callable $callable, ?\ReflectionFunction $reflector = null): ResolverInterface
+ {
+ $callable = $callable(...);
+ $parameters = ($reflector ?? new \ReflectionFunction($callable))->getParameters();
+ $arguments = function () use ($parameters) {
+ $arguments = [];
+
+ try {
+ foreach ($parameters as $parameter) {
+ $type = $parameter->getType();
+ $arguments[] = $this->getArgument($parameter, $type instanceof \ReflectionNamedType ? $type->getName() : null);
+ }
+ } catch (\InvalidArgumentException $e) {
+ if (!$parameter->isOptional()) {
+ throw $e;
+ }
+ }
+
+ return $arguments;
+ };
+
+ if ($_SERVER[$this->options['debug_var_name']]) {
+ return new DebugClosureResolver($callable, $arguments);
+ }
+
+ return new ClosureResolver($callable, $arguments);
+ }
+
+ public function getRunner(?object $application): RunnerInterface
+ {
+ $application ??= static fn () => 0;
+
+ if ($application instanceof RunnerInterface) {
+ return $application;
+ }
+
+ if (!$application instanceof \Closure) {
+ if ($runtime = $this->resolveRuntime($application::class)) {
+ return $runtime->getRunner($application);
+ }
+
+ if (!\is_callable($application)) {
+ throw new \LogicException(\sprintf('"%s" doesn\'t know how to handle apps of type "%s".', get_debug_type($this), get_debug_type($application)));
+ }
+
+ $application = $application(...);
+ }
+
+ if ($_SERVER[$this->options['debug_var_name']] && ($r = new \ReflectionFunction($application)) && $r->getNumberOfRequiredParameters()) {
+ throw new \ArgumentCountError(\sprintf('Zero argument should be required by the runner callable, but at least one is in "%s" on line "%d.', $r->getFileName(), $r->getStartLine()));
+ }
+
+ return new ClosureRunner($application);
+ }
+
+ protected function getArgument(\ReflectionParameter $parameter, ?string $type): mixed
+ {
+ if ('array' === $type) {
+ switch ($parameter->name) {
+ case 'context':
+ $context = $_SERVER;
+
+ if ($_ENV && !isset($_SERVER['PATH']) && !isset($_SERVER['Path'])) {
+ $context += $_ENV;
+ }
+
+ return $context;
+
+ case 'argv':
+ return $_SERVER['argv'] ?? [];
+
+ case 'request':
+ return [
+ 'query' => $_GET,
+ 'body' => $_POST,
+ 'files' => $_FILES,
+ 'session' => &$_SESSION,
+ ];
+ }
+ }
+
+ if (RuntimeInterface::class === $type) {
+ return $this;
+ }
+
+ if (!$runtime = $this->getRuntime($type)) {
+ $r = $parameter->getDeclaringFunction();
+
+ throw new \InvalidArgumentException(\sprintf('Cannot resolve argument "%s $%s" in "%s" on line "%d": "%s" supports only arguments "array $context", "array $argv" and "array $request", or a runtime named "Symfony\Runtime\%1$sRuntime".', $type, $parameter->name, $r->getFileName(), $r->getStartLine(), get_debug_type($this)));
+ }
+
+ return $runtime->getArgument($parameter, $type);
+ }
+
+ protected static function register(self $runtime): self
+ {
+ return $runtime;
+ }
+
+ private function getRuntime(string $type): ?self
+ {
+ if (null === $runtime = ($this->options['runtimes'][$type] ?? null)) {
+ $runtime = 'Symfony\Runtime\\'.$type.'Runtime';
+ $runtime = class_exists($runtime) ? $runtime : $this->options['runtimes'][$type] = false;
+ }
+
+ if (\is_string($runtime)) {
+ $runtime = $runtime::register($this);
+ }
+
+ if ($this === $runtime) {
+ return null;
+ }
+
+ return $runtime ?: null;
+ }
+
+ private function resolveRuntime(string $class): ?self
+ {
+ if ($runtime = $this->getRuntime($class)) {
+ return $runtime;
+ }
+
+ foreach (class_parents($class) as $type) {
+ if ($runtime = $this->getRuntime($type)) {
+ return $runtime;
+ }
+ }
+
+ foreach (class_implements($class) as $type) {
+ if ($runtime = $this->getRuntime($type)) {
+ return $runtime;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/lib/symfony/runtime/Internal/BasicErrorHandler.php b/lib/symfony/runtime/Internal/BasicErrorHandler.php
new file mode 100644
index 0000000000..c0c290e686
--- /dev/null
+++ b/lib/symfony/runtime/Internal/BasicErrorHandler.php
@@ -0,0 +1,53 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Runtime\Internal;
+
+/**
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+class BasicErrorHandler
+{
+ public static function register(bool $debug): void
+ {
+ error_reporting(\E_ALL & ~\E_DEPRECATED & ~\E_USER_DEPRECATED);
+
+ if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) {
+ ini_set('display_errors', $debug);
+ } elseif (!filter_var(\ini_get('log_errors'), \FILTER_VALIDATE_BOOL) || \ini_get('error_log')) {
+ // CLI - display errors only if they're not already logged to STDERR
+ ini_set('display_errors', 1);
+ }
+
+ if (0 <= \ini_get('zend.assertions')) {
+ ini_set('zend.assertions', (int) $debug);
+ }
+ ini_set('assert.active', 1);
+ ini_set('assert.exception', 1);
+
+ set_error_handler(new self());
+ }
+
+ public function __invoke(int $type, string $message, string $file, int $line): bool
+ {
+ if ((\E_DEPRECATED | \E_USER_DEPRECATED) & $type) {
+ return true;
+ }
+
+ if ((error_reporting() | \E_ERROR | \E_RECOVERABLE_ERROR | \E_PARSE | \E_CORE_ERROR | \E_COMPILE_ERROR | \E_USER_ERROR) & $type) {
+ throw new \ErrorException($message, 0, $type, $file, $line);
+ }
+
+ return false;
+ }
+}
diff --git a/lib/symfony/runtime/Internal/ComposerPlugin.php b/lib/symfony/runtime/Internal/ComposerPlugin.php
new file mode 100644
index 0000000000..cad8d85fc3
--- /dev/null
+++ b/lib/symfony/runtime/Internal/ComposerPlugin.php
@@ -0,0 +1,119 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Runtime\Internal;
+
+use Composer\Composer;
+use Composer\EventDispatcher\EventSubscriberInterface;
+use Composer\Factory;
+use Composer\IO\IOInterface;
+use Composer\Plugin\PluginInterface;
+use Composer\Script\ScriptEvents;
+use Symfony\Component\Filesystem\Filesystem;
+use Symfony\Component\Runtime\SymfonyRuntime;
+
+/**
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+class ComposerPlugin implements PluginInterface, EventSubscriberInterface
+{
+ private Composer $composer;
+ private IOInterface $io;
+
+ private static bool $activated = false;
+
+ public function activate(Composer $composer, IOInterface $io): void
+ {
+ self::$activated = true;
+ $this->composer = $composer;
+ $this->io = $io;
+ }
+
+ public function deactivate(Composer $composer, IOInterface $io): void
+ {
+ self::$activated = false;
+ }
+
+ public function uninstall(Composer $composer, IOInterface $io): void
+ {
+ @unlink($composer->getConfig()->get('vendor-dir').'/autoload_runtime.php');
+ }
+
+ public function updateAutoloadFile(): void
+ {
+ $vendorDir = realpath($this->composer->getConfig()->get('vendor-dir'));
+
+ if (!is_file($autoloadFile = $vendorDir.'/autoload.php')
+ || false === $extra = $this->composer->getPackage()->getExtra()['runtime'] ?? []
+ ) {
+ return;
+ }
+
+ $fs = new Filesystem();
+ $projectDir = \dirname(realpath(Factory::getComposerFile()));
+
+ if (null === $autoloadTemplate = $extra['autoload_template'] ?? null) {
+ $autoloadTemplate = __DIR__.'/autoload_runtime.template';
+ } else {
+ if (!$fs->isAbsolutePath($autoloadTemplate)) {
+ $autoloadTemplate = $projectDir.'/'.$autoloadTemplate;
+ }
+
+ if (!is_file($autoloadTemplate)) {
+ throw new \InvalidArgumentException(\sprintf('File "%s" defined under "extra.runtime.autoload_template" in your composer.json file not found.', $this->composer->getPackage()->getExtra()['runtime']['autoload_template']));
+ }
+ }
+
+ $projectDir = $fs->makePathRelative($projectDir, $vendorDir);
+ $nestingLevel = 0;
+
+ while (str_starts_with($projectDir, '../')) {
+ ++$nestingLevel;
+ $projectDir = substr($projectDir, 3);
+ }
+
+ if (!$nestingLevel) {
+ $projectDir = '__'.'DIR__.'.var_export('/'.$projectDir, true);
+ } else {
+ $projectDir = 'dirname(__'."DIR__, $nestingLevel)".('' !== $projectDir ? '.'.var_export('/'.$projectDir, true) : '');
+ }
+
+ $runtimeClass = $extra['class'] ?? SymfonyRuntime::class;
+
+ unset($extra['class'], $extra['autoload_template']);
+
+ $code = strtr(file_get_contents($autoloadTemplate), [
+ '%project_dir%' => $projectDir,
+ '%runtime_class%' => var_export($runtimeClass, true),
+ '%runtime_options%' => '['.substr(var_export($extra, true), 7, -1)." 'project_dir' => {$projectDir},\n]",
+ ]);
+
+ // could use Composer\Util\Filesystem::filePutContentsIfModified once Composer 1.x support is dropped for this plugin
+ $path = substr_replace($autoloadFile, '_runtime', -4, 0);
+ $currentContent = @file_exists($path) ? @file_get_contents($path) : false;
+ if (false === $currentContent || $currentContent !== $code) {
+ file_put_contents($path, $code);
+ }
+ }
+
+ public static function getSubscribedEvents(): array
+ {
+ if (!self::$activated) {
+ return [];
+ }
+
+ return [
+ ScriptEvents::POST_AUTOLOAD_DUMP => 'updateAutoloadFile',
+ ];
+ }
+}
diff --git a/lib/symfony/runtime/Internal/Console/ApplicationRuntime.php b/lib/symfony/runtime/Internal/Console/ApplicationRuntime.php
new file mode 100644
index 0000000000..de6b1d9748
--- /dev/null
+++ b/lib/symfony/runtime/Internal/Console/ApplicationRuntime.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Runtime\Symfony\Component\Console;
+
+use Symfony\Component\Runtime\SymfonyRuntime;
+
+/**
+ * @internal
+ */
+class ApplicationRuntime extends SymfonyRuntime
+{
+}
diff --git a/lib/symfony/runtime/Internal/Console/Command/CommandRuntime.php b/lib/symfony/runtime/Internal/Console/Command/CommandRuntime.php
new file mode 100644
index 0000000000..9cc198ea1b
--- /dev/null
+++ b/lib/symfony/runtime/Internal/Console/Command/CommandRuntime.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Runtime\Symfony\Component\Console\Command;
+
+use Symfony\Component\Runtime\SymfonyRuntime;
+
+/**
+ * @internal
+ */
+class CommandRuntime extends SymfonyRuntime
+{
+}
diff --git a/lib/symfony/runtime/Internal/Console/Input/InputInterfaceRuntime.php b/lib/symfony/runtime/Internal/Console/Input/InputInterfaceRuntime.php
new file mode 100644
index 0000000000..44360bf508
--- /dev/null
+++ b/lib/symfony/runtime/Internal/Console/Input/InputInterfaceRuntime.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Runtime\Symfony\Component\Console\Input;
+
+use Symfony\Component\Runtime\SymfonyRuntime;
+
+/**
+ * @internal
+ */
+class InputInterfaceRuntime extends SymfonyRuntime
+{
+}
diff --git a/lib/symfony/runtime/Internal/Console/Output/OutputInterfaceRuntime.php b/lib/symfony/runtime/Internal/Console/Output/OutputInterfaceRuntime.php
new file mode 100644
index 0000000000..7c59186adc
--- /dev/null
+++ b/lib/symfony/runtime/Internal/Console/Output/OutputInterfaceRuntime.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Runtime\Symfony\Component\Console\Output;
+
+use Symfony\Component\Runtime\SymfonyRuntime;
+
+/**
+ * @internal
+ */
+class OutputInterfaceRuntime extends SymfonyRuntime
+{
+}
diff --git a/lib/symfony/runtime/Internal/HttpFoundation/RequestRuntime.php b/lib/symfony/runtime/Internal/HttpFoundation/RequestRuntime.php
new file mode 100644
index 0000000000..2a3a0bb827
--- /dev/null
+++ b/lib/symfony/runtime/Internal/HttpFoundation/RequestRuntime.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Runtime\Symfony\Component\HttpFoundation;
+
+use Symfony\Component\Runtime\SymfonyRuntime;
+
+/**
+ * @internal
+ */
+class RequestRuntime extends SymfonyRuntime
+{
+}
diff --git a/lib/symfony/runtime/Internal/HttpFoundation/ResponseRuntime.php b/lib/symfony/runtime/Internal/HttpFoundation/ResponseRuntime.php
new file mode 100644
index 0000000000..c70fbfff59
--- /dev/null
+++ b/lib/symfony/runtime/Internal/HttpFoundation/ResponseRuntime.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Runtime\Symfony\Component\HttpFoundation;
+
+use Symfony\Component\Runtime\SymfonyRuntime;
+
+/**
+ * @internal
+ */
+class ResponseRuntime extends SymfonyRuntime
+{
+}
diff --git a/lib/symfony/runtime/Internal/HttpKernel/HttpKernelInterfaceRuntime.php b/lib/symfony/runtime/Internal/HttpKernel/HttpKernelInterfaceRuntime.php
new file mode 100644
index 0000000000..08601abffc
--- /dev/null
+++ b/lib/symfony/runtime/Internal/HttpKernel/HttpKernelInterfaceRuntime.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Runtime\Symfony\Component\HttpKernel;
+
+use Symfony\Component\Runtime\SymfonyRuntime;
+
+/**
+ * @internal
+ */
+class HttpKernelInterfaceRuntime extends SymfonyRuntime
+{
+}
diff --git a/lib/symfony/runtime/Internal/MissingDotenv.php b/lib/symfony/runtime/Internal/MissingDotenv.php
new file mode 100644
index 0000000000..896865653e
--- /dev/null
+++ b/lib/symfony/runtime/Internal/MissingDotenv.php
@@ -0,0 +1,19 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Runtime\Internal;
+
+/**
+ * @internal class that should be loaded only when symfony/dotenv is not installed
+ */
+class MissingDotenv
+{
+}
diff --git a/lib/symfony/runtime/Internal/SymfonyErrorHandler.php b/lib/symfony/runtime/Internal/SymfonyErrorHandler.php
new file mode 100644
index 0000000000..47c67605b0
--- /dev/null
+++ b/lib/symfony/runtime/Internal/SymfonyErrorHandler.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Runtime\Internal;
+
+use Symfony\Component\ErrorHandler\BufferingLogger;
+use Symfony\Component\ErrorHandler\DebugClassLoader;
+use Symfony\Component\ErrorHandler\ErrorHandler;
+
+/**
+ * @author Nicolas Grekas
+ *
+ * @internal
+ */
+class SymfonyErrorHandler
+{
+ public static function register(bool $debug): void
+ {
+ if (!class_exists(ErrorHandler::class)) {
+ BasicErrorHandler::register($debug);
+
+ return;
+ }
+
+ error_reporting(\E_ALL & ~\E_DEPRECATED & ~\E_USER_DEPRECATED);
+
+ if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) {
+ ini_set('display_errors', $debug);
+ } elseif (!filter_var(\ini_get('log_errors'), \FILTER_VALIDATE_BOOL) || \ini_get('error_log')) {
+ // CLI - display errors only if they're not already logged to STDERR
+ ini_set('display_errors', 1);
+ }
+
+ if (0 <= \ini_get('zend.assertions')) {
+ ini_set('zend.assertions', (int) $debug);
+ }
+ ini_set('assert.active', 1);
+ ini_set('assert.exception', 1);
+
+ if ($debug) {
+ DebugClassLoader::enable();
+ }
+
+ ErrorHandler::register(new ErrorHandler(new BufferingLogger(), $debug));
+ }
+}
diff --git a/lib/symfony/runtime/Internal/autoload_runtime.template b/lib/symfony/runtime/Internal/autoload_runtime.template
new file mode 100644
index 0000000000..68af945932
--- /dev/null
+++ b/lib/symfony/runtime/Internal/autoload_runtime.template
@@ -0,0 +1,28 @@
+getResolver($app)
+ ->resolve();
+
+$app = $app(...$args);
+
+exit(
+ $runtime
+ ->getRunner($app)
+ ->run()
+);
diff --git a/lib/symfony/runtime/LICENSE b/lib/symfony/runtime/LICENSE
new file mode 100644
index 0000000000..99c6bdf356
--- /dev/null
+++ b/lib/symfony/runtime/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2021-present Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/lib/symfony/runtime/README.md b/lib/symfony/runtime/README.md
new file mode 100644
index 0000000000..006e7a22cd
--- /dev/null
+++ b/lib/symfony/runtime/README.md
@@ -0,0 +1,13 @@
+Runtime Component
+=================
+
+Symfony Runtime enables decoupling applications from global state.
+
+Resources
+---------
+
+ * [Documentation](https://symfony.com/doc/current/components/runtime.html)
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/lib/symfony/runtime/Resolver/ClosureResolver.php b/lib/symfony/runtime/Resolver/ClosureResolver.php
new file mode 100644
index 0000000000..c58cf2fbfb
--- /dev/null
+++ b/lib/symfony/runtime/Resolver/ClosureResolver.php
@@ -0,0 +1,31 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Runtime\Resolver;
+
+use Symfony\Component\Runtime\ResolverInterface;
+
+/**
+ * @author Nicolas Grekas
+ */
+class ClosureResolver implements ResolverInterface
+{
+ public function __construct(
+ private readonly \Closure $closure,
+ private readonly \Closure $arguments,
+ ) {
+ }
+
+ public function resolve(): array
+ {
+ return [$this->closure, ($this->arguments)()];
+ }
+}
diff --git a/lib/symfony/runtime/Resolver/DebugClosureResolver.php b/lib/symfony/runtime/Resolver/DebugClosureResolver.php
new file mode 100644
index 0000000000..026b2731b1
--- /dev/null
+++ b/lib/symfony/runtime/Resolver/DebugClosureResolver.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Runtime\Resolver;
+
+/**
+ * @author Nicolas Grekas
+ */
+class DebugClosureResolver extends ClosureResolver
+{
+ public function resolve(): array
+ {
+ [$closure, $arguments] = parent::resolve();
+
+ return [
+ static function (...$arguments) use ($closure) {
+ if (\is_object($app = $closure(...$arguments)) || null === $app) {
+ return $app;
+ }
+
+ $r = new \ReflectionFunction($closure);
+
+ throw new \TypeError(\sprintf('Unexpected value of type "%s" returned, "object" expected from "%s" on line "%d".', get_debug_type($app), $r->getFileName(), $r->getStartLine()));
+ },
+ $arguments,
+ ];
+ }
+}
diff --git a/lib/symfony/runtime/ResolverInterface.php b/lib/symfony/runtime/ResolverInterface.php
new file mode 100644
index 0000000000..f6fa5980ed
--- /dev/null
+++ b/lib/symfony/runtime/ResolverInterface.php
@@ -0,0 +1,23 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Runtime;
+
+/**
+ * @author Nicolas Grekas
+ */
+interface ResolverInterface
+{
+ /**
+ * @return array{0: callable, 1: mixed[]}
+ */
+ public function resolve(): array;
+}
diff --git a/lib/symfony/runtime/Runner/ClosureRunner.php b/lib/symfony/runtime/Runner/ClosureRunner.php
new file mode 100644
index 0000000000..2b54f5a320
--- /dev/null
+++ b/lib/symfony/runtime/Runner/ClosureRunner.php
@@ -0,0 +1,44 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Runtime\Runner;
+
+use Symfony\Component\Runtime\RunnerInterface;
+
+/**
+ * @author Nicolas Grekas
+ */
+class ClosureRunner implements RunnerInterface
+{
+ public function __construct(
+ private readonly \Closure $closure,
+ ) {
+ }
+
+ public function run(): int
+ {
+ $exitStatus = ($this->closure)();
+
+ if (\is_string($exitStatus)) {
+ echo $exitStatus;
+
+ return 0;
+ }
+
+ if (null !== $exitStatus && !\is_int($exitStatus)) {
+ $r = new \ReflectionFunction($this->closure);
+
+ throw new \TypeError(\sprintf('Unexpected value of type "%s" returned, "string|int|null" expected from "%s" on line "%d".', get_debug_type($exitStatus), $r->getFileName(), $r->getStartLine()));
+ }
+
+ return $exitStatus ?? 0;
+ }
+}
diff --git a/lib/symfony/runtime/Runner/Symfony/ConsoleApplicationRunner.php b/lib/symfony/runtime/Runner/Symfony/ConsoleApplicationRunner.php
new file mode 100644
index 0000000000..d64bc1c77b
--- /dev/null
+++ b/lib/symfony/runtime/Runner/Symfony/ConsoleApplicationRunner.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Runtime\Runner\Symfony;
+
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Runtime\RunnerInterface;
+
+/**
+ * @author Nicolas Grekas
+ */
+class ConsoleApplicationRunner implements RunnerInterface
+{
+ public function __construct(
+ private readonly Application $application,
+ private readonly ?string $defaultEnv,
+ private readonly InputInterface $input,
+ private readonly ?OutputInterface $output = null,
+ ) {
+ }
+
+ public function run(): int
+ {
+ if (null === $this->defaultEnv) {
+ return $this->application->run($this->input, $this->output);
+ }
+
+ $definition = $this->application->getDefinition();
+
+ if (!$definition->hasOption('env') && !$definition->hasOption('e') && !$definition->hasShortcut('e')) {
+ $definition->addOption(new InputOption('--env', '-e', InputOption::VALUE_REQUIRED, 'The Environment name.', $this->defaultEnv));
+ }
+
+ if (!$definition->hasOption('no-debug')) {
+ $definition->addOption(new InputOption('--no-debug', null, InputOption::VALUE_NONE, 'Switches off debug mode.'));
+ }
+
+ return $this->application->run($this->input, $this->output);
+ }
+}
diff --git a/lib/symfony/runtime/Runner/Symfony/HttpKernelRunner.php b/lib/symfony/runtime/Runner/Symfony/HttpKernelRunner.php
new file mode 100644
index 0000000000..13c9037ca8
--- /dev/null
+++ b/lib/symfony/runtime/Runner/Symfony/HttpKernelRunner.php
@@ -0,0 +1,58 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Runtime\Runner\Symfony;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+use Symfony\Component\HttpKernel\Kernel;
+use Symfony\Component\HttpKernel\TerminableInterface;
+use Symfony\Component\Runtime\RunnerInterface;
+
+/**
+ * @author Nicolas Grekas
+ */
+class HttpKernelRunner implements RunnerInterface
+{
+ public function __construct(
+ private readonly HttpKernelInterface $kernel,
+ private readonly Request $request,
+ private readonly bool $debug = false,
+ ) {
+ }
+
+ public function run(): int
+ {
+ $response = $this->kernel->handle($this->request);
+
+ if (Kernel::VERSION_ID >= 60400) {
+ $response->send(false);
+
+ if (\function_exists('fastcgi_finish_request') && !$this->debug) {
+ fastcgi_finish_request();
+ } elseif (\function_exists('litespeed_finish_request') && !$this->debug) {
+ litespeed_finish_request();
+ } else {
+ Response::closeOutputBuffers(0, true);
+ flush();
+ }
+ } else {
+ $response->send();
+ }
+
+ if ($this->kernel instanceof TerminableInterface) {
+ $this->kernel->terminate($this->request, $response);
+ }
+
+ return 0;
+ }
+}
diff --git a/lib/symfony/runtime/Runner/Symfony/ResponseRunner.php b/lib/symfony/runtime/Runner/Symfony/ResponseRunner.php
new file mode 100644
index 0000000000..04e5a3fa14
--- /dev/null
+++ b/lib/symfony/runtime/Runner/Symfony/ResponseRunner.php
@@ -0,0 +1,33 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Runtime\Runner\Symfony;
+
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Runtime\RunnerInterface;
+
+/**
+ * @author Nicolas Grekas
+ */
+class ResponseRunner implements RunnerInterface
+{
+ public function __construct(
+ private readonly Response $response,
+ ) {
+ }
+
+ public function run(): int
+ {
+ $this->response->send();
+
+ return 0;
+ }
+}
diff --git a/lib/symfony/runtime/RunnerInterface.php b/lib/symfony/runtime/RunnerInterface.php
new file mode 100644
index 0000000000..9001ff4357
--- /dev/null
+++ b/lib/symfony/runtime/RunnerInterface.php
@@ -0,0 +1,20 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Runtime;
+
+/**
+ * @author Nicolas Grekas
+ */
+interface RunnerInterface
+{
+ public function run(): int;
+}
diff --git a/lib/symfony/runtime/RuntimeInterface.php b/lib/symfony/runtime/RuntimeInterface.php
new file mode 100644
index 0000000000..f151757e98
--- /dev/null
+++ b/lib/symfony/runtime/RuntimeInterface.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Runtime;
+
+/**
+ * Enables decoupling applications from global state.
+ *
+ * @author Nicolas Grekas
+ */
+interface RuntimeInterface
+{
+ /**
+ * Returns a resolver that should compute the arguments of a callable.
+ *
+ * The callable itself should return an object that represents the application to pass to the getRunner() method.
+ */
+ public function getResolver(callable $callable, ?\ReflectionFunction $reflector = null): ResolverInterface;
+
+ /**
+ * Returns a callable that knows how to run the passed object and that returns its exit status as int.
+ *
+ * The passed object is typically created by calling ResolverInterface::resolve().
+ */
+ public function getRunner(?object $application): RunnerInterface;
+}
diff --git a/lib/symfony/runtime/SymfonyRuntime.php b/lib/symfony/runtime/SymfonyRuntime.php
new file mode 100644
index 0000000000..a6e1cba733
--- /dev/null
+++ b/lib/symfony/runtime/SymfonyRuntime.php
@@ -0,0 +1,235 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Runtime;
+
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\ArgvInput;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\ConsoleOutput;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Dotenv\Dotenv;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+use Symfony\Component\Runtime\Internal\MissingDotenv;
+use Symfony\Component\Runtime\Internal\SymfonyErrorHandler;
+use Symfony\Component\Runtime\Runner\Symfony\ConsoleApplicationRunner;
+use Symfony\Component\Runtime\Runner\Symfony\HttpKernelRunner;
+use Symfony\Component\Runtime\Runner\Symfony\ResponseRunner;
+
+// Help opcache.preload discover always-needed symbols
+class_exists(MissingDotenv::class, false) || class_exists(Dotenv::class) || class_exists(MissingDotenv::class);
+
+/**
+ * Knows the basic conventions to run Symfony apps.
+ *
+ * In addition to the options managed by GenericRuntime, it accepts the following options:
+ * - "env" to define the name of the environment the app runs in;
+ * - "disable_dotenv" to disable looking for .env files;
+ * - "dotenv_path" to define the path of dot-env files - defaults to ".env";
+ * - "prod_envs" to define the names of the production envs - defaults to ["prod"];
+ * - "test_envs" to define the names of the test envs - defaults to ["test"];
+ * - "use_putenv" to tell Dotenv to set env vars using putenv() (NOT RECOMMENDED.)
+ * - "dotenv_overload" to tell Dotenv to override existing vars
+ *
+ * When the "debug" / "env" options are not defined, they will fallback to the
+ * "APP_DEBUG" / "APP_ENV" environment variables, and to the "--env|-e" / "--no-debug"
+ * command line arguments if "symfony/console" is installed.
+ *
+ * When the "symfony/dotenv" component is installed, .env files are loaded.
+ * When "symfony/error-handler" is installed, it is registered in debug mode.
+ *
+ * On top of the base arguments provided by GenericRuntime,
+ * this runtime can feed the app-callable with arguments of type:
+ * - Request from "symfony/http-foundation" if the component is installed;
+ * - Application, Command, InputInterface and/or OutputInterface
+ * from "symfony/console" if the component is installed.
+ *
+ * This runtime can handle app-callables that return instances of either:
+ * - HttpKernelInterface,
+ * - Response,
+ * - Application,
+ * - Command,
+ * - int|string|null as handled by GenericRuntime.
+ *
+ * @author Nicolas Grekas