From f8148489101f76f67c3a00315826b381cd0a255f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Auswo=CC=88ger?= Date: Tue, 31 Oct 2023 23:48:40 +0100 Subject: [PATCH 01/16] Fix memory limit in PhpSubprocess unit test --- src/Symfony/Component/Process/Tests/PhpSubprocessTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Process/Tests/PhpSubprocessTest.php b/src/Symfony/Component/Process/Tests/PhpSubprocessTest.php index 56b32ae805429..3406e649bda52 100644 --- a/src/Symfony/Component/Process/Tests/PhpSubprocessTest.php +++ b/src/Symfony/Component/Process/Tests/PhpSubprocessTest.php @@ -47,7 +47,7 @@ public static function subprocessProvider(): \Generator yield 'Process does ignore dynamic memory_limit' => [ 'Process', self::getRandomMemoryLimit(), - self::getCurrentMemoryLimit(), + self::getDefaultMemoryLimit(), ]; yield 'PhpSubprocess does not ignore dynamic memory_limit' => [ @@ -57,16 +57,16 @@ public static function subprocessProvider(): \Generator ]; } - private static function getCurrentMemoryLimit(): string + private static function getDefaultMemoryLimit(): string { - return trim(\ini_get('memory_limit')); + return trim(ini_get_all()['memory_limit']['global_value']); } private static function getRandomMemoryLimit(): string { $memoryLimit = 123; // Take something that's really unlikely to be configured on a user system. - while (($formatted = $memoryLimit.'M') === self::getCurrentMemoryLimit()) { + while (($formatted = $memoryLimit.'M') === self::getDefaultMemoryLimit()) { ++$memoryLimit; } From c85d118e4c023bbd66c2d78247f501f21ec1a74f Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 15 Nov 2023 17:45:06 +0100 Subject: [PATCH 02/16] Update Github template for 7.1 --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index ff72a1cc13a5c..be833bfec1a14 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,6 +1,6 @@ | Q | A | ------------- | --- -| Branch? | 6.4 for features / 5.4 or 6.3 for bug fixes +| Branch? | 7.1 for features / 5.4, 6.3, 6.4, or 7.0 for bug fixes | Bug fix? | yes/no | New feature? | yes/no | Deprecations? | yes/no From c5bd6766285d49a6bf1083ca5a8693f6d8572690 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 16 Nov 2023 20:26:22 +0100 Subject: [PATCH 03/16] [VarExporter] Fix handling mangled property names returned by __sleep() --- src/Symfony/Component/VarExporter/Internal/Exporter.php | 8 +++----- .../Component/VarExporter/Tests/Fixtures/var-on-sleep.php | 8 ++++++++ .../Component/VarExporter/Tests/VarExporterTest.php | 6 +++++- src/Symfony/Component/VarExporter/VarExporter.php | 2 +- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Component/VarExporter/Internal/Exporter.php b/src/Symfony/Component/VarExporter/Internal/Exporter.php index b5ee88c0ff091..51c29e45f1998 100644 --- a/src/Symfony/Component/VarExporter/Internal/Exporter.php +++ b/src/Symfony/Component/VarExporter/Internal/Exporter.php @@ -157,11 +157,11 @@ public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount $n = substr($n, 1 + $i); } if (null !== $sleep) { - if (!isset($sleep[$n]) || ($i && $c !== $class)) { + if (!isset($sleep[$name]) && (!isset($sleep[$n]) || ($i && $c !== $class))) { unset($arrayValue[$name]); continue; } - $sleep[$n] = false; + unset($sleep[$name], $sleep[$n]); } if (!\array_key_exists($name, $proto) || $proto[$name] !== $v || "\x00Error\x00trace" === $name || "\x00Exception\x00trace" === $name) { $properties[$c][$n] = $v; @@ -169,9 +169,7 @@ public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount } if ($sleep) { foreach ($sleep as $n => $v) { - if (false !== $v) { - trigger_error(sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $n), \E_USER_NOTICE); - } + trigger_error(sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $n), \E_USER_NOTICE); } } if (method_exists($class, '__unserialize')) { diff --git a/src/Symfony/Component/VarExporter/Tests/Fixtures/var-on-sleep.php b/src/Symfony/Component/VarExporter/Tests/Fixtures/var-on-sleep.php index 9fd44bd59092d..a0d7e3f8cb21e 100644 --- a/src/Symfony/Component/VarExporter/Tests/Fixtures/var-on-sleep.php +++ b/src/Symfony/Component/VarExporter/Tests/Fixtures/var-on-sleep.php @@ -11,6 +11,14 @@ 'night', ], ], + 'Symfony\\Component\\VarExporter\\Tests\\GoodNight' => [ + 'foo' => [ + 'afternoon', + ], + 'bar' => [ + 'morning', + ], + ], ], $o[0], [] diff --git a/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php b/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php index 8e67d02d76b1e..7d99328966af5 100644 --- a/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php +++ b/src/Symfony/Component/VarExporter/Tests/VarExporterTest.php @@ -349,17 +349,21 @@ public function setFlags($flags): void class GoodNight { public $good; + protected $foo; + private $bar; public function __construct() { unset($this->good); + $this->foo = 'afternoon'; + $this->bar = 'morning'; } public function __sleep(): array { $this->good = 'night'; - return ['good']; + return ['good', 'foo', "\0*\0foo", "\0".__CLASS__."\0bar"]; } } diff --git a/src/Symfony/Component/VarExporter/VarExporter.php b/src/Symfony/Component/VarExporter/VarExporter.php index 85813378137df..59d5e8631da1a 100644 --- a/src/Symfony/Component/VarExporter/VarExporter.php +++ b/src/Symfony/Component/VarExporter/VarExporter.php @@ -83,7 +83,7 @@ public static function export($value, bool &$isStaticValue = null, array &$found ksort($states); $wakeups = [null]; - foreach ($states as $k => $v) { + foreach ($states as $v) { if (\is_array($v)) { $wakeups[-$v[0]] = $v[1]; } else { From 790fb389b3064f9b4fb615c3bb19c771d7f276bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Fri, 17 Nov 2023 15:11:02 +0100 Subject: [PATCH 04/16] [DomCrawler] Revert "bug #52579 UriResolver support path with colons" --- src/Symfony/Component/DomCrawler/Tests/UriResolverTest.php | 4 +--- src/Symfony/Component/DomCrawler/UriResolver.php | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/DomCrawler/Tests/UriResolverTest.php b/src/Symfony/Component/DomCrawler/Tests/UriResolverTest.php index f5ca403a61a4a..ab98cb52cbeeb 100644 --- a/src/Symfony/Component/DomCrawler/Tests/UriResolverTest.php +++ b/src/Symfony/Component/DomCrawler/Tests/UriResolverTest.php @@ -85,9 +85,7 @@ public static function provideResolverTests() ['foo', 'http://localhost?bar=1', 'http://localhost/foo'], ['foo', 'http://localhost#bar', 'http://localhost/foo'], - ['foo:1', 'http://localhost', 'http://localhost/foo:1'], - ['/bar:1', 'http://localhost', 'http://localhost/bar:1'], - ['foo/bar:1', 'http://localhost', 'http://localhost/foo/bar:1'], + ['http://', 'http://localhost', 'http://'], ]; } } diff --git a/src/Symfony/Component/DomCrawler/UriResolver.php b/src/Symfony/Component/DomCrawler/UriResolver.php index 903696ba8fecd..5ff2245284c67 100644 --- a/src/Symfony/Component/DomCrawler/UriResolver.php +++ b/src/Symfony/Component/DomCrawler/UriResolver.php @@ -33,7 +33,7 @@ public static function resolve(string $uri, ?string $baseUri): string $uri = trim($uri); // absolute URL? - if (\is_string(parse_url($uri, \PHP_URL_SCHEME))) { + if (null !== parse_url($uri, \PHP_URL_SCHEME)) { return $uri; } From ab1d7fdcf85f6fa7cc9f6205b39b66f51eb6d0c3 Mon Sep 17 00:00:00 2001 From: Jeroeny Date: Fri, 13 Oct 2023 11:43:42 +0200 Subject: [PATCH 05/16] Don't lose checkpoint state when lock is acquired from another --- .../Scheduler/Generator/Checkpoint.php | 10 ++++---- .../Tests/Generator/CheckpointTest.php | 24 +++++++++++++++++++ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Component/Scheduler/Generator/Checkpoint.php b/src/Symfony/Component/Scheduler/Generator/Checkpoint.php index 0b0e7ae1e5e8e..fcc6dd810ccd5 100644 --- a/src/Symfony/Component/Scheduler/Generator/Checkpoint.php +++ b/src/Symfony/Component/Scheduler/Generator/Checkpoint.php @@ -31,20 +31,18 @@ public function __construct( public function acquire(\DateTimeImmutable $now): bool { if ($this->lock && !$this->lock->acquire()) { - // Reset local state if a Lock is acquired by another Worker. + // Reset local state if a Lock is acquired by another Worker and state is not shared through cache. $this->reset = true; return false; } - if ($this->reset) { - $this->reset = false; - $this->save($now, -1); - } - if ($this->cache) { [$this->time, $this->index, $this->from] = $this->cache->get($this->name, fn () => [$now, -1, $now]) + [2 => $now]; $this->save($this->time, $this->index); + } elseif ($this->reset) { + $this->reset = false; + $this->save($now, -1); } $this->time ??= $now; diff --git a/src/Symfony/Component/Scheduler/Tests/Generator/CheckpointTest.php b/src/Symfony/Component/Scheduler/Tests/Generator/CheckpointTest.php index b64d3bda3f9fa..34d3c1e9b5816 100644 --- a/src/Symfony/Component/Scheduler/Tests/Generator/CheckpointTest.php +++ b/src/Symfony/Component/Scheduler/Tests/Generator/CheckpointTest.php @@ -190,6 +190,30 @@ public function testWithLockResetStateAfterLockedAcquiring() $this->assertFalse($concurrentLock->isAcquired()); } + public function testWithLockResetStateAfterLockedAcquiringCache() + { + $concurrentLock = new Lock(new Key('locked'), $store = new InMemoryStore(), autoRelease: false); + $concurrentLock->acquire(); + $this->assertTrue($concurrentLock->isAcquired()); + + $lock = new Lock(new Key('locked'), $store, autoRelease: false); + $checkpoint = new Checkpoint('locked', $lock, $cache = new ArrayAdapter()); + $now = new \DateTimeImmutable('2020-02-20 20:20:20Z'); + + $checkpoint->save($savedTime = $now->modify('-2 min'), $savedIndex = 0); + $checkpoint->acquire($now->modify('-1 min')); + + $two = new Checkpoint('locked', $lock, $cache); + + $concurrentLock->release(); + + $this->assertTrue($two->acquire($now)); + $this->assertEquals($savedTime, $two->time()); + $this->assertEquals($savedIndex, $two->index()); + $this->assertTrue($lock->isAcquired()); + $this->assertFalse($concurrentLock->isAcquired()); + } + public function testWithLockKeepLock() { $lock = new Lock(new Key('lock'), new InMemoryStore()); From bf6186fb123c50296cb5732227a28b22608dbeea Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 15 Nov 2023 22:27:42 +0100 Subject: [PATCH 06/16] register the virtual request stack together with common profiling services The debug.php file is never loaded when the Stopwatch component is not installed. However, the virtual request stack is always valuable as soon as the profiling feature is enabled. --- .../FrameworkBundle/Console/Application.php | 6 ++++ .../VirtualRequestStackPass.php | 34 +++++++++++++++++++ .../FrameworkBundle/FrameworkBundle.php | 2 ++ .../Resources/config/debug.php | 5 --- .../Resources/config/profiling.php | 5 +++ 5 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/DependencyInjection/VirtualRequestStackPass.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php index 1fe1e57feb1be..b8bae8fc22286 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php @@ -108,6 +108,12 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI } (new SymfonyStyle($input, $output))->warning('The "--profile" option needs the Stopwatch component. Try running "composer require symfony/stopwatch".'); + } elseif (!$container->has('.virtual_request_stack')) { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + (new SymfonyStyle($input, $output))->warning('The "--profile" option needs the profiler integration. Try enabling the "framework.profiler" option.'); } else { $command = new TraceableCommand($command, $container->get('debug.stopwatch')); diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/VirtualRequestStackPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/VirtualRequestStackPass.php new file mode 100644 index 0000000000000..158054fe4dc12 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/VirtualRequestStackPass.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\Bundle\FrameworkBundle\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +class VirtualRequestStackPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if ($container->has('.virtual_request_stack')) { + return; + } + + if ($container->hasDefinition('debug.event_dispatcher')) { + $container->getDefinition('debug.event_dispatcher')->replaceArgument(3, new Reference('request_stack', ContainerBuilder::NULL_ON_INVALID_REFERENCE)); + } + + if ($container->hasDefinition('debug.log_processor')) { + $container->getDefinition('debug.log_processor')->replaceArgument(0, new Reference('request_stack')); + } + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index 47564d0fe46f5..4ba30a5c8eeeb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -21,6 +21,7 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TestServiceContainerRealRefPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TestServiceContainerWeakRefPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\UnusedTagsPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\VirtualRequestStackPass; use Symfony\Component\Cache\Adapter\ApcuAdapter; use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Adapter\ChainAdapter; @@ -184,6 +185,7 @@ public function build(ContainerBuilder $container) $container->addCompilerPass(new RemoveUnusedSessionMarshallingHandlerPass()); // must be registered after MonologBundle's LoggerChannelPass $container->addCompilerPass(new ErrorLoggerCompilerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32); + $container->addCompilerPass(new VirtualRequestStackPass()); if ($container->getParameter('kernel.debug')) { $container->addCompilerPass(new AddDebugLogProcessorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 2); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.php index d9341e16f7727..5c426653daeca 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.php @@ -15,7 +15,6 @@ use Symfony\Component\HttpKernel\Controller\TraceableArgumentResolver; use Symfony\Component\HttpKernel\Controller\TraceableControllerResolver; use Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher; -use Symfony\Component\HttpKernel\Debug\VirtualRequestStack; return static function (ContainerConfigurator $container) { $container->services() @@ -47,9 +46,5 @@ ->set('argument_resolver.not_tagged_controller', NotTaggedControllerValueResolver::class) ->args([abstract_arg('Controller argument, set in FrameworkExtension')]) ->tag('controller.argument_value_resolver', ['priority' => -200]) - - ->set('.virtual_request_stack', VirtualRequestStack::class) - ->args([service('request_stack')]) - ->public() ; }; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/profiling.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/profiling.php index c4b9f68a3b88a..ec764d8375665 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/profiling.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/profiling.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; use Symfony\Bundle\FrameworkBundle\EventListener\ConsoleProfilerListener; +use Symfony\Component\HttpKernel\Debug\VirtualRequestStack; use Symfony\Component\HttpKernel\EventListener\ProfilerListener; use Symfony\Component\HttpKernel\Profiler\FileProfilerStorage; use Symfony\Component\HttpKernel\Profiler\Profiler; @@ -45,5 +46,9 @@ service('router'), ]) ->tag('kernel.event_subscriber') + + ->set('.virtual_request_stack', VirtualRequestStack::class) + ->args([service('request_stack')]) + ->public() ; }; From 9ec9ead9870cca40bb498038014a7aac697b738b Mon Sep 17 00:00:00 2001 From: Frederik Schmitt Date: Fri, 17 Nov 2023 17:49:38 +0100 Subject: [PATCH 07/16] Add hint that changing input arguments has no effect --- src/Symfony/Component/Console/Event/ConsoleCommandEvent.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Console/Event/ConsoleCommandEvent.php b/src/Symfony/Component/Console/Event/ConsoleCommandEvent.php index 08bd18fd1f32f..1b4f9f9b1392d 100644 --- a/src/Symfony/Component/Console/Event/ConsoleCommandEvent.php +++ b/src/Symfony/Component/Console/Event/ConsoleCommandEvent.php @@ -12,7 +12,10 @@ namespace Symfony\Component\Console\Event; /** - * Allows to do things before the command is executed, like skipping the command or changing the input. + * Allows to do things before the command is executed, like skipping the command or executing code before the command is + * going to be executed. + * + * Changing the input arguments will have no effect. * * @author Fabien Potencier */ From cd6a28ce9243fb17b56e53164b417867d59f0048 Mon Sep 17 00:00:00 2001 From: paullallier <42591123+paullallier@users.noreply.github.com> Date: Thu, 9 Nov 2023 23:46:58 +0000 Subject: [PATCH 08/16] Add some more non-countable English nouns --- .../String/Inflector/EnglishInflector.php | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/Symfony/Component/String/Inflector/EnglishInflector.php b/src/Symfony/Component/String/Inflector/EnglishInflector.php index 5d16977e43b21..16efc53a82234 100644 --- a/src/Symfony/Component/String/Inflector/EnglishInflector.php +++ b/src/Symfony/Component/String/Inflector/EnglishInflector.php @@ -21,7 +21,7 @@ final class EnglishInflector implements InflectorInterface private const PLURAL_MAP = [ // First entry: plural suffix, reversed // Second entry: length of plural suffix - // Third entry: Whether the suffix may succeed a vocal + // Third entry: Whether the suffix may succeed a vowel // Fourth entry: Whether the suffix may succeed a consonant // Fifth entry: singular suffix, normal @@ -162,7 +162,7 @@ final class EnglishInflector implements InflectorInterface private const SINGULAR_MAP = [ // First entry: singular suffix, reversed // Second entry: length of singular suffix - // Third entry: Whether the suffix may succeed a vocal + // Third entry: Whether the suffix may succeed a vowel // Fourth entry: Whether the suffix may succeed a consonant // Fifth entry: plural suffix, normal @@ -343,15 +343,30 @@ final class EnglishInflector implements InflectorInterface // deer 'reed', + // equipment + 'tnempiuqe', + // feedback 'kcabdeef', // fish 'hsif', + // health + 'htlaeh', + + // history + 'yrotsih', + // info 'ofni', + // information + 'noitamrofni', + + // money + 'yenom', + // moose 'esoom', @@ -363,6 +378,9 @@ final class EnglishInflector implements InflectorInterface // species 'seiceps', + + // traffic + 'ciffart', ]; /** @@ -399,14 +417,14 @@ public function singularize(string $plural): array if ($j === $suffixLength) { // Is there any character preceding the suffix in the plural string? if ($j < $pluralLength) { - $nextIsVocal = false !== strpos('aeiou', $lowerPluralRev[$j]); + $nextIsVowel = false !== strpos('aeiou', $lowerPluralRev[$j]); - if (!$map[2] && $nextIsVocal) { - // suffix may not succeed a vocal but next char is one + if (!$map[2] && $nextIsVowel) { + // suffix may not succeed a vowel but next char is one break; } - if (!$map[3] && !$nextIsVocal) { + if (!$map[3] && !$nextIsVowel) { // suffix may not succeed a consonant but next char is one break; } @@ -479,14 +497,14 @@ public function pluralize(string $singular): array if ($j === $suffixLength) { // Is there any character preceding the suffix in the plural string? if ($j < $singularLength) { - $nextIsVocal = false !== strpos('aeiou', $lowerSingularRev[$j]); + $nextIsVowel = false !== strpos('aeiou', $lowerSingularRev[$j]); - if (!$map[2] && $nextIsVocal) { - // suffix may not succeed a vocal but next char is one + if (!$map[2] && $nextIsVowel) { + // suffix may not succeed a vowel but next char is one break; } - if (!$map[3] && !$nextIsVocal) { + if (!$map[3] && !$nextIsVowel) { // suffix may not succeed a consonant but next char is one break; } From 8f7c7aef8f5e87b0cd565f8046e70df8a5fb79ed Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Tue, 14 Nov 2023 05:54:27 +0100 Subject: [PATCH 09/16] [Serializer] Fix denormalize constructor arguments --- .../Normalizer/AbstractNormalizer.php | 29 ++++++++++++++----- .../Component/Serializer/Serializer.php | 14 ++++++++- .../Serializer/Tests/Fixtures/Php74Full.php | 2 +- .../ConstructorArgumentsTestTrait.php | 10 +++---- .../Serializer/Tests/SerializerTest.php | 21 +++++++++++++- 5 files changed, 61 insertions(+), 15 deletions(-) diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php index 5c88e4455e09c..d7512d62161e1 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php @@ -350,6 +350,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex $constructorParameters = $constructor->getParameters(); $missingConstructorArguments = []; $params = []; + $unsetKeys = []; foreach ($constructorParameters as $constructorParameter) { $paramName = $constructorParameter->name; $key = $this->nameConverter ? $this->nameConverter->normalize($paramName, $class, $format, $context) : $paramName; @@ -368,18 +369,17 @@ protected function instantiateObject(array &$data, string $class, array &$contex } $params = array_merge($params, $variadicParameters); - unset($data[$key]); + $unsetKeys[] = $key; } } elseif ($allowed && !$ignored && (isset($data[$key]) || \array_key_exists($key, $data))) { $parameterData = $data[$key]; if (null === $parameterData && $constructorParameter->allowsNull()) { $params[] = null; - // Don't run set for a parameter passed to the constructor - unset($data[$key]); + $unsetKeys[] = $key; + continue; } - // Don't run set for a parameter passed to the constructor try { $params[] = $this->denormalizeParameter($reflectionClass, $constructorParameter, $paramName, $parameterData, $context, $format); } catch (NotNormalizableValueException $exception) { @@ -390,7 +390,8 @@ protected function instantiateObject(array &$data, string $class, array &$contex $context['not_normalizable_value_exceptions'][] = $exception; $params[] = $parameterData; } - unset($data[$key]); + + $unsetKeys[] = $key; } elseif (\array_key_exists($key, $context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class] ?? [])) { $params[] = $context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key]; } elseif (\array_key_exists($key, $this->defaultContext[self::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class] ?? [])) { @@ -421,11 +422,25 @@ protected function instantiateObject(array &$data, string $class, array &$contex } if (!$constructor->isConstructor()) { - return $constructor->invokeArgs(null, $params); + $instance = $constructor->invokeArgs(null, $params); + + // do not set a parameter that has been set in the constructor + foreach ($unsetKeys as $key) { + unset($data[$key]); + } + + return $instance; } try { - return $reflectionClass->newInstanceArgs($params); + $instance = $reflectionClass->newInstanceArgs($params); + + // do not set a parameter that has been set in the constructor + foreach ($unsetKeys as $key) { + unset($data[$key]); + } + + return $instance; } catch (\TypeError $e) { if (!isset($context['not_normalizable_value_exceptions'])) { throw $e; diff --git a/src/Symfony/Component/Serializer/Serializer.php b/src/Symfony/Component/Serializer/Serializer.php index c0a49a8089db0..3b9943740e49b 100644 --- a/src/Symfony/Component/Serializer/Serializer.php +++ b/src/Symfony/Component/Serializer/Serializer.php @@ -228,8 +228,20 @@ public function denormalize($data, string $type, string $format = null, array $c $context['not_normalizable_value_exceptions'] = []; $errors = &$context['not_normalizable_value_exceptions']; $denormalized = $normalizer->denormalize($data, $type, $format, $context); + if ($errors) { - throw new PartialDenormalizationException($denormalized, $errors); + // merge errors so that one path has only one error + $uniqueErrors = []; + foreach ($errors as $error) { + if (null === $error->getPath()) { + $uniqueErrors[] = $error; + continue; + } + + $uniqueErrors[$error->getPath()] = $uniqueErrors[$error->getPath()] ?? $error; + } + + throw new PartialDenormalizationException($denormalized, array_values($uniqueErrors)); } return $denormalized; diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/Php74Full.php b/src/Symfony/Component/Serializer/Tests/Fixtures/Php74Full.php index 5491c4cacb009..0fe8ffd15ca9d 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/Php74Full.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/Php74Full.php @@ -46,7 +46,7 @@ public function __construct($constructorArgument) final class Php74FullWithTypedConstructor { - public function __construct(float $something) + public function __construct(float $something, bool $somethingElse) { } } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php b/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php index f7e18241c7210..821c537326940 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php @@ -58,7 +58,7 @@ public function testMetadataAwareNameConvertorWithNotSerializedConstructorParame public function testConstructorWithMissingData() { $data = [ - 'foo' => 10, + 'bar' => 10, ]; $normalizer = $this->getDenormalizerForConstructArguments(); @@ -66,15 +66,15 @@ public function testConstructorWithMissingData() $normalizer->denormalize($data, ConstructorArgumentsObject::class); self::fail(sprintf('Failed asserting that exception of type "%s" is thrown.', MissingConstructorArgumentsException::class)); } catch (MissingConstructorArgumentsException $e) { - self::assertSame(sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires the following parameters to be present : "$bar", "$baz".', ConstructorArgumentsObject::class), $e->getMessage()); - self::assertSame(['bar', 'baz'], $e->getMissingConstructorArguments()); + self::assertSame(sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires the following parameters to be present : "$foo", "$baz".', ConstructorArgumentsObject::class), $e->getMessage()); + self::assertSame(['foo', 'baz'], $e->getMissingConstructorArguments()); } } public function testExceptionsAreCollectedForConstructorWithMissingData() { $data = [ - 'foo' => 10, + 'bar' => 10, ]; $exceptions = []; @@ -85,7 +85,7 @@ public function testExceptionsAreCollectedForConstructorWithMissingData() ]); self::assertCount(2, $exceptions); - self::assertSame('Failed to create object because the class misses the "bar" property.', $exceptions[0]->getMessage()); + self::assertSame('Failed to create object because the class misses the "foo" property.', $exceptions[0]->getMessage()); self::assertSame('Failed to create object because the class misses the "baz" property.', $exceptions[1]->getMessage()); } } diff --git a/src/Symfony/Component/Serializer/Tests/SerializerTest.php b/src/Symfony/Component/Serializer/Tests/SerializerTest.php index fdd98d0be5b5a..ab4bbe908ab29 100644 --- a/src/Symfony/Component/Serializer/Tests/SerializerTest.php +++ b/src/Symfony/Component/Serializer/Tests/SerializerTest.php @@ -868,7 +868,8 @@ public function testCollectDenormalizationErrors(?ClassMetadataFactory $classMet ], "php74FullWithConstructor": {}, "php74FullWithTypedConstructor": { - "something": "not a float" + "something": "not a float", + "somethingElse": "not a bool" }, "dummyMessage": { }, @@ -1032,6 +1033,24 @@ public function testCollectDenormalizationErrors(?ClassMetadataFactory $classMet 'useMessageForUser' => false, 'message' => 'The type of the "something" attribute for class "Symfony\Component\Serializer\Tests\Fixtures\Php74FullWithTypedConstructor" must be one of "float" ("string" given).', ], + [ + 'currentType' => 'string', + 'expectedTypes' => [ + 'float', + ], + 'path' => 'php74FullWithTypedConstructor.something', + 'useMessageForUser' => false, + 'message' => 'The type of the "something" attribute for class "Symfony\Component\Serializer\Tests\Fixtures\Php74FullWithTypedConstructor" must be one of "float" ("string" given).', + ], + [ + 'currentType' => 'string', + 'expectedTypes' => [ + 'bool', + ], + 'path' => 'php74FullWithTypedConstructor.somethingElse', + 'useMessageForUser' => false, + 'message' => 'The type of the "somethingElse" attribute for class "Symfony\Component\Serializer\Tests\Fixtures\Php74FullWithTypedConstructor" must be one of "bool" ("string" given).', + ], $classMetadataFactory ? [ 'currentType' => 'null', From 74eeadafb6311150a38ab4dc4fd8a0d3b70dc434 Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Mon, 20 Nov 2023 09:08:48 +0100 Subject: [PATCH 10/16] [Serializer] Remove wrong final tags --- src/Symfony/Component/Serializer/Attribute/Context.php | 2 -- src/Symfony/Component/Serializer/Attribute/DiscriminatorMap.php | 2 -- src/Symfony/Component/Serializer/Attribute/Groups.php | 2 -- src/Symfony/Component/Serializer/Attribute/Ignore.php | 2 -- src/Symfony/Component/Serializer/Attribute/MaxDepth.php | 2 -- src/Symfony/Component/Serializer/Attribute/SerializedName.php | 2 -- src/Symfony/Component/Serializer/Attribute/SerializedPath.php | 2 -- 7 files changed, 14 deletions(-) diff --git a/src/Symfony/Component/Serializer/Attribute/Context.php b/src/Symfony/Component/Serializer/Attribute/Context.php index d62c43046a2e3..baa958839780d 100644 --- a/src/Symfony/Component/Serializer/Attribute/Context.php +++ b/src/Symfony/Component/Serializer/Attribute/Context.php @@ -21,8 +21,6 @@ * @Target({"PROPERTY", "METHOD"}) * * @author Maxime Steinhausser - * - * @final since Symfony 6.4 */ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] class Context diff --git a/src/Symfony/Component/Serializer/Attribute/DiscriminatorMap.php b/src/Symfony/Component/Serializer/Attribute/DiscriminatorMap.php index 7bba10ab036a6..4c1f23722eb52 100644 --- a/src/Symfony/Component/Serializer/Attribute/DiscriminatorMap.php +++ b/src/Symfony/Component/Serializer/Attribute/DiscriminatorMap.php @@ -21,8 +21,6 @@ * @Target({"CLASS"}) * * @author Samuel Roze - * - * @final since Symfony 6.4 */ #[\Attribute(\Attribute::TARGET_CLASS)] class DiscriminatorMap diff --git a/src/Symfony/Component/Serializer/Attribute/Groups.php b/src/Symfony/Component/Serializer/Attribute/Groups.php index 386a2ce00bd2d..9a351910aed57 100644 --- a/src/Symfony/Component/Serializer/Attribute/Groups.php +++ b/src/Symfony/Component/Serializer/Attribute/Groups.php @@ -21,8 +21,6 @@ * @Target({"PROPERTY", "METHOD", "CLASS"}) * * @author Kévin Dunglas - * - * @final since Symfony 6.4 */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_CLASS)] class Groups diff --git a/src/Symfony/Component/Serializer/Attribute/Ignore.php b/src/Symfony/Component/Serializer/Attribute/Ignore.php index 577a77084326b..2e04a45ffb077 100644 --- a/src/Symfony/Component/Serializer/Attribute/Ignore.php +++ b/src/Symfony/Component/Serializer/Attribute/Ignore.php @@ -18,8 +18,6 @@ * @Target({"PROPERTY", "METHOD"}) * * @author Kévin Dunglas - * - * @final since Symfony 6.4 */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] class Ignore diff --git a/src/Symfony/Component/Serializer/Attribute/MaxDepth.php b/src/Symfony/Component/Serializer/Attribute/MaxDepth.php index bbd190ccfaa9b..3ecfcb993755d 100644 --- a/src/Symfony/Component/Serializer/Attribute/MaxDepth.php +++ b/src/Symfony/Component/Serializer/Attribute/MaxDepth.php @@ -21,8 +21,6 @@ * @Target({"PROPERTY", "METHOD"}) * * @author Kévin Dunglas - * - * @final since Symfony 6.4 */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] class MaxDepth diff --git a/src/Symfony/Component/Serializer/Attribute/SerializedName.php b/src/Symfony/Component/Serializer/Attribute/SerializedName.php index 2afd8d9ed1f8e..e278990c9cbe2 100644 --- a/src/Symfony/Component/Serializer/Attribute/SerializedName.php +++ b/src/Symfony/Component/Serializer/Attribute/SerializedName.php @@ -21,8 +21,6 @@ * @Target({"PROPERTY", "METHOD"}) * * @author Fabien Bourigault - * - * @final since Symfony 6.4 */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] class SerializedName diff --git a/src/Symfony/Component/Serializer/Attribute/SerializedPath.php b/src/Symfony/Component/Serializer/Attribute/SerializedPath.php index eed46c5185a61..18b84ed536269 100644 --- a/src/Symfony/Component/Serializer/Attribute/SerializedPath.php +++ b/src/Symfony/Component/Serializer/Attribute/SerializedPath.php @@ -23,8 +23,6 @@ * @Target({"PROPERTY", "METHOD"}) * * @author Tobias Bönner - * - * @final since Symfony 6.4 */ #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] class SerializedPath From 5010257b95474eb59b828f1e71b37d95864d27ad Mon Sep 17 00:00:00 2001 From: Tomasz Kowalczyk Date: Tue, 14 Nov 2023 10:17:37 +0100 Subject: [PATCH 11/16] [Validator] updated Turkish translation --- .../Resources/translations/validators.tr.xlf | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.tr.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.tr.xlf index 715137d5890a9..092eb0dd161f6 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.tr.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.tr.xlf @@ -402,6 +402,30 @@ The value of the netmask should be between {{ min }} and {{ max }}. Netmask'in değeri {{ min }} ve {{ max }} arasında olmaldır. + + The filename is too long. It should have {{ filename_max_length }} character or less.|The filename is too long. It should have {{ filename_max_length }} characters or less. + Dosya adı çok uzun. {{ filename_max_length }} karakter veya daha az olmalıdır.|Dosya adı çok uzun. {{ filename_max_length }} karakter veya daha az olmalıdır. + + + The password strength is too low. Please use a stronger password. + Şifre gücü çok düşük. Lütfen daha güçlü bir şifre kullanın. + + + This value contains characters that are not allowed by the current restriction-level. + Bu değer, geçerli kısıtlama düzeyinin izin vermediği karakterleri içeriyor. + + + Using invisible characters is not allowed. + Görünmez karakterlerin kullanılması yasaktır. + + + Mixing numbers from different scripts is not allowed. + Farklı kodlardaki sayıların karıştırılması yasaktır. + + + Using hidden overlay characters is not allowed. + Gizli kaplama karakterlerinin kullanılması yasaktır. + From c88d49ea1bb7d3d6bc19e8bfe017e8a08f5ddc49 Mon Sep 17 00:00:00 2001 From: Ivan Nemets Date: Fri, 17 Nov 2023 15:19:12 +0300 Subject: [PATCH 12/16] [Serializer] Fix denormalizing date intervals having both weeks and days --- .../Normalizer/DateIntervalNormalizer.php | 4 ++++ .../Normalizer/DateIntervalNormalizerTest.php | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/Symfony/Component/Serializer/Normalizer/DateIntervalNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/DateIntervalNormalizer.php index c60ffdbc85047..93128d35bdcd4 100644 --- a/src/Symfony/Component/Serializer/Normalizer/DateIntervalNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/DateIntervalNormalizer.php @@ -128,6 +128,10 @@ public function supportsDenormalization($data, string $type, string $format = nu private function isISO8601(string $string): bool { + if (\PHP_VERSION_ID >= 80000) { + return preg_match('/^[\-+]?P(?=\w*(?:\d|%\w))(?:\d+Y|%[yY]Y)?(?:\d+M|%[mM]M)?(?:\d+W|%[wW]W)?(?:\d+D|%[dD]D)?(?:T(?:\d+H|[hH]H)?(?:\d+M|[iI]M)?(?:\d+S|[sS]S)?)?$/', $string); + } + return preg_match('/^[\-+]?P(?=\w*(?:\d|%\w))(?:\d+Y|%[yY]Y)?(?:\d+M|%[mM]M)?(?:(?:\d+D|%[dD]D)|(?:\d+W|%[wW]W))?(?:T(?:\d+H|[hH]H)?(?:\d+M|[iI]M)?(?:\d+S|[sS]S)?)?$/', $string); } } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/DateIntervalNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/DateIntervalNormalizerTest.php index fe59e098bdbf5..375702bcafe78 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/DateIntervalNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/DateIntervalNormalizerTest.php @@ -119,6 +119,21 @@ public function testDenormalizeIntervalsWithOmittedPartsBeingZero() $this->assertDateIntervalEquals($this->getInterval('P0Y0M0DT12H34M0S'), $normalizer->denormalize('PT12H34M', \DateInterval::class)); } + /** + * Since PHP 8.0 DateInterval::construct supports periods containing both D and W period designators. + * + * @requires PHP 8 + */ + public function testDenormalizeIntervalWithBothWeeksAndDays() + { + $input = 'P1W1D'; + $interval = $this->normalizer->denormalize($input, \DateInterval::class, null, [ + DateIntervalNormalizer::FORMAT_KEY => '%rP%yY%mM%wW%dDT%hH%iM%sS', + ]); + $this->assertDateIntervalEquals($this->getInterval($input), $interval); + $this->assertSame(8, $interval->d); + } + public function testDenormalizeExpectsString() { $this->expectException(NotNormalizableValueException::class); From 6084ed418757196a97cb5ff048382cf2c90b8544 Mon Sep 17 00:00:00 2001 From: shubhalgupta <107386458+shubhalgupta@users.noreply.github.com> Date: Mon, 23 Oct 2023 02:56:55 +0530 Subject: [PATCH 13/16] Added missing translations in turkish and updated validators.tr.xlf --- .../Validator/Resources/translations/validators.tr.xlf | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.tr.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.tr.xlf index 092eb0dd161f6..09e841565504f 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.tr.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.tr.xlf @@ -404,7 +404,7 @@ The filename is too long. It should have {{ filename_max_length }} character or less.|The filename is too long. It should have {{ filename_max_length }} characters or less. - Dosya adı çok uzun. {{ filename_max_length }} karakter veya daha az olmalıdır.|Dosya adı çok uzun. {{ filename_max_length }} karakter veya daha az olmalıdır. + Dosya adı çok uzun. {{ filename_max_length }} karakter veya daha az olmalıdır. The password strength is too low. Please use a stronger password. @@ -412,19 +412,19 @@ This value contains characters that are not allowed by the current restriction-level. - Bu değer, geçerli kısıtlama düzeyinin izin vermediği karakterleri içeriyor. + Bu değer, mevcut kısıtlama seviyesi tarafından izin verilmeyen karakterler içeriyor. Using invisible characters is not allowed. - Görünmez karakterlerin kullanılması yasaktır. + Görünmez karakterlerin kullanılması izin verilmez. Mixing numbers from different scripts is not allowed. - Farklı kodlardaki sayıların karıştırılması yasaktır. + Farklı yazı türlerinden sayıların karıştırılması izin verilmez. Using hidden overlay characters is not allowed. - Gizli kaplama karakterlerinin kullanılması yasaktır. + Gizli üstü kaplama karakterlerinin kullanılması izin verilmez. From 7ad9db0c6a838cd421ac452e6b841730447a6d21 Mon Sep 17 00:00:00 2001 From: shubhalgupta <107386458+shubhalgupta@users.noreply.github.com> Date: Mon, 23 Oct 2023 01:46:34 +0530 Subject: [PATCH 14/16] Closes #51936-Added Missing translations for Czech (cs) in validators.cs.xlf file --- .../Resources/translations/validators.cs.xlf | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.cs.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.cs.xlf index 75410192190ef..d53747e2aef70 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.cs.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.cs.xlf @@ -402,6 +402,30 @@ The value of the netmask should be between {{ min }} and {{ max }}. Hodnota masky sítě musí být mezi {{ min }} a {{ max }}. + + The filename is too long. It should have {{ filename_max_length }} character or less.|The filename is too long. It should have {{ filename_max_length }} characters or less. + Název souboru je příliš dlouhý. Měl by obsahovat {{ filename_max_length }} znak nebo méně.|Název souboru je příliš dlouhý. Měl by obsahovat {{ filename_max_length }} znaky nebo méně.|Název souboru je příliš dlouhý. Měl by obsahovat {{ filename_max_length }} znaků nebo méně. + + + The password strength is too low. Please use a stronger password. + Síla hesla je příliš nízká. Použijte silnější heslo, prosím. + + + This value contains characters that are not allowed by the current restriction-level. + Tato hodnota obsahuje znaky, které nejsou povoleny aktuální úrovní omezení. + + + Using invisible characters is not allowed. + Používání neviditelných znaků není povoleno. + + + Mixing numbers from different scripts is not allowed. + Kombinování čísel z různých písem není povoleno. + + + Using hidden overlay characters is not allowed. + Použití skrytých překrývajících znaků není povoleno. + From cb5d832e4e9a58da300ce0a737613950114bfa34 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Tue, 7 Nov 2023 16:18:53 +0100 Subject: [PATCH 15/16] [Cache][Lock] Fix PDO store not creating table + add tests --- .../Cache/Adapter/DoctrineDbalAdapter.php | 3 +- .../Component/Cache/Adapter/PdoAdapter.php | 21 ++++++++- .../Tests/Adapter/DoctrineDbalAdapterTest.php | 43 +++++++++++++------ .../Cache/Tests/Adapter/PdoAdapterTest.php | 43 ++++++++++++++----- .../Storage/Handler/SessionHandlerFactory.php | 1 + src/Symfony/Component/Lock/Store/PdoStore.php | 33 ++++++++++++-- .../Tests/Store/DoctrineDbalStoreTest.php | 36 +++++++++++++--- .../Lock/Tests/Store/PdoStoreTest.php | 38 +++++++++++++--- .../Transport/PostgreSqlConnection.php | 1 + 9 files changed, 177 insertions(+), 42 deletions(-) diff --git a/src/Symfony/Component/Cache/Adapter/DoctrineDbalAdapter.php b/src/Symfony/Component/Cache/Adapter/DoctrineDbalAdapter.php index eacf8eb9bcc88..0e061d26ea1d8 100644 --- a/src/Symfony/Component/Cache/Adapter/DoctrineDbalAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/DoctrineDbalAdapter.php @@ -420,7 +420,8 @@ private function getServerVersion(): string return $this->serverVersion; } - $conn = $this->conn->getWrappedConnection(); + // The condition should be removed once support for DBAL <3.3 is dropped + $conn = method_exists($this->conn, 'getNativeConnection') ? $this->conn->getNativeConnection() : $this->conn->getWrappedConnection(); if ($conn instanceof ServerInfoAwareConnection) { return $this->serverVersion = $conn->getServerVersion(); } diff --git a/src/Symfony/Component/Cache/Adapter/PdoAdapter.php b/src/Symfony/Component/Cache/Adapter/PdoAdapter.php index b339defeb30fd..ba0aaa15853bf 100644 --- a/src/Symfony/Component/Cache/Adapter/PdoAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/PdoAdapter.php @@ -507,7 +507,7 @@ protected function doSave(array $values, int $lifetime) try { $stmt = $conn->prepare($sql); } catch (\PDOException $e) { - if (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) { + if ($this->isTableMissing($e) && (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true))) { $this->createTable(); } $stmt = $conn->prepare($sql); @@ -542,7 +542,7 @@ protected function doSave(array $values, int $lifetime) try { $stmt->execute(); } catch (\PDOException $e) { - if (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) { + if ($this->isTableMissing($e) && (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true))) { $this->createTable(); } $stmt->execute(); @@ -596,4 +596,21 @@ private function getServerVersion(): string return $this->serverVersion; } + + private function isTableMissing(\PDOException $exception): bool + { + $driver = $this->driver; + $code = $exception->getCode(); + + switch (true) { + case 'pgsql' === $driver && '42P01' === $code: + case 'sqlite' === $driver && str_contains($exception->getMessage(), 'no such table:'): + case 'oci' === $driver && 942 === $code: + case 'sqlsrv' === $driver && 208 === $code: + case 'mysql' === $driver && 1146 === $code: + return true; + default: + return false; + } + } } diff --git a/src/Symfony/Component/Cache/Tests/Adapter/DoctrineDbalAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/DoctrineDbalAdapterTest.php index 79299ecd61506..63a567a069e08 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/DoctrineDbalAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/DoctrineDbalAdapterTest.php @@ -18,12 +18,13 @@ use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; use Doctrine\DBAL\Schema\Schema; -use PHPUnit\Framework\SkippedTestSuiteError; use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\Cache\Adapter\DoctrineDbalAdapter; use Symfony\Component\Cache\Tests\Fixtures\DriverWrapper; /** + * @requires extension pdo_sqlite + * * @group time-sensitive */ class DoctrineDbalAdapterTest extends AdapterTestCase @@ -32,10 +33,6 @@ class DoctrineDbalAdapterTest extends AdapterTestCase public static function setUpBeforeClass(): void { - if (!\extension_loaded('pdo_sqlite')) { - throw new SkippedTestSuiteError('Extension pdo_sqlite required.'); - } - self::$dbFile = tempnam(sys_get_temp_dir(), 'sf_sqlite_cache'); } @@ -107,13 +104,12 @@ public function testConfigureSchemaTableExists() } /** - * @dataProvider provideDsn + * @dataProvider provideDsnWithSQLite */ - public function testDsn(string $dsn, string $file = null) + public function testDsnWithSQLite(string $dsn, string $file = null) { try { $pool = new DoctrineDbalAdapter($dsn); - $pool->createTable(); $item = $pool->getItem('key'); $item->set('value'); @@ -125,12 +121,35 @@ public function testDsn(string $dsn, string $file = null) } } - public static function provideDsn() + public static function provideDsnWithSQLite() { $dbFile = tempnam(sys_get_temp_dir(), 'sf_sqlite_cache'); - yield ['sqlite://localhost/'.$dbFile.'1', $dbFile.'1']; - yield ['sqlite3:///'.$dbFile.'3', $dbFile.'3']; - yield ['sqlite://localhost/:memory:']; + yield 'SQLite file' => ['sqlite://localhost/'.$dbFile.'1', $dbFile.'1']; + yield 'SQLite3 file' => ['sqlite3:///'.$dbFile.'3', $dbFile.'3']; + yield 'SQLite in memory' => ['sqlite://localhost/:memory:']; + } + + /** + * @requires extension pdo_pgsql + * + * @group integration + */ + public function testDsnWithPostgreSQL() + { + if (!$host = getenv('POSTGRES_HOST')) { + $this->markTestSkipped('Missing POSTGRES_HOST env variable'); + } + + try { + $pool = new DoctrineDbalAdapter('pgsql://postgres:password@'.$host); + + $item = $pool->getItem('key'); + $item->set('value'); + $this->assertTrue($pool->save($item)); + } finally { + $pdo = new \PDO('pgsql:host='.$host.';user=postgres;password=password'); + $pdo->exec('DROP TABLE IF EXISTS cache_items'); + } } protected function isPruned(DoctrineDbalAdapter $cache, string $name): bool diff --git a/src/Symfony/Component/Cache/Tests/Adapter/PdoAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/PdoAdapterTest.php index 6bed9285c59ac..b630e9eebea3a 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/PdoAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/PdoAdapterTest.php @@ -11,11 +11,12 @@ namespace Symfony\Component\Cache\Tests\Adapter; -use PHPUnit\Framework\SkippedTestSuiteError; use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\Cache\Adapter\PdoAdapter; /** + * @requires extension pdo_sqlite + * * @group time-sensitive */ class PdoAdapterTest extends AdapterTestCase @@ -24,10 +25,6 @@ class PdoAdapterTest extends AdapterTestCase public static function setUpBeforeClass(): void { - if (!\extension_loaded('pdo_sqlite')) { - throw new SkippedTestSuiteError('Extension pdo_sqlite required.'); - } - self::$dbFile = tempnam(sys_get_temp_dir(), 'sf_sqlite_cache'); $pool = new PdoAdapter('sqlite:'.self::$dbFile); @@ -71,13 +68,12 @@ public function testCleanupExpiredItems() } /** - * @dataProvider provideDsn + * @dataProvider provideDsnSQLite */ - public function testDsn(string $dsn, string $file = null) + public function testDsnWithSQLite(string $dsn, string $file = null) { try { $pool = new PdoAdapter($dsn); - $pool->createTable(); $item = $pool->getItem('key'); $item->set('value'); @@ -89,11 +85,36 @@ public function testDsn(string $dsn, string $file = null) } } - public static function provideDsn() + public static function provideDsnSQLite() { $dbFile = tempnam(sys_get_temp_dir(), 'sf_sqlite_cache'); - yield ['sqlite:'.$dbFile.'2', $dbFile.'2']; - yield ['sqlite::memory:']; + yield 'SQLite file' => ['sqlite:'.$dbFile.'2', $dbFile.'2']; + yield 'SQLite in memory' => ['sqlite::memory:']; + } + + /** + * @requires extension pdo_pgsql + * + * @group integration + */ + public function testDsnWithPostgreSQL() + { + if (!$host = getenv('POSTGRES_HOST')) { + $this->markTestSkipped('Missing POSTGRES_HOST env variable'); + } + + $dsn = 'pgsql:host='.$host.';user=postgres;password=password'; + + try { + $pool = new PdoAdapter($dsn); + + $item = $pool->getItem('key'); + $item->set('value'); + $this->assertTrue($pool->save($item)); + } finally { + $pdo = new \PDO($dsn); + $pdo->exec('DROP TABLE IF EXISTS cache_items'); + } } protected function isPruned(PdoAdapter $cache, string $name): bool diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php index 14454d0b80b47..76e4373f83809 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php @@ -82,6 +82,7 @@ public static function createHandler($connection): AbstractSessionHandler } $connection = DriverManager::getConnection($params, $config); + // The condition should be removed once support for DBAL <3.3 is dropped $connection = method_exists($connection, 'getNativeConnection') ? $connection->getNativeConnection() : $connection->getWrappedConnection(); // no break; diff --git a/src/Symfony/Component/Lock/Store/PdoStore.php b/src/Symfony/Component/Lock/Store/PdoStore.php index 3eeb83b572e9c..159b9287d6852 100644 --- a/src/Symfony/Component/Lock/Store/PdoStore.php +++ b/src/Symfony/Component/Lock/Store/PdoStore.php @@ -115,7 +115,7 @@ public function save(Key $key) try { $stmt = $conn->prepare($sql); } catch (\PDOException $e) { - if (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) { + if ($this->isTableMissing($e) && (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true))) { $this->createTable(); } $stmt = $conn->prepare($sql); @@ -127,8 +127,18 @@ public function save(Key $key) try { $stmt->execute(); } catch (\PDOException $e) { - // the lock is already acquired. It could be us. Let's try to put off. - $this->putOffExpiration($key, $this->initialTtl); + if ($this->isTableMissing($e) && (!$conn->inTransaction() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true))) { + $this->createTable(); + + try { + $stmt->execute(); + } catch (\PDOException $e) { + $this->putOffExpiration($key, $this->initialTtl); + } + } else { + // the lock is already acquired. It could be us. Let's try to put off. + $this->putOffExpiration($key, $this->initialTtl); + } } $this->randomlyPrune(); @@ -316,4 +326,21 @@ private function getCurrentTimestampStatement(): string return (string) time(); } } + + private function isTableMissing(\PDOException $exception): bool + { + $driver = $this->getDriver(); + $code = $exception->getCode(); + + switch (true) { + case 'pgsql' === $driver && '42P01' === $code: + case 'sqlite' === $driver && str_contains($exception->getMessage(), 'no such table:'): + case 'oci' === $driver && 942 === $code: + case 'sqlsrv' === $driver && 208 === $code: + case 'mysql' === $driver && 1146 === $code: + return true; + default: + return false; + } + } } diff --git a/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalStoreTest.php index 9f8c2aac6be3b..e037341e5f05f 100644 --- a/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalStoreTest.php +++ b/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalStoreTest.php @@ -79,9 +79,9 @@ public function testAbortAfterExpiration() } /** - * @dataProvider provideDsn + * @dataProvider provideDsnWithSQLite */ - public function testDsn(string $dsn, string $file = null) + public function testDsnWithSQLite(string $dsn, string $file = null) { $key = new Key(uniqid(__METHOD__, true)); @@ -97,12 +97,36 @@ public function testDsn(string $dsn, string $file = null) } } - public static function provideDsn() + public static function provideDsnWithSQLite() { $dbFile = tempnam(sys_get_temp_dir(), 'sf_sqlite_cache'); - yield ['sqlite://localhost/'.$dbFile.'1', $dbFile.'1']; - yield ['sqlite3:///'.$dbFile.'3', $dbFile.'3']; - yield ['sqlite://localhost/:memory:']; + yield 'SQLite file' => ['sqlite://localhost/'.$dbFile.'1', $dbFile.'1']; + yield 'SQLite3 file' => ['sqlite3:///'.$dbFile.'3', $dbFile.'3']; + yield 'SQLite in memory' => ['sqlite://localhost/:memory:']; + } + + /** + * @requires extension pdo_pgsql + * + * @group integration + */ + public function testDsnWithPostgreSQL() + { + if (!$host = getenv('POSTGRES_HOST')) { + $this->markTestSkipped('Missing POSTGRES_HOST env variable'); + } + + $key = new Key(uniqid(__METHOD__, true)); + + try { + $store = new DoctrineDbalStore('pgsql://postgres:password@'.$host); + + $store->save($key); + $this->assertTrue($store->exists($key)); + } finally { + $pdo = new \PDO('pgsql:host='.$host.';user=postgres;password=password'); + $pdo->exec('DROP TABLE IF EXISTS lock_keys'); + } } /** diff --git a/src/Symfony/Component/Lock/Tests/Store/PdoStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/PdoStoreTest.php index 0dc4eb015bafd..d2960d08bf274 100644 --- a/src/Symfony/Component/Lock/Tests/Store/PdoStoreTest.php +++ b/src/Symfony/Component/Lock/Tests/Store/PdoStoreTest.php @@ -20,8 +20,6 @@ * @author Jérémy Derussé * * @requires extension pdo_sqlite - * - * @group integration */ class PdoStoreTest extends AbstractStoreTestCase { @@ -78,9 +76,9 @@ public function testInvalidTtlConstruct() } /** - * @dataProvider provideDsn + * @dataProvider provideDsnWithSQLite */ - public function testDsn(string $dsn, string $file = null) + public function testDsnWithSQLite(string $dsn, string $file = null) { $key = new Key(uniqid(__METHOD__, true)); @@ -96,10 +94,36 @@ public function testDsn(string $dsn, string $file = null) } } - public static function provideDsn() + public static function provideDsnWithSQLite() { $dbFile = tempnam(sys_get_temp_dir(), 'sf_sqlite_cache'); - yield ['sqlite:'.$dbFile.'2', $dbFile.'2']; - yield ['sqlite::memory:']; + yield 'SQLite file' => ['sqlite:'.$dbFile.'2', $dbFile.'2']; + yield 'SQLite in memory' => ['sqlite::memory:']; + } + + /** + * @requires extension pdo_pgsql + * + * @group integration + */ + public function testDsnWithPostgreSQL() + { + if (!$host = getenv('POSTGRES_HOST')) { + $this->markTestSkipped('Missing POSTGRES_HOST env variable'); + } + + $key = new Key(uniqid(__METHOD__, true)); + + $dsn = 'pgsql:host='.$host.';user=postgres;password=password'; + + try { + $store = new PdoStore($dsn); + + $store->save($key); + $this->assertTrue($store->exists($key)); + } finally { + $pdo = new \PDO($dsn); + $pdo->exec('DROP TABLE IF EXISTS lock_keys'); + } } } diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/PostgreSqlConnection.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/PostgreSqlConnection.php index 3691a9383f293..4d0c3f422971d 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/PostgreSqlConnection.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/PostgreSqlConnection.php @@ -64,6 +64,7 @@ public function get(): ?array // https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS $this->executeStatement(sprintf('LISTEN "%s"', $this->configuration['table_name'])); + // The condition should be removed once support for DBAL <3.3 is dropped if (method_exists($this->driverConnection, 'getNativeConnection')) { $wrappedConnection = $this->driverConnection->getNativeConnection(); } else { From 1f560bc2e01c4702fda48cc8c4c05e645e5521f8 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 20 Nov 2023 17:10:54 +0100 Subject: [PATCH 16/16] [FrameworkBundle] Add TemplateController to the list of allowed controllers for fragments --- src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php index 817ed07c18a09..6710dabdab3e5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php @@ -13,6 +13,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver; +use Symfony\Bundle\FrameworkBundle\Controller\TemplateController; use Symfony\Component\HttpKernel\Controller\ArgumentResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\BackedEnumValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DateTimeValueResolver; @@ -41,7 +42,7 @@ service('service_container'), service('logger')->ignoreOnInvalid(), ]) - ->call('allowControllers', [[AbstractController::class]]) + ->call('allowControllers', [[AbstractController::class, TemplateController::class]]) ->tag('monolog.logger', ['channel' => 'request']) ->set('argument_metadata_factory', ArgumentMetadataFactory::class)