diff --git a/.github/expected-missing-return-types.diff b/.github/expected-missing-return-types.diff index 2f04b85fcaf69..caf2a7e780ecc 100644 --- a/.github/expected-missing-return-types.diff +++ b/.github/expected-missing-return-types.diff @@ -626,7 +626,7 @@ index fedc30d06e..bb934fe1f6 100644 { if (!$container->hasDefinition('session.marshalling_handler')) { diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php -index 09f272daa9..1fd0608bde 100644 +index aed3b13404..91a6804fea 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php @@ -25,5 +25,5 @@ class TestServiceContainerRealRefPass implements CompilerPassInterface @@ -648,7 +648,7 @@ index 6e7669a710..27517d34ae 100644 { if (!$container->hasDefinition('test.private_services_locator')) { diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php -index a76b84ad83..7e71517edb 100644 +index b04516410f..fc5a085a37 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php @@ -109,5 +109,5 @@ class UnusedTagsPass implements CompilerPassInterface @@ -670,17 +670,17 @@ index bda9ca9515..c0d1f91339 100644 { if (!$container->hasParameter('workflow.has_guard_listeners')) { diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php -index d209d9902b..92b9f508c4 100644 +index 981a865672..b7e456ba54 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php -@@ -211,5 +211,5 @@ class FrameworkExtension extends Extension +@@ -212,5 +212,5 @@ class FrameworkExtension extends Extension * @throws LogicException */ - public function load(array $configs, ContainerBuilder $container) + public function load(array $configs, ContainerBuilder $container): void { $loader = new PhpFileLoader($container, new FileLocator(\dirname(__DIR__).'/Resources/config')); -@@ -2890,5 +2890,5 @@ class FrameworkExtension extends Extension +@@ -2901,5 +2901,5 @@ class FrameworkExtension extends Extension * @return void */ - public static function registerRateLimiter(ContainerBuilder $container, string $name, array $limiterConfig) @@ -688,7 +688,7 @@ index d209d9902b..92b9f508c4 100644 { trigger_deprecation('symfony/framework-bundle', '6.2', 'The "%s()" method is deprecated.', __METHOD__); diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php -index ffb96a23e5..e7f77881c8 100644 +index 339b28e83c..448402d81e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -94,5 +94,5 @@ class FrameworkBundle extends Bundle @@ -697,8 +697,8 @@ index ffb96a23e5..e7f77881c8 100644 - public function boot() + public function boot(): void { - $handler = ErrorHandler::register(null, false); -@@ -111,5 +111,5 @@ class FrameworkBundle extends Bundle + $_ENV['DOCTRINE_DEPRECATIONS'] = $_SERVER['DOCTRINE_DEPRECATIONS'] ??= 'trigger'; +@@ -119,5 +119,5 @@ class FrameworkBundle extends Bundle * @return void */ - public function build(ContainerBuilder $container) @@ -999,7 +999,7 @@ index a2c5815e4b..1c9721ccc6 100644 + public function addConfiguration(NodeDefinition $builder): void; } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php -index 37978b285f..ca1f5ae517 100644 +index ddb1d3cc92..18203f5104 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -82,5 +82,5 @@ class SecurityExtension extends Extension implements PrependExtensionInterface @@ -2702,7 +2702,7 @@ index 84dbef950c..5a38c8c28a 100644 { self::$formatters ??= self::initPlaceholderFormatters(); diff --git a/src/Symfony/Component/Console/Helper/QuestionHelper.php b/src/Symfony/Component/Console/Helper/QuestionHelper.php -index d83bee17ef..548be8f3ce 100644 +index f32813c6c5..93cb49a1be 100644 --- a/src/Symfony/Component/Console/Helper/QuestionHelper.php +++ b/src/Symfony/Component/Console/Helper/QuestionHelper.php @@ -93,5 +93,5 @@ class QuestionHelper extends Helper @@ -2712,14 +2712,14 @@ index d83bee17ef..548be8f3ce 100644 + public static function disableStty(): void { self::$stty = false; -@@ -183,5 +183,5 @@ class QuestionHelper extends Helper +@@ -194,5 +194,5 @@ class QuestionHelper extends Helper * @return void */ - protected function writePrompt(OutputInterface $output, Question $question) + protected function writePrompt(OutputInterface $output, Question $question): void { $message = $question->getQuestion(); -@@ -221,5 +221,5 @@ class QuestionHelper extends Helper +@@ -232,5 +232,5 @@ class QuestionHelper extends Helper * @return void */ - protected function writeError(OutputInterface $output, \Exception $error) @@ -2851,7 +2851,7 @@ index 0f5617cd17..bdd5dd264f 100644 { $this->stream = $stream; diff --git a/src/Symfony/Component/Console/Input/InputArgument.php b/src/Symfony/Component/Console/Input/InputArgument.php -index 0db0c7709b..5fbca28f99 100644 +index 5cb151488d..2dc276a372 100644 --- a/src/Symfony/Component/Console/Input/InputArgument.php +++ b/src/Symfony/Component/Console/Input/InputArgument.php @@ -96,5 +96,5 @@ class InputArgument @@ -3680,16 +3680,16 @@ index 3f070dcc0c..aa0e5186bf 100644 { foreach ($container->findTaggedServiceIds('auto_alias') as $serviceId => $tags) { diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php -index 9d9250b9f9..4f78f4ddeb 100644 +index f84a7faff0..fb0bb08933 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php -@@ -62,5 +62,5 @@ class AutowirePass extends AbstractRecursivePass +@@ -71,5 +71,5 @@ class AutowirePass extends AbstractRecursivePass * @return void */ - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { - try { + $this->defaultArgument->bag = $container->getParameterBag(); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckCircularReferencesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckCircularReferencesPass.php index 1fb8935c3e..1cfccaa671 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/CheckCircularReferencesPass.php @@ -3759,7 +3759,7 @@ index 2ad4a048ba..719267be1e 100644 + public function process(ContainerBuilder $container): void; } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/DecoratorServicePass.php b/src/Symfony/Component/DependencyInjection/Compiler/DecoratorServicePass.php -index c38bfa7744..10d999f093 100644 +index d05878fe85..dc75d9ef10 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/DecoratorServicePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/DecoratorServicePass.php @@ -31,5 +31,5 @@ class DecoratorServicePass extends AbstractRecursivePass @@ -3769,6 +3769,17 @@ index c38bfa7744..10d999f093 100644 + public function process(ContainerBuilder $container): void { $definitions = new \SplPriorityQueue(); +diff --git a/src/Symfony/Component/DependencyInjection/Compiler/DefinitionErrorExceptionPass.php b/src/Symfony/Component/DependencyInjection/Compiler/DefinitionErrorExceptionPass.php +index b6ee648160..5682941b11 100644 +--- a/src/Symfony/Component/DependencyInjection/Compiler/DefinitionErrorExceptionPass.php ++++ b/src/Symfony/Component/DependencyInjection/Compiler/DefinitionErrorExceptionPass.php +@@ -33,5 +33,5 @@ class DefinitionErrorExceptionPass extends AbstractRecursivePass + * @return void + */ +- public function process(ContainerBuilder $container) ++ public function process(ContainerBuilder $container): void + { + try { diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ExtensionCompilerPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ExtensionCompilerPass.php index 953b7f942e..96912701e5 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ExtensionCompilerPass.php @@ -4148,101 +4159,101 @@ index ac67b468c5..bc1e395810 100644 { if (1 > \func_num_args()) { diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php -index 44df682ebc..e5957c6f57 100644 +index a7a9c145aa..bd16e937ca 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php -@@ -179,5 +179,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface +@@ -177,5 +177,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * @return void */ - public function setResourceTracking(bool $track) + public function setResourceTracking(bool $track): void { $this->trackResources = $track; -@@ -197,5 +197,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface +@@ -195,5 +195,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * @return void */ - public function setProxyInstantiator(InstantiatorInterface $proxyInstantiator) + public function setProxyInstantiator(InstantiatorInterface $proxyInstantiator): void { $this->proxyInstantiator = $proxyInstantiator; -@@ -205,5 +205,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface +@@ -203,5 +203,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * @return void */ - public function registerExtension(ExtensionInterface $extension) + public function registerExtension(ExtensionInterface $extension): void { $this->extensions[$extension->getAlias()] = $extension; -@@ -487,5 +487,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface +@@ -485,5 +485,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * @throws BadMethodCallException When this ContainerBuilder is compiled */ - public function set(string $id, ?object $service) + public function set(string $id, ?object $service): void { if ($this->isCompiled() && (isset($this->definitions[$id]) && !$this->definitions[$id]->isSynthetic())) { -@@ -504,5 +504,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface +@@ -502,5 +502,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * @return void */ - public function removeDefinition(string $id) + public function removeDefinition(string $id): void { if (isset($this->definitions[$id])) { -@@ -616,5 +616,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface +@@ -614,5 +614,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * @throws BadMethodCallException When this ContainerBuilder is compiled */ - public function merge(self $container) + public function merge(self $container): void { if ($this->isCompiled()) { -@@ -708,5 +708,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface +@@ -706,5 +706,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * @return void */ - public function prependExtensionConfig(string $name, array $config) + public function prependExtensionConfig(string $name, array $config): void { if (!isset($this->extensionConfigs[$name])) { -@@ -752,5 +752,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface +@@ -750,5 +750,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * @return void */ - public function compile(bool $resolveEnvPlaceholders = false) + public function compile(bool $resolveEnvPlaceholders = false): void { $compiler = $this->getCompiler(); -@@ -816,5 +816,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface +@@ -814,5 +814,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * @return void */ - public function addAliases(array $aliases) + public function addAliases(array $aliases): void { foreach ($aliases as $alias => $id) { -@@ -830,5 +830,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface +@@ -828,5 +828,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * @return void */ - public function setAliases(array $aliases) + public function setAliases(array $aliases): void { $this->aliasDefinitions = []; -@@ -864,5 +864,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface +@@ -862,5 +862,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * @return void */ - public function removeAlias(string $alias) + public function removeAlias(string $alias): void { if (isset($this->aliasDefinitions[$alias])) { -@@ -926,5 +926,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface +@@ -924,5 +924,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * @return void */ - public function addDefinitions(array $definitions) + public function addDefinitions(array $definitions): void { foreach ($definitions as $id => $definition) { -@@ -940,5 +940,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface +@@ -938,5 +938,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * @return void */ - public function setDefinitions(array $definitions) + public function setDefinitions(array $definitions): void { $this->definitions = []; -@@ -1338,5 +1338,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface +@@ -1330,5 +1330,5 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * @return void */ - public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) @@ -4698,7 +4709,7 @@ index f610b014a0..9458751c28 100644 + abstract protected function setNode(\DOMElement $node): void; } diff --git a/src/Symfony/Component/DomCrawler/Crawler.php b/src/Symfony/Component/DomCrawler/Crawler.php -index 59eec3068c..b750e80938 100644 +index 274aeee5fc..ccf37dae8b 100644 --- a/src/Symfony/Component/DomCrawler/Crawler.php +++ b/src/Symfony/Component/DomCrawler/Crawler.php @@ -96,5 +96,5 @@ class Crawler implements \Countable, \IteratorAggregate @@ -5336,7 +5347,7 @@ index 241725b9c5..420932897f 100644 { $token = $this->current; diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php -index d3280bee51..913c309358 100644 +index a379ce1863..e2035fb337 100644 --- a/src/Symfony/Component/Filesystem/Filesystem.php +++ b/src/Symfony/Component/Filesystem/Filesystem.php @@ -37,5 +37,5 @@ class Filesystem @@ -5423,7 +5434,7 @@ index d3280bee51..913c309358 100644 + public function dumpFile(string $filename, $content): void { if (\is_array($content)) { -@@ -698,5 +698,5 @@ class Filesystem +@@ -704,5 +704,5 @@ class Filesystem * @throws IOException If the file is not writable */ - public function appendToFile(string $filename, $content/* , bool $lock = false */) @@ -7319,7 +7330,7 @@ index 472437e465..1dfe39146b 100644 { if ($this->client instanceof ResetInterface) { diff --git a/src/Symfony/Component/HttpClient/HttpClientTrait.php b/src/Symfony/Component/HttpClient/HttpClientTrait.php -index 48782153b4..f2bbcf288d 100644 +index 3364d32acd..1b990e5a5a 100644 --- a/src/Symfony/Component/HttpClient/HttpClientTrait.php +++ b/src/Symfony/Component/HttpClient/HttpClientTrait.php @@ -679,5 +679,5 @@ trait HttpClientTrait @@ -7484,7 +7495,7 @@ index 9d7012de35..545339d4ef 100644 { unset($this->parameters[$key]); diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php -index 633b4a63e2..b69b8cc723 100644 +index 0bef6f8d70..ca99fd9dad 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -269,5 +269,5 @@ class Request @@ -7571,14 +7582,14 @@ index 633b4a63e2..b69b8cc723 100644 + public function setLocale(string $locale): void { $this->setPhpDefaultLocale($this->locale = $locale); -@@ -1736,5 +1736,5 @@ class Request +@@ -1756,5 +1756,5 @@ class Request * @return string */ - protected function prepareRequestUri() + protected function prepareRequestUri(): string { $requestUri = ''; -@@ -1907,5 +1907,5 @@ class Request +@@ -1927,5 +1927,5 @@ class Request * @return void */ - protected static function initializeFormats() @@ -7987,7 +7998,7 @@ index 534883d2d2..1240e36f74 100644 /** diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php -index 65452a5207..ce0357e36b 100644 +index a40a7bc77b..170918d832 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php @@ -242,5 +242,5 @@ class PdoSessionHandler extends AbstractSessionHandler @@ -8957,7 +8968,7 @@ index 0f3630e7fe..ddf77b8a19 100644 { return <<<'EOF' diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php -index f9a969b659..e8d115c435 100644 +index 9d4c5f22b3..4030ab8b12 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -107,5 +107,5 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl @@ -9613,45 +9624,45 @@ index 9087874c29..5cfd87c4ad 100644 { foreach ($this->stores as $store) { diff --git a/src/Symfony/Component/Lock/Store/DoctrineDbalPostgreSqlStore.php b/src/Symfony/Component/Lock/Store/DoctrineDbalPostgreSqlStore.php -index 4344a6f41f..c3ee27f8a5 100644 +index d87a7d5667..1409a85a83 100644 --- a/src/Symfony/Component/Lock/Store/DoctrineDbalPostgreSqlStore.php +++ b/src/Symfony/Component/Lock/Store/DoctrineDbalPostgreSqlStore.php -@@ -57,5 +57,5 @@ class DoctrineDbalPostgreSqlStore implements BlockingSharedLockStoreInterface, B +@@ -81,5 +81,5 @@ class DoctrineDbalPostgreSqlStore implements BlockingSharedLockStoreInterface, B * @return void */ - public function save(Key $key) + public function save(Key $key): void { // prevent concurrency within the same connection -@@ -92,5 +92,5 @@ class DoctrineDbalPostgreSqlStore implements BlockingSharedLockStoreInterface, B +@@ -116,5 +116,5 @@ class DoctrineDbalPostgreSqlStore implements BlockingSharedLockStoreInterface, B * @return void */ - public function saveRead(Key $key) + public function saveRead(Key $key): void { // prevent concurrency within the same connection -@@ -127,5 +127,5 @@ class DoctrineDbalPostgreSqlStore implements BlockingSharedLockStoreInterface, B +@@ -151,5 +151,5 @@ class DoctrineDbalPostgreSqlStore implements BlockingSharedLockStoreInterface, B * @return void */ - public function putOffExpiration(Key $key, float $ttl) + public function putOffExpiration(Key $key, float $ttl): void { // postgresql locks forever. -@@ -139,5 +139,5 @@ class DoctrineDbalPostgreSqlStore implements BlockingSharedLockStoreInterface, B +@@ -163,5 +163,5 @@ class DoctrineDbalPostgreSqlStore implements BlockingSharedLockStoreInterface, B * @return void */ - public function delete(Key $key) + public function delete(Key $key): void { // Prevent deleting locks own by an other key in the same connection -@@ -179,5 +179,5 @@ class DoctrineDbalPostgreSqlStore implements BlockingSharedLockStoreInterface, B +@@ -203,5 +203,5 @@ class DoctrineDbalPostgreSqlStore implements BlockingSharedLockStoreInterface, B * @return void */ - public function waitAndSave(Key $key) + public function waitAndSave(Key $key): void { // prevent concurrency within the same connection -@@ -205,5 +205,5 @@ class DoctrineDbalPostgreSqlStore implements BlockingSharedLockStoreInterface, B +@@ -229,5 +229,5 @@ class DoctrineDbalPostgreSqlStore implements BlockingSharedLockStoreInterface, B * @return void */ - public function waitAndSaveRead(Key $key) @@ -9659,24 +9670,24 @@ index 4344a6f41f..c3ee27f8a5 100644 { // prevent concurrency within the same connection diff --git a/src/Symfony/Component/Lock/Store/DoctrineDbalStore.php b/src/Symfony/Component/Lock/Store/DoctrineDbalStore.php -index 7c749bfec0..f24cc1d51f 100644 +index 0fce5d900c..b826c2866e 100644 --- a/src/Symfony/Component/Lock/Store/DoctrineDbalStore.php +++ b/src/Symfony/Component/Lock/Store/DoctrineDbalStore.php -@@ -76,5 +76,5 @@ class DoctrineDbalStore implements PersistingStoreInterface +@@ -100,5 +100,5 @@ class DoctrineDbalStore implements PersistingStoreInterface * @return void */ - public function save(Key $key) + public function save(Key $key): void { $key->reduceLifetime($this->initialTtl); -@@ -118,5 +118,5 @@ class DoctrineDbalStore implements PersistingStoreInterface +@@ -142,5 +142,5 @@ class DoctrineDbalStore implements PersistingStoreInterface * @return void */ - public function putOffExpiration(Key $key, $ttl) + public function putOffExpiration(Key $key, $ttl): void { if ($ttl < 1) { -@@ -152,5 +152,5 @@ class DoctrineDbalStore implements PersistingStoreInterface +@@ -176,5 +176,5 @@ class DoctrineDbalStore implements PersistingStoreInterface * @return void */ - public function delete(Key $key) @@ -10070,7 +10081,7 @@ index 86fa255015..08cbde055f 100644 { $busIds = []; diff --git a/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php b/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php -index 73c4013b6b..c8b52a3c0e 100644 +index ba775b0c17..1e1c50b517 100644 --- a/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php +++ b/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php @@ -52,5 +52,5 @@ class SendFailedMessageForRetryListener implements EventSubscriberInterface @@ -10904,7 +10915,7 @@ index bde72c0eb0..3d6813e1d4 100644 { if (!\is_array($config)) { diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/CompiledUrlMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/CompiledUrlMatcherDumper.php -index e92a5ea3d7..4a0af31349 100644 +index 0e740bdf6c..21b68e3600 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/CompiledUrlMatcherDumper.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/CompiledUrlMatcherDumper.php @@ -54,5 +54,5 @@ EOF; @@ -11905,7 +11916,7 @@ index 079b1e7a9e..e3cfe43e67 100644 { if (null !== $object = $this->extractObjectToPopulate($class, $context, self::OBJECT_TO_POPULATE)) { diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php -index a741bd3d9f..2896ccaef3 100644 +index 0dba039bc0..576445825e 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -143,5 +143,5 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer @@ -12096,31 +12107,31 @@ index 1835568745..2b98fc11d6 100644 { $this->serializer = $serializer; diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php -index ed5dcb97dd..7e0b71cd3b 100644 +index 78046229eb..bed7c33cec 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php -@@ -686,5 +686,5 @@ class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer +@@ -764,5 +764,5 @@ class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer } - protected function setAttributeValue(object $object, string $attribute, $value, string $format = null, array $context = []) + protected function setAttributeValue(object $object, string $attribute, $value, string $format = null, array $context = []): void { $object->$attribute = $value; -@@ -886,5 +886,5 @@ class AbstractObjectNormalizerWithMetadata extends AbstractObjectNormalizer +@@ -964,5 +964,5 @@ class AbstractObjectNormalizerWithMetadata extends AbstractObjectNormalizer } - protected function setAttributeValue(object $object, string $attribute, $value, string $format = null, array $context = []) + protected function setAttributeValue(object $object, string $attribute, $value, string $format = null, array $context = []): void { if (property_exists($object, $attribute)) { -@@ -1007,5 +1007,5 @@ class AbstractObjectNormalizerCollectionDummy extends AbstractObjectNormalizer +@@ -1085,5 +1085,5 @@ class AbstractObjectNormalizerCollectionDummy extends AbstractObjectNormalizer } - protected function setAttributeValue(object $object, string $attribute, $value, string $format = null, array $context = []) + protected function setAttributeValue(object $object, string $attribute, $value, string $format = null, array $context = []): void { $object->$attribute = $value; -@@ -1064,5 +1064,5 @@ class ArrayDenormalizerDummy implements DenormalizerInterface, SerializerAwareIn +@@ -1142,5 +1142,5 @@ class ArrayDenormalizerDummy implements DenormalizerInterface, SerializerAwareIn } - public function setSerializer(SerializerInterface $serializer) @@ -13587,7 +13598,7 @@ index 7c960ffee1..3952a146b0 100644 { if (!$constraint instanceof Valid) { diff --git a/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php b/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php -index 305a597665..3b14b2d078 100644 +index b78b39b42c..3246233ac3 100644 --- a/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php +++ b/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php @@ -70,5 +70,5 @@ interface ExecutionContextInterface @@ -13596,6 +13607,41 @@ index 305a597665..3b14b2d078 100644 - public function addViolation(string $message, array $params = []); + public function addViolation(string $message, array $params = []): void; + /** +@@ -127,5 +127,5 @@ interface ExecutionContextInterface + * @return void + */ +- public function setNode(mixed $value, ?object $object, MetadataInterface $metadata = null, string $propertyPath); ++ public function setNode(mixed $value, ?object $object, MetadataInterface $metadata = null, string $propertyPath): void; + + /** +@@ -136,5 +136,5 @@ interface ExecutionContextInterface + * @return void + */ +- public function setGroup(?string $group); ++ public function setGroup(?string $group): void; + + /** +@@ -143,5 +143,5 @@ interface ExecutionContextInterface + * @return void + */ +- public function setConstraint(Constraint $constraint); ++ public function setConstraint(Constraint $constraint): void; + + /** +@@ -154,5 +154,5 @@ interface ExecutionContextInterface + * @return void + */ +- public function markGroupAsValidated(string $cacheKey, string $groupHash); ++ public function markGroupAsValidated(string $cacheKey, string $groupHash): void; + + /** +@@ -173,5 +173,5 @@ interface ExecutionContextInterface + * @return void + */ +- public function markConstraintAsValidated(string $cacheKey, string $constraintHash); ++ public function markConstraintAsValidated(string $cacheKey, string $constraintHash): void; + /** diff --git a/src/Symfony/Component/Validator/DependencyInjection/AddAutoMappingConfigurationPass.php b/src/Symfony/Component/Validator/DependencyInjection/AddAutoMappingConfigurationPass.php index b1680b7cd4..805d093001 100644 @@ -13664,7 +13710,7 @@ index 2106e0a0ba..0b64f07c89 100644 { return $this->value; diff --git a/src/Symfony/Component/Validator/Mapping/ClassMetadata.php b/src/Symfony/Component/Validator/Mapping/ClassMetadata.php -index f5f084f2ed..b0f6545ab0 100644 +index 17f58466ce..aae693ea55 100644 --- a/src/Symfony/Component/Validator/Mapping/ClassMetadata.php +++ b/src/Symfony/Component/Validator/Mapping/ClassMetadata.php @@ -325,5 +325,5 @@ class ClassMetadata extends GenericMetadata implements ClassMetadataInterface @@ -13674,7 +13720,7 @@ index f5f084f2ed..b0f6545ab0 100644 + public function mergeConstraints(self $source): void { if ($source->isGroupSequenceProvider()) { -@@ -434,5 +434,5 @@ class ClassMetadata extends GenericMetadata implements ClassMetadataInterface +@@ -435,5 +435,5 @@ class ClassMetadata extends GenericMetadata implements ClassMetadataInterface * @throws GroupDefinitionException */ - public function setGroupSequenceProvider(bool $active) @@ -13905,7 +13951,7 @@ index d2d3fc1294..c28829f4e2 100644 { $a += [ diff --git a/src/Symfony/Component/VarDumper/Caster/DateCaster.php b/src/Symfony/Component/VarDumper/Caster/DateCaster.php -index 1394a78132..ee8471c88f 100644 +index a0cbddb76f..32fed323bb 100644 --- a/src/Symfony/Component/VarDumper/Caster/DateCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/DateCaster.php @@ -28,5 +28,5 @@ class DateCaster @@ -14611,7 +14657,7 @@ index 6a746b88e3..4c0940bd31 100644 { $this->minDepth = $minDepth; diff --git a/src/Symfony/Component/VarDumper/Cloner/Data.php b/src/Symfony/Component/VarDumper/Cloner/Data.php -index 3bf0a09cb5..8273ab227d 100644 +index 928f72d7a8..4d638d2cc1 100644 --- a/src/Symfony/Component/VarDumper/Cloner/Data.php +++ b/src/Symfony/Component/VarDumper/Cloner/Data.php @@ -266,5 +266,5 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate @@ -14671,7 +14717,7 @@ index 053a90972b..5876b02373 100644 { if (-1 !== $depth) { diff --git a/src/Symfony/Component/VarDumper/Dumper/CliDumper.php b/src/Symfony/Component/VarDumper/Dumper/CliDumper.php -index d9ce7ae4ed..ab6b70a682 100644 +index c155d4c79a..c40356579f 100644 --- a/src/Symfony/Component/VarDumper/Dumper/CliDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/CliDumper.php @@ -90,5 +90,5 @@ class CliDumper extends AbstractDumper @@ -14709,49 +14755,49 @@ index d9ce7ae4ed..ab6b70a682 100644 + public function dumpScalar(Cursor $cursor, string $type, string|int|float|bool|null $value): void { $this->dumpKey($cursor); -@@ -189,5 +189,5 @@ class CliDumper extends AbstractDumper +@@ -195,5 +195,5 @@ class CliDumper extends AbstractDumper * @return void */ - public function dumpString(Cursor $cursor, string $str, bool $bin, int $cut) + public function dumpString(Cursor $cursor, string $str, bool $bin, int $cut): void { $this->dumpKey($cursor); -@@ -280,5 +280,5 @@ class CliDumper extends AbstractDumper +@@ -286,5 +286,5 @@ class CliDumper extends AbstractDumper * @return void */ - public function enterHash(Cursor $cursor, int $type, string|int|null $class, bool $hasChild) + public function enterHash(Cursor $cursor, int $type, string|int|null $class, bool $hasChild): void { $this->colors ??= $this->supportsColors(); -@@ -319,5 +319,5 @@ class CliDumper extends AbstractDumper +@@ -325,5 +325,5 @@ class CliDumper extends AbstractDumper * @return void */ - public function leaveHash(Cursor $cursor, int $type, string|int|null $class, bool $hasChild, int $cut) + public function leaveHash(Cursor $cursor, int $type, string|int|null $class, bool $hasChild, int $cut): void { if (empty($cursor->attr['cut_hash'])) { -@@ -337,5 +337,5 @@ class CliDumper extends AbstractDumper +@@ -343,5 +343,5 @@ class CliDumper extends AbstractDumper * @return void */ - protected function dumpEllipsis(Cursor $cursor, bool $hasChild, int $cut) + protected function dumpEllipsis(Cursor $cursor, bool $hasChild, int $cut): void { if ($cut) { -@@ -355,5 +355,5 @@ class CliDumper extends AbstractDumper +@@ -361,5 +361,5 @@ class CliDumper extends AbstractDumper * @return void */ - protected function dumpKey(Cursor $cursor) + protected function dumpKey(Cursor $cursor): void { if (null !== $key = $cursor->hashKey) { -@@ -566,5 +566,5 @@ class CliDumper extends AbstractDumper +@@ -572,5 +572,5 @@ class CliDumper extends AbstractDumper * @return void */ - protected function dumpLine(int $depth, bool $endOfValue = false) + protected function dumpLine(int $depth, bool $endOfValue = false): void { if ($this->colors) { -@@ -577,5 +577,5 @@ class CliDumper extends AbstractDumper +@@ -583,5 +583,5 @@ class CliDumper extends AbstractDumper * @return void */ - protected function endValue(Cursor $cursor) @@ -14780,7 +14826,7 @@ index df05b6af57..024fb95d2f 100644 + public function dump(Data $data): ?string; } diff --git a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php -index 34ef644ad0..f09c1fbb4e 100644 +index 8a2570b2c4..87a4fdfdc3 100644 --- a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php @@ -86,5 +86,5 @@ class HtmlDumper extends CliDumper @@ -14825,28 +14871,28 @@ index 34ef644ad0..f09c1fbb4e 100644 + protected function getDumpHeader(): string { $this->headerIsDumped = $this->outputStream ?? $this->lineDumper; -@@ -790,5 +790,5 @@ EOHTML +@@ -788,5 +788,5 @@ EOHTML * @return void */ - public function dumpString(Cursor $cursor, string $str, bool $bin, int $cut) + public function dumpString(Cursor $cursor, string $str, bool $bin, int $cut): void { if ('' === $str && isset($cursor->attr['img-data'], $cursor->attr['content-type'])) { -@@ -808,5 +808,5 @@ EOHTML +@@ -806,5 +806,5 @@ EOHTML * @return void */ - public function enterHash(Cursor $cursor, int $type, string|int|null $class, bool $hasChild) + public function enterHash(Cursor $cursor, int $type, string|int|null $class, bool $hasChild): void { if (Cursor::HASH_OBJECT === $type) { -@@ -839,5 +839,5 @@ EOHTML +@@ -837,5 +837,5 @@ EOHTML * @return void */ - public function leaveHash(Cursor $cursor, int $type, string|int|null $class, bool $hasChild, int $cut) + public function leaveHash(Cursor $cursor, int $type, string|int|null $class, bool $hasChild, int $cut): void { $this->dumpEllipsis($cursor, $hasChild, $cut); -@@ -952,5 +952,5 @@ EOHTML +@@ -953,5 +953,5 @@ EOHTML * @return void */ - protected function dumpLine(int $depth, bool $endOfValue = false) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 8ced1a7434a50..17fd116ceaf5d 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -28,7 +28,7 @@ jobs: services: postgres: - image: postgres:9.6-alpine + image: postgres:10.6-alpine ports: - 5432:5432 env: diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index e9bc06a077a73..80feca1d07177 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -27,11 +27,11 @@ jobs: matrix: include: - php: '8.1' - - php: '8.1' + - php: '8.2' mode: high-deps - - php: '8.1' - mode: low-deps - php: '8.2' + mode: low-deps + - php: '8.3' #mode: experimental fail-fast: false @@ -59,12 +59,14 @@ jobs: git config --global init.defaultBranch main git config --global advice.detachedHead false + (php --ri relay 2>&1 > /dev/null) || sudo rm /etc/php/*/cli/conf.d/20-relay.ini + COMPOSER_HOME="$(composer config home)" ([ -d "$COMPOSER_HOME" ] || mkdir "$COMPOSER_HOME") && cp .github/composer-config.json "$COMPOSER_HOME/config.json" echo COLUMNS=120 >> $GITHUB_ENV echo PHPUNIT="$(pwd)/phpunit --exclude-group tty,benchmark,intl-data,integration" >> $GITHUB_ENV - echo COMPOSER_UP='composer update --no-progress --ansi'$([[ "${{ matrix.mode }}" = experimental ]] && echo ' --ignore-platform-req=php+') >> $GITHUB_ENV + echo COMPOSER_UP='composer update --no-progress --ansi'$([[ "${{ matrix.mode }}" != low-deps ]] && echo ' --ignore-platform-req=php+') >> $GITHUB_ENV SYMFONY_VERSIONS=$(git ls-remote -q --heads | cut -f2 | grep -o '/[1-9][0-9]*\.[0-9].*' | sort -V) SYMFONY_VERSION=$(grep ' VERSION = ' src/Symfony/Component/HttpKernel/Kernel.php | cut -d "'" -f2 | cut -d '.' -f 1-2) diff --git a/CHANGELOG-6.3.md b/CHANGELOG-6.3.md index e5d6c9162a7c5..54f6bec0c12d9 100644 --- a/CHANGELOG-6.3.md +++ b/CHANGELOG-6.3.md @@ -7,6 +7,56 @@ in 6.3 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v6.3.0...v6.3.1 +* 6.3.1 (2023-06-26) + + * bug #50763 [DependencyInjection] Skip errored definitions deep-referenced as runtime exceptions (nicolas-grekas) + * bug #50637 [FrameworkBundle] Fixed parsing new JSON output of debug:config not possible (Toflar) + * bug #50728 [HttpClient] Explicitly exclude CURLOPT_POSTREDIR (nicolas-grekas) + * bug #50710 [FrameworkBundle] Fix setting decorated services during tests (nicolas-grekas) + * bug #50749 [AssetMapper] Allow DirectoryResource for cache (weaverryan) + * bug #50760 [HttpFoundation] Require PHPUnit 9.6 by default (nicolas-grekas) + * bug #50747 [HttpKernel] Nullable and default value arguments in RequestPayloadValueResolver (mdeboer) + * bug #48961 [WebProfilerBundle] right blocks: fix display (jmsche) + * bug #50730 [HttpFoundation] Make Request::getPayload() return an empty InputBag if request body is empty (nicolas-grekas) + * bug #50654 [Validator] Add the `message` option to the `PasswordStrength` constraint (alexandre-daubois) + * bug #50671 [HttpClient] Fix encoding some characters in query strings (Daniel Kozák) + * bug #50673 [HttpKernel] make `RequestPayloadValueResolver::resolve()` throw on variadic argument (javaDeveloperKid) + * bug #50655 Revert "Respect isRetryable decision of the retry strategy for re-delivery" (bendavies) + * bug #50665 [FrameworkBundle] Ignore missing directories in about command (ro0NL) + * bug #50644 [VarDumper] Dumping DateTime throws error if getTimezone is false (bogdanmoza) + * bug #50712 [FrameworkBundle] Fix secrets:list not displaying local vars (nicolas-grekas) + * bug #50656 Only update autoload_runtime.php when it changed (Seldaek) + * bug #50698 [HttpClient] Fix int conversion for `GenericRetryStrategy` with floated multiplier (francisbesset) + * bug #50686 [Messenger] Don't mark `RedispatchMessage` as internal (valtzu) + * bug #50530 [DependencyInjection] Fix support for `false` boolean env vars (Okhoshi) + * bug #50577 [HttpClient] Remove final keyword on `AsyncResponse` (lyrixx) + * bug #50611 [Clock] Fix MockClock::modify() on PHP 8.3 (nicolas-grekas) + * bug #50548 [FrameworkBundle] Show non-bundle extensions in `debug:config` & `config:dump` list view & completion (HypeMC) + * bug #50585 [Cache] Fix RedisTrait::createConnection for cluster (darkanakin41) + * bug #50599 [MonologBridge] widen return type for Monolog 3 compatibility (xabbuh) + * bug #50546 [FrameworkBundle] Fix `debug:config` & `config:dump` in debug mode (HypeMC) + * bug #50560 [DependencyInjection] Support PHP 8.2 `true` and `null` type (ruudk) + * bug #50563 [FrameworkBundle] remove unusable cache pools (xabbuh) + * bug #50567 [PhpUnitBridge] Ignore deprecations about the annotation mapping driver when it's not possible to move to the attribute driver yet (nicolas-grekas) + * bug #50562 [Lock] Fix sprintf (fancyweb) + * bug #50540 [Validator] GH-50526: Reverting ExecutionContextInterface void return types in favor of docblock annotations. (upchuk) + * bug #50524 Fix Doctrine deprecations (nicolas-grekas) + * bug #50539 [Validator] Remove internal from methods on non-internal interfaces (wouterj) + * bug #50532 [Messenger] Prevent `StopWorkerOnSignalsListener::$signals` to be assigned to null in case `SIGTERM` constant doesn't exist (alexandre-daubois) + * bug #50534 [PhpUnitBridge] Fix support for the NO_COLOR env var (nicolas-grekas) + * bug #50525 [PhpUnitBridge] Fix classifying doctrine/deprecations as direct/indirect (nicolas-grekas) + * bug #50521 [Serializer] Fix ignoring objects that only implement DenormalizableInterface (spideyfusion) + * bug #50517 [DependencyInjection] Fix casting scalar env vars from null (fancyweb) + * bug #50515 [Mailer] [MailPace] Fix undefined array key in errors response (Florian Heller) + * bug #50514 [PhpUnitBridge] Disable deduplication of Doctrine deprecations (nicolas-grekas) + * bug #50508 [Messenger] Add deprecation message for the `messenger.listener.stop_worker_on_sigterm_signal_listener` service (alexandre-daubois) + * bug #50507 [Cache] Fix DBAL deprecations (MatTheCat) + * bug #50501 [Serializer] Fix discriminator map not working with `AbstractNormalizer::OBJECT_TO_POPULATE` (HypeMC) + * bug #50503 [SecurityBundle] Fix error message when using OIDC and web-token/jwt-core is not installed (nicolas-grekas) + * bug #50498 [FrameworkBundle] ease migration to symfony 6.3 (lyrixx) + * bug #50480 [Serializer] Fix discriminator map not working with `AbstractNormalizer::OBJECT_TO_POPULATE` (HypeMC) + * bug #50493 [VarDumper] Use documentElement instead of body for JS flag (ohader) + * 6.3.0 (2023-05-30) * bug #50432 [Security] Validate `aud` and `iss` claims on OidcTokenHandler (vincentchalamon) @@ -203,7 +253,7 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c * feature #48678 [FrameworkBundle] Rename service `notifier.logger_notification_listener` to `notifier.notification_logger_listener` (ker0x) * feature #48516 [PhpUnitBridge] Add `enum_exists` mock (alexandre-daubois) * feature #48855 [Notifier] Add new Symfony Notifier for PagerDuty (stloyd) - * feature #48876 [HttpKernel] Rename HttpStatus atribute to WithHttpStatus (fabpot) + * feature #48876 [HttpKernel] Rename HttpStatus attribute to WithHttpStatus (fabpot) * feature #48797 [FrameworkBundle] Add `extra` attribute for HttpClient Configuration (voodooism) * feature #48747 [HttpKernel] Allow using `#[WithLogLevel]` for setting custom log level for exceptions (angelov) * feature #48820 [HttpFoundation] ParameterBag::getEnum() (nikophil) diff --git a/UPGRADE-6.3.md b/UPGRADE-6.3.md index 883fef0ba544b..94a33f7d1b1b3 100644 --- a/UPGRADE-6.3.md +++ b/UPGRADE-6.3.md @@ -1,6 +1,11 @@ UPGRADE FROM 6.2 to 6.3 ======================= +Cache +----- + + * `DoctrineDbalAdapter` now takes an optional `$isSameDatabase` parameter + Console ------- @@ -22,6 +27,8 @@ DoctrineBridge * Deprecate `DoctrineDbalCacheAdapterSchemaSubscriber` in favor of `DoctrineDbalCacheAdapterSchemaListener` * Deprecate `MessengerTransportDoctrineSchemaSubscriber` in favor of `MessengerTransportDoctrineSchemaListener` * Deprecate `RememberMeTokenProviderDoctrineSchemaSubscriber` in favor of `RememberMeTokenProviderDoctrineSchemaListener` + * `DoctrineTransport` now takes an optional `$isSameDatabase` parameter + * `DoctrineTokenProvider` now takes an optional `$isSameDatabase` parameter Form ---- diff --git a/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php b/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php index 42cb71bfca07c..39983773ba615 100644 --- a/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php +++ b/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php @@ -63,9 +63,6 @@ public function dispatchEvent($eventName, EventArgs $eventArgs = null): void } } - /** - * @return object[][] - */ public function getListeners($event = null): array { if (null === $event) { diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php index c8b341f0edddb..e3a4c021f0ce2 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\Doctrine\Form\ChoiceList; +use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Types\ConversionException; use Doctrine\DBAL\Types\Type; @@ -70,13 +71,13 @@ public function getEntitiesByIds(string $identifier, array $values): array $entity = current($qb->getRootEntities()); $metadata = $qb->getEntityManager()->getClassMetadata($entity); if (\in_array($type = $metadata->getTypeOfField($identifier), ['integer', 'bigint', 'smallint'])) { - $parameterType = Connection::PARAM_INT_ARRAY; + $parameterType = class_exists(ArrayParameterType::class) ? ArrayParameterType::INTEGER : Connection::PARAM_INT_ARRAY; // Filter out non-integer values (e.g. ""). If we don't, some // databases such as PostgreSQL fail. $values = array_values(array_filter($values, fn ($v) => (string) $v === (string) (int) $v || ctype_digit($v))); } elseif (\in_array($type, ['ulid', 'uuid', 'guid'])) { - $parameterType = Connection::PARAM_STR_ARRAY; + $parameterType = class_exists(ArrayParameterType::class) ? ArrayParameterType::STRING : Connection::PARAM_STR_ARRAY; // Like above, but we just filter out empty strings. $values = array_values(array_filter($values, fn ($v) => '' !== (string) $v)); @@ -95,7 +96,7 @@ public function getEntitiesByIds(string $identifier, array $values): array unset($value); } } else { - $parameterType = Connection::PARAM_STR_ARRAY; + $parameterType = class_exists(ArrayParameterType::class) ? ArrayParameterType::STRING : Connection::PARAM_STR_ARRAY; } if (!$values) { return []; diff --git a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php index d816ee2303a01..34d36c52ffb54 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php @@ -185,7 +185,7 @@ public function testMappingTypeDetection() // The ordinary fixtures contain annotation $mappingType = $method->invoke($this->extension, __DIR__.'/../Fixtures', $container); - $this->assertSame($mappingType, 'annotation'); + $this->assertSame($mappingType, 'attribute'); // In the attribute folder, attributes are used $mappingType = $method->invoke($this->extension, __DIR__.'/../Fixtures/Attribute', $container); @@ -273,9 +273,9 @@ public function testUnrecognizedCacheDriverException() public static function providerBundles() { - yield ['AnnotationsBundle', 'annotation', '/Entity']; - yield ['AnnotationsOneLineBundle', 'annotation', '/Entity']; - yield ['FullEmbeddableAnnotationsBundle', 'annotation', '/Entity']; + yield ['AnnotationsBundle', 'attribute', '/Entity']; + yield ['AnnotationsOneLineBundle', 'attribute', '/Entity']; + yield ['FullEmbeddableAnnotationsBundle', 'attribute', '/Entity']; yield ['AttributesBundle', 'attribute', '/Entity']; yield ['FullEmbeddableAttributesBundle', 'attribute', '/Entity']; yield ['XmlBundle', 'xml', '/Resources/config/doctrine']; @@ -284,7 +284,7 @@ public static function providerBundles() yield ['SrcXmlBundle', 'xml', '/Resources/config/doctrine']; - yield ['NewAnnotationsBundle', 'annotation', \DIRECTORY_SEPARATOR.'src/Entity']; + yield ['NewAnnotationsBundle', 'attribute', \DIRECTORY_SEPARATOR.'src/Entity']; yield ['NewXmlBundle', 'xml', '/config/doctrine']; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/DoctrineTestHelper.php b/src/Symfony/Bridge/Doctrine/Tests/DoctrineTestHelper.php index be18506ba96ae..2cbf416a67b26 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DoctrineTestHelper.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DoctrineTestHelper.php @@ -12,10 +12,15 @@ namespace Symfony\Bridge\Doctrine\Tests; use Doctrine\Common\Annotations\AnnotationReader; +use Doctrine\Common\EventManager; +use Doctrine\DBAL\DriverManager; +use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; use Doctrine\ORM\Configuration; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Mapping\Driver\AnnotationDriver; +use Doctrine\ORM\Mapping\Driver\AttributeDriver; use Doctrine\ORM\Mapping\Driver\XmlDriver; +use Doctrine\ORM\ORMSetup; use Doctrine\Persistence\Mapping\Driver\MappingDriverChain; use Doctrine\Persistence\Mapping\Driver\SymfonyFileLocator; use PHPUnit\Framework\TestCase; @@ -41,17 +46,32 @@ public static function createTestEntityManager(Configuration $config = null): En 'memory' => true, ]; - return EntityManager::create($params, $config ?? self::createTestConfiguration()); + $config ??= self::createTestConfiguration(); + + if (!(new \ReflectionMethod(EntityManager::class, '__construct'))->isPublic()) { + return EntityManager::create($params, $config); + } + + $eventManager = new EventManager(); + + return new EntityManager(DriverManager::getConnection($params, $config, $eventManager), $config, $eventManager); } public static function createTestConfiguration(): Configuration { - $config = new Configuration(); + $config = class_exists(ORMSetup::class) ? ORMSetup::createConfiguration(true) : new Configuration(); $config->setEntityNamespaces(['SymfonyTestsDoctrine' => 'Symfony\Bridge\Doctrine\Tests\Fixtures']); $config->setAutoGenerateProxyClasses(true); $config->setProxyDir(sys_get_temp_dir()); $config->setProxyNamespace('SymfonyTests\Doctrine'); - $config->setMetadataDriverImpl(new AnnotationDriver(new AnnotationReader())); + if (class_exists(AttributeDriver::class)) { + $config->setMetadataDriverImpl(new AttributeDriver([__DIR__.'/../Tests/Fixtures' => 'Symfony\Bridge\Doctrine\Tests\Fixtures'], true)); + } else { + $config->setMetadataDriverImpl(new AnnotationDriver(new AnnotationReader(), null, true)); + } + if (class_exists(DefaultSchemaManagerFactory::class)) { + $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); + } return $config; } @@ -65,7 +85,9 @@ public static function createTestConfigurationWithXmlLoader(): Configuration new XmlDriver( new SymfonyFileLocator( [__DIR__.'/../Tests/Resources/orm' => 'Symfony\\Bridge\\Doctrine\\Tests\\Fixtures'], '.orm.xml' - ) + ), + '.orm.xml', + true ), 'Symfony\\Bridge\\Doctrine\\Tests\\Fixtures' ); diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/AssociationEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/AssociationEntity.php index c6d689a96a68c..94be71ec9f153 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/AssociationEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/AssociationEntity.php @@ -16,6 +16,7 @@ /** * @ORM\Entity */ +#[ORM\Entity] class AssociationEntity { /** @@ -23,6 +24,7 @@ class AssociationEntity * @ORM\Id @ORM\GeneratedValue * @ORM\Column(type="integer") */ + #[ORM\Id, ORM\GeneratedValue, ORM\Column(type: 'integer')] private $id; /** @@ -30,6 +32,7 @@ class AssociationEntity * * @var \Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity */ + #[ORM\ManyToOne(targetEntity: SingleIntIdEntity::class)] public $single; /** @@ -41,5 +44,8 @@ class AssociationEntity * * @var \Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity */ + #[ORM\ManyToOne(targetEntity: CompositeIntIdEntity::class)] + #[ORM\JoinColumn(name: 'composite_id1', referencedColumnName: 'id1')] + #[ORM\JoinColumn(name: 'composite_id2', referencedColumnName: 'id2')] public $composite; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/AssociationEntity2.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/AssociationEntity2.php index a0a76124583a0..df4894e76ecbc 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/AssociationEntity2.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/AssociationEntity2.php @@ -16,6 +16,7 @@ /** * @ORM\Entity */ +#[ORM\Entity] class AssociationEntity2 { /** @@ -23,6 +24,7 @@ class AssociationEntity2 * @ORM\Id @ORM\GeneratedValue * @ORM\Column(type="integer") */ + #[ORM\Id, ORM\GeneratedValue, ORM\Column(type: 'integer')] private $id; /** @@ -30,6 +32,7 @@ class AssociationEntity2 * * @var \Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdNoToStringEntity */ + #[ORM\ManyToOne(targetEntity: SingleIntIdNoToStringEntity::class)] public $single; /** @@ -41,5 +44,8 @@ class AssociationEntity2 * * @var \Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity */ + #[ORM\ManyToOne(targetEntity: CompositeIntIdEntity::class)] + #[ORM\JoinColumn(name: 'composite_id1', referencedColumnName: 'id1')] + #[ORM\JoinColumn(name: 'composite_id2', referencedColumnName: 'id2')] public $composite; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/AnnotationsBundle/Entity/Person.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/AnnotationsBundle/Entity/Person.php index 0d7cc91362da3..4f33525506493 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/AnnotationsBundle/Entity/Person.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/AnnotationsBundle/Entity/Person.php @@ -18,12 +18,15 @@ /** * @Entity */ +#[Entity] class Person { /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id; /** @Column(type="string") */ + #[Column(type: 'string')] public $name; public function __construct($id, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/AnnotationsOneLineBundle/Entity/Person.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/AnnotationsOneLineBundle/Entity/Person.php index b55fe6f86503b..0e77dbffc6dff 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/AnnotationsOneLineBundle/Entity/Person.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/AnnotationsOneLineBundle/Entity/Person.php @@ -16,12 +16,15 @@ use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class Person { /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id; /** @Column(type="string") */ + #[Column(type: 'string')] public $name; public function __construct($id, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/AttributesBundle/AnnotatedEntity/Person.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/AttributesBundle/AnnotatedEntity/Person.php index 0ec41bb096861..ae6fec848d1f2 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/AttributesBundle/AnnotatedEntity/Person.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/AttributesBundle/AnnotatedEntity/Person.php @@ -18,12 +18,15 @@ /** * @Entity */ +#[Entity] class Person { /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id; /** @Column(type="string") */ + #[Column(type: 'string')] public $name; public function __construct($id, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/FullEmbeddableAnnotationsBundle/Entity/Address.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/FullEmbeddableAnnotationsBundle/Entity/Address.php index d311a3f1ad1a1..bad7402e51c95 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/FullEmbeddableAnnotationsBundle/Entity/Address.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/FullEmbeddableAnnotationsBundle/Entity/Address.php @@ -13,21 +13,24 @@ use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Embeddable; -use Doctrine\ORM\Mapping\Id; /** * @Embeddable */ +#[Embeddable] class Address { /** @Column(type="string") */ + #[Column(type: 'string')] public $street; /** @Column(type="string") */ + #[Column(type: 'string')] public $zipCode; /** @Column(type="string") */ + #[Column(type: 'string')] public $city; public function __construct($street, $zipCode, $city) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/FullEmbeddableAttributesBundle/Entity/Address.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/FullEmbeddableAttributesBundle/Entity/Address.php index c0c58d6a21ce2..e257442ae18e6 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/FullEmbeddableAttributesBundle/Entity/Address.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/FullEmbeddableAttributesBundle/Entity/Address.php @@ -13,7 +13,6 @@ use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Embeddable; -use Doctrine\ORM\Mapping\Id; #[Embeddable] class Address diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/NewAnnotationsBundle/src/Entity/Person.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/NewAnnotationsBundle/src/Entity/Person.php index e94a24e1a95c7..9ab508e30523f 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/NewAnnotationsBundle/src/Entity/Person.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Bundles/NewAnnotationsBundle/src/Entity/Person.php @@ -18,12 +18,15 @@ /** * @Entity */ +#[Entity] class Person { /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id; /** @Column(type="string") */ + #[Column(type: 'string')] public $name; public function __construct($id, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeIntIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeIntIdEntity.php index 7c64cc20ad7e1..40ff64d9488b3 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeIntIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeIntIdEntity.php @@ -16,15 +16,19 @@ use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class CompositeIntIdEntity { /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id1; /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id2; /** @Column(type="string") */ + #[Column(type: 'string')] public $name; public function __construct($id1, $id2, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeObjectNoToStringIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeObjectNoToStringIdEntity.php index 73a6419e2f0c0..67f3c4f96c07a 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeObjectNoToStringIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeObjectNoToStringIdEntity.php @@ -18,6 +18,7 @@ * * @ORM\Entity */ +#[ORM\Entity] class CompositeObjectNoToStringIdEntity { /** @@ -27,6 +28,9 @@ class CompositeObjectNoToStringIdEntity * @ORM\ManyToOne(targetEntity="SingleIntIdNoToStringEntity", cascade={"persist"}) * @ORM\JoinColumn(name="object_one_id") */ + #[ORM\Id] + #[ORM\ManyToOne(targetEntity: SingleIntIdNoToStringEntity::class, cascade: ['persist'])] + #[ORM\JoinColumn(name: 'object_one_id')] protected $objectOne; /** @@ -36,6 +40,9 @@ class CompositeObjectNoToStringIdEntity * @ORM\ManyToOne(targetEntity="SingleIntIdNoToStringEntity", cascade={"persist"}) * @ORM\JoinColumn(name="object_two_id") */ + #[ORM\Id] + #[ORM\ManyToOne(targetEntity: SingleIntIdNoToStringEntity::class, cascade: ['persist'])] + #[ORM\JoinColumn(name: 'object_two_id')] protected $objectTwo; public function __construct(SingleIntIdNoToStringEntity $objectOne, SingleIntIdNoToStringEntity $objectTwo) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeStringIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeStringIdEntity.php index d6e8d2cd2aafa..95e687f6bfa50 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeStringIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeStringIdEntity.php @@ -16,15 +16,19 @@ use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class CompositeStringIdEntity { /** @Id @Column(type="string") */ + #[Id, Column(type: 'string')] protected $id1; /** @Id @Column(type="string") */ + #[Id, Column(type: 'string')] protected $id2; /** @Column(type="string") */ + #[Column(type: 'string')] public $name; public function __construct($id1, $id2, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEmbed.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEmbed.php index fc16f1cc135bc..5912ae6b1f9ea 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEmbed.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEmbed.php @@ -16,15 +16,18 @@ /** * @ORM\Embeddable */ +#[ORM\Embeddable] class DoctrineLoaderEmbed { /** * @ORM\Column(length=25) */ + #[ORM\Column(length: 25)] public $embeddedMaxLength; /** * @ORM\Embedded(class=DoctrineLoaderNestedEmbed::class) */ + #[ORM\Embedded(class: DoctrineLoaderNestedEmbed::class)] public $nestedEmbedded; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEntity.php index d6aee2d18b0b1..c32a9ef49d472 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEntity.php @@ -21,58 +21,70 @@ * * @author Kévin Dunglas */ +#[ORM\Entity, UniqueEntity(fields: ["alreadyMappedUnique"])] class DoctrineLoaderEntity extends DoctrineLoaderParentEntity { /** * @ORM\Id * @ORM\Column */ + #[ORM\Id, ORM\Column] public $id; /** * @ORM\Column(length=20) */ + #[ORM\Column(length: 20)] public $maxLength; /** * @ORM\Column(length=20) * @Assert\Length(min=5) */ + #[ORM\Column(length: 20), Assert\Length(min: 5)] public $mergedMaxLength; /** * @ORM\Column(length=20) * @Assert\Length(min=1, max=10) */ + #[ORM\Column(length: 20), Assert\Length(min: 1, max: 10)] public $alreadyMappedMaxLength; /** * @ORM\Column(unique=true) */ + #[ORM\Column(unique: true)] public $unique; /** * @ORM\Column(unique=true) */ + #[ORM\Column(unique: true)] public $alreadyMappedUnique; /** * @ORM\Embedded(class=DoctrineLoaderEmbed::class) */ + #[ORM\Embedded(class: DoctrineLoaderEmbed::class)] public $embedded; /** @ORM\Column(type="text", nullable=true, length=1000) */ + #[ORM\Column(type: 'text', nullable: true, length: 1000)] public $textField; /** @ORM\Id @ORM\Column(type="guid", length=50) */ + #[ORM\Id, ORM\Column(type: 'guid', length: 50)] protected $guidField; /** @ORM\Column(type="simple_array", length=100) */ + #[ORM\Column(type: 'simple_array', length: 100)] public $simpleArrayField = []; /** * @ORM\Column(length=10) * @Assert\DisableAutoMapping */ + #[ORM\Column(length: 10), Assert\DisableAutoMapping] public $noAutoMapping; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEnum.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEnum.php index 8ac883e89c4a2..4ba7211456902 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEnum.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEnum.php @@ -12,25 +12,31 @@ namespace Symfony\Bridge\Doctrine\Tests\Fixtures; use Doctrine\ORM\Mapping as ORM; +use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumInt; +use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumString; /** * @ORM\Entity */ +#[ORM\Entity] class DoctrineLoaderEnum { /** * @ORM\Id * @ORM\Column */ + #[ORM\Id, ORM\Column] public $id; /** * @ORM\Column(type="string", enumType="Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumString", length=1) */ + #[ORM\Column(type: 'string', enumType: EnumString::class, length: 1)] public $enumString; /** * @ORM\Column(type="integer", enumType="Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumInt") */ + #[ORM\Column(type: 'integer', enumType: EnumInt::class)] public $enumInt; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderNestedEmbed.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderNestedEmbed.php index fbf41555a1316..9d9424f0ece0d 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderNestedEmbed.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderNestedEmbed.php @@ -16,10 +16,12 @@ /** * @ORM\Embeddable() */ +#[ORM\Embeddable] class DoctrineLoaderNestedEmbed { /** * @ORM\Column(length=27) */ + #[ORM\Column(length: 27)] public $nestedEmbeddedMaxLength; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderNoAutoMappingEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderNoAutoMappingEntity.php index 0914411431201..515ec30bc897f 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderNoAutoMappingEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderNoAutoMappingEntity.php @@ -20,22 +20,26 @@ * * @author Kévin Dunglas */ +#[ORM\Entity, Assert\DisableAutoMapping] class DoctrineLoaderNoAutoMappingEntity { /** * @ORM\Id * @ORM\Column */ + #[ORM\Id, ORM\Column] public $id; /** * @ORM\Column(length=20, unique=true) */ + #[ORM\Column(length: 20, unique: true)] public $maxLength; /** * @Assert\EnableAutoMapping * @ORM\Column(length=20) */ + #[Assert\EnableAutoMapping, ORM\Column(length: 20)] public $autoMappingExplicitlyEnabled; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderParentEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderParentEntity.php index 7ec0263559c71..d7d832e6af23a 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderParentEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderParentEntity.php @@ -16,16 +16,19 @@ /** * @ORM\MappedSuperclass */ +#[ORM\MappedSuperclass] class DoctrineLoaderParentEntity { /** * @ORM\Column(length=35) */ + #[ORM\Column(length: 35)] public $publicParentMaxLength; /** * @ORM\Column(length=30) */ + #[ORM\Column(length: 30)] private $privateParentMaxLength; public function getPrivateParentMaxLength() diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoubleNameEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoubleNameEntity.php index 3559568787bcd..2c8ac68ce17c2 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoubleNameEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoubleNameEntity.php @@ -16,15 +16,19 @@ use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class DoubleNameEntity { /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id; /** @Column(type="string") */ + #[Column(type: 'string')] public $name; /** @Column(type="string", nullable=true) */ + #[Column(type: 'string', nullable: true)] public $name2; public function __construct($id, $name, $name2) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoubleNullableNameEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoubleNullableNameEntity.php index 20ef14fd1b578..0c37651412ab4 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoubleNullableNameEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoubleNullableNameEntity.php @@ -16,15 +16,19 @@ use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class DoubleNullableNameEntity { /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id; /** @Column(type="string", nullable=true) */ + #[Column(type: 'string', nullable: true)] public $name; /** @Column(type="string", nullable=true) */ + #[Column(type: 'string', nullable: true)] public $name2; public function __construct($id, $name, $name2) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Embeddable/Identifier.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Embeddable/Identifier.php index f8000dbfd9814..cdc82cf94e34a 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Embeddable/Identifier.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Embeddable/Identifier.php @@ -16,6 +16,7 @@ /** * @ORM\Embeddable */ +#[ORM\Embeddable] class Identifier { /** @@ -24,5 +25,6 @@ class Identifier * @ORM\Id * @ORM\Column(type="integer") */ + #[ORM\Id, ORM\Column(type: 'integer')] protected $value; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/EmbeddedIdentifierEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/EmbeddedIdentifierEntity.php index 6d7b2670962c7..c2cec427ad77e 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/EmbeddedIdentifierEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/EmbeddedIdentifierEntity.php @@ -12,10 +12,12 @@ namespace Symfony\Bridge\Doctrine\Tests\Fixtures; use Doctrine\ORM\Mapping as ORM; +use Symfony\Bridge\Doctrine\Tests\Fixtures\Embeddable\Identifier; /** * @ORM\Entity */ +#[ORM\Entity] class EmbeddedIdentifierEntity { /** @@ -23,5 +25,6 @@ class EmbeddedIdentifierEntity * * @ORM\Embedded(class="Symfony\Bridge\Doctrine\Tests\Fixtures\Embeddable\Identifier") */ + #[ORM\Embedded(class: Identifier::class)] protected $id; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Employee.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Employee.php index 24f08b00d781a..b9888adc8beae 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Employee.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Employee.php @@ -14,6 +14,7 @@ use Doctrine\ORM\Mapping\Entity; /** @Entity */ +#[Entity] class Employee extends Person { } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/GroupableEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/GroupableEntity.php index 730a9b2b1dbf8..d803ca2310d8b 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/GroupableEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/GroupableEntity.php @@ -16,15 +16,19 @@ use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class GroupableEntity { /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id; /** @Column(type="string", nullable=true) */ + #[Column(type: 'string', nullable: true)] public $name; /** @Column(type="string", nullable=true) */ + #[Column(type: 'string', nullable: true)] public $groupName; public function __construct($id, $name, $groupName) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/GuidIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/GuidIdEntity.php index 0d447ffc1e62c..d575659595010 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/GuidIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/GuidIdEntity.php @@ -16,9 +16,11 @@ use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class GuidIdEntity { /** @Id @Column(type="guid") */ + #[Id, Column(type: 'guid')] protected $id; public function __construct($id) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Person.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Person.php index b90a54ac02c61..35f3836e0d901 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Person.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Person.php @@ -24,12 +24,15 @@ * @DiscriminatorColumn(name="discr", type="string") * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"}) */ +#[Entity, InheritanceType('SINGLE_TABLE'), DiscriminatorColumn(name: 'discr', type: 'string'), DiscriminatorMap(['person' => 'Person', 'employee' => 'Employee'])] class Person { /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id; /** @Column(type="string") */ + #[Column(type: 'string')] public $name; public function __construct($id, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleAssociationToIntIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleAssociationToIntIdEntity.php index bed8bb9a51d01..b4a1ede9f0a2a 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleAssociationToIntIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleAssociationToIntIdEntity.php @@ -17,12 +17,15 @@ use Doctrine\ORM\Mapping\OneToOne; /** @Entity */ +#[Entity] class SingleAssociationToIntIdEntity { /** @Id @OneToOne(targetEntity="SingleIntIdNoToStringEntity", cascade={"ALL"}) */ + #[Id, OneToOne(targetEntity: SingleIntIdNoToStringEntity::class, cascade: ['ALL'])] protected $entity; /** @Column(type="string", nullable=true) */ + #[Column(type: 'string', nullable: true)] public $name; public function __construct(SingleIntIdNoToStringEntity $entity, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdEntity.php index 612566b45d94b..94b47da855a37 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdEntity.php @@ -16,15 +16,19 @@ use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class SingleIntIdEntity { /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id; /** @Column(type="string", nullable=true) */ + #[Column(type: 'string', nullable: true)] public $name; /** @Column(type="array", nullable=true) */ + #[Column(type: 'array', nullable: true)] public $phoneNumbers = []; public function __construct($id, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdNoToStringEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdNoToStringEntity.php index c94815ca02cad..8887c5b5a3d6c 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdNoToStringEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdNoToStringEntity.php @@ -16,12 +16,15 @@ use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class SingleIntIdNoToStringEntity { /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id; /** @Column(type="string", nullable=true) */ + #[Column(type: 'string', nullable: true)] public $name; public function __construct($id, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdStringWrapperNameEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdStringWrapperNameEntity.php index 8f4c3663d2148..8c774009ba530 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdStringWrapperNameEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdStringWrapperNameEntity.php @@ -16,12 +16,15 @@ use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class SingleIntIdStringWrapperNameEntity { /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id; /** @Column(type="string_wrapper", nullable=true) */ + #[Column(type: 'string_wrapper', nullable: true)] public $name; public function __construct($id, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleStringCastableIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleStringCastableIdEntity.php index 128801a02c922..b3e952d9a8b33 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleStringCastableIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleStringCastableIdEntity.php @@ -17,6 +17,7 @@ use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class SingleStringCastableIdEntity { /** @@ -24,9 +25,11 @@ class SingleStringCastableIdEntity * @Column(type="string") * @GeneratedValue(strategy="NONE") */ + #[Id, Column(type: 'string'), GeneratedValue(strategy: 'NONE')] protected $id; /** @Column(type="string", nullable=true) */ + #[Column(type: 'string', nullable: true)] public $name; public function __construct($id, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleStringIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleStringIdEntity.php index 83f7a9f9ab39d..ce3a5526a5b15 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleStringIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleStringIdEntity.php @@ -16,12 +16,15 @@ use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class SingleStringIdEntity { /** @Id @Column(type="string") */ + #[Id, Column(type: 'string')] protected $id; /** @Column(type="string") */ + #[Column(type: 'string')] public $name; public function __construct($id, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UlidIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UlidIdEntity.php index 3ee909fe4bfc5..238a08511ebe9 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UlidIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UlidIdEntity.php @@ -16,9 +16,11 @@ use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class UlidIdEntity { /** @Id @Column(type="ulid") */ + #[Id, Column(type: 'ulid')] protected $id; public function __construct($id) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/User.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/User.php index 02d31078451b8..5604c33370092 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/User.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/User.php @@ -18,15 +18,19 @@ use Symfony\Component\Security\Core\User\UserInterface; /** @Entity */ +#[Entity] class User implements UserInterface, PasswordAuthenticatedUserInterface { /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id1; /** @Id @Column(type="integer") */ + #[Id, Column(type: 'integer')] protected $id2; /** @Column(type="string") */ + #[Column(type: 'string')] public $name; public function __construct($id1, $id2, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UuidIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UuidIdEntity.php index 46084ab292d49..60271f7d15ab0 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UuidIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UuidIdEntity.php @@ -16,9 +16,11 @@ use Doctrine\ORM\Mapping\Id; /** @Entity */ +#[Entity] class UuidIdEntity { /** @Id @Column(type="uuid") */ + #[Id, Column(type: 'uuid')] protected $id; public function __construct($id) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php b/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php index 2bb628442459c..b1096fbf60cb5 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php @@ -17,8 +17,10 @@ use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Result; +use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; use Doctrine\DBAL\Statement; use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\ORMSetup; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\Middleware\Debug\DebugDataHolder; use Symfony\Bridge\Doctrine\Middleware\Debug\Middleware; @@ -49,14 +51,17 @@ private function init(bool $withStopwatch = true): void { $this->stopwatch = $withStopwatch ? new Stopwatch() : null; - $configuration = new Configuration(); + $config = class_exists(ORMSetup::class) ? ORMSetup::createConfiguration(true) : new Configuration(); + if (class_exists(DefaultSchemaManagerFactory::class)) { + $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); + } $this->debugDataHolder = new DebugDataHolder(); - $configuration->setMiddlewares([new Middleware($this->debugDataHolder, $this->stopwatch)]); + $config->setMiddlewares([new Middleware($this->debugDataHolder, $this->stopwatch)]); $this->conn = DriverManager::getConnection([ 'driver' => 'pdo_sqlite', 'memory' => true, - ], $configuration); + ], $config); $this->conn->executeQuery(<<getResourceFromString('mydata'); $stmt = $this->conn->prepare($sql); - $stmt->bindParam(1, $product); - $stmt->bindParam(2, $price); - $stmt->bindParam(3, $stock, ParameterType::INTEGER); - $stmt->bindParam(4, $res, ParameterType::BINARY); - - $product = 'product1'; - $price = 12.5; - $stock = 5; + $stmt->bindValue(1, 'product1'); + $stmt->bindValue(2, '12.5'); + $stmt->bindValue(3, 5, ParameterType::INTEGER); + $stmt->bindValue(4, $res, ParameterType::BINARY); $executeMethod($stmt); @@ -168,7 +169,7 @@ public function testWithParamBound(callable $executeMethod) $debug = $this->debugDataHolder->getData()['default'] ?? []; $this->assertCount(2, $debug); $this->assertSame($sql, $debug[1]['sql']); - $this->assertSame(['product1', 12.5, 5, $expectedRes], $debug[1]['params']); + $this->assertSame(['product1', '12.5', 5, $expectedRes], $debug[1]['params']); $this->assertSame([ParameterType::STRING, ParameterType::STRING, ParameterType::INTEGER, ParameterType::BINARY], $debug[1]['types']); $this->assertGreaterThan(0, $debug[1]['executionMS']); } @@ -188,6 +189,7 @@ public function testTransaction(callable $endTransactionMethod, string $expected { $this->init(); + $this->conn->setNestTransactionsWithSavepoints(true); $this->conn->beginTransaction(); $this->conn->beginTransaction(); $this->conn->executeStatement('INSERT INTO products(name, price, stock) VALUES ("product1", 12.5, 5)'); @@ -198,19 +200,23 @@ public function testTransaction(callable $endTransactionMethod, string $expected $endTransactionMethod($this->conn); $debug = $this->debugDataHolder->getData()['default'] ?? []; - $this->assertCount(7, $debug); + $this->assertCount(9, $debug); $this->assertSame('"START TRANSACTION"', $debug[1]['sql']); $this->assertGreaterThan(0, $debug[1]['executionMS']); - $this->assertSame('INSERT INTO products(name, price, stock) VALUES ("product1", 12.5, 5)', $debug[2]['sql']); + $this->assertSame('SAVEPOINT DOCTRINE2_SAVEPOINT_2', $debug[2]['sql']); $this->assertGreaterThan(0, $debug[2]['executionMS']); - $this->assertSame($expectedEndTransactionDebug, $debug[3]['sql']); + $this->assertSame('INSERT INTO products(name, price, stock) VALUES ("product1", 12.5, 5)', $debug[3]['sql']); $this->assertGreaterThan(0, $debug[3]['executionMS']); - $this->assertSame('"START TRANSACTION"', $debug[4]['sql']); + $this->assertSame(('"ROLLBACK"' === $expectedEndTransactionDebug ? 'ROLLBACK TO' : 'RELEASE').' SAVEPOINT DOCTRINE2_SAVEPOINT_2', $debug[4]['sql']); $this->assertGreaterThan(0, $debug[4]['executionMS']); - $this->assertSame('INSERT INTO products(name, price, stock) VALUES ("product2", 15.5, 12)', $debug[5]['sql']); + $this->assertSame($expectedEndTransactionDebug, $debug[5]['sql']); $this->assertGreaterThan(0, $debug[5]['executionMS']); - $this->assertSame($expectedEndTransactionDebug, $debug[6]['sql']); + $this->assertSame('"START TRANSACTION"', $debug[6]['sql']); $this->assertGreaterThan(0, $debug[6]['executionMS']); + $this->assertSame('INSERT INTO products(name, price, stock) VALUES ("product2", 15.5, 12)', $debug[7]['sql']); + $this->assertGreaterThan(0, $debug[7]['executionMS']); + $this->assertSame($expectedEndTransactionDebug, $debug[8]['sql']); + $this->assertGreaterThan(0, $debug[8]['executionMS']); } public static function provideExecuteAndEndTransactionMethods(): array diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php index 1ddd8e5ea59f8..5ca97f6f0b64a 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php @@ -12,9 +12,13 @@ namespace Symfony\Bridge\Doctrine\Tests\PropertyInfo; use Doctrine\Common\Collections\Collection; +use Doctrine\Common\EventManager; +use Doctrine\DBAL\DriverManager; +use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; use Doctrine\DBAL\Types\Type as DBALType; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Driver\AttributeDriver; use Doctrine\ORM\ORMSetup; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor; @@ -33,8 +37,18 @@ class DoctrineExtractorTest extends TestCase { private function createExtractor() { - $config = ORMSetup::createAnnotationMetadataConfiguration([__DIR__.\DIRECTORY_SEPARATOR.'Fixtures'], true); - $entityManager = EntityManager::create(['driver' => 'pdo_sqlite'], $config); + $config = ORMSetup::createConfiguration(true); + $config->setMetadataDriverImpl(new AttributeDriver([__DIR__.'/../Tests/Fixtures' => 'Symfony\Bridge\Doctrine\Tests\Fixtures'], true)); + if (class_exists(DefaultSchemaManagerFactory::class)) { + $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); + } + + if (!(new \ReflectionMethod(EntityManager::class, '__construct'))->isPublic()) { + $entityManager = EntityManager::create(['driver' => 'pdo_sqlite'], $config); + } else { + $eventManager = new EventManager(); + $entityManager = new EntityManager(DriverManager::getConnection(['driver' => 'pdo_sqlite'], $config, $eventManager), $config, $eventManager); + } if (!DBALType::hasType('foo')) { DBALType::addType('foo', 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineFooType'); diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php index 8190540e27566..902850c5828ba 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php @@ -23,102 +23,122 @@ * * @author Kévin Dunglas */ +#[Entity] class DoctrineDummy { /** * @Id * @Column(type="smallint") */ + #[Id, Column(type: 'smallint')] public $id; /** * @ManyToOne(targetEntity="DoctrineRelation") */ + #[ManyToOne(targetEntity: DoctrineRelation::class)] public $foo; /** * @ManyToMany(targetEntity="DoctrineRelation") */ + #[ManyToMany(targetEntity: DoctrineRelation::class)] public $bar; /** * @ManyToMany(targetEntity="DoctrineRelation", indexBy="rguid") */ + #[ManyToMany(targetEntity: DoctrineRelation::class, indexBy: 'rguid')] protected $indexedRguid; /** * @ManyToMany(targetEntity="DoctrineRelation", indexBy="rguid_column") */ + #[ManyToMany(targetEntity: DoctrineRelation::class, indexBy: 'rguid_column')] protected $indexedBar; /** * @OneToMany(targetEntity="DoctrineRelation", mappedBy="foo", indexBy="foo") */ + #[OneToMany(targetEntity: DoctrineRelation::class, mappedBy: 'foo', indexBy: 'foo')] protected $indexedFoo; /** * @OneToMany(targetEntity="DoctrineRelation", mappedBy="baz", indexBy="baz_id") */ + #[OneToMany(targetEntity: DoctrineRelation::class, mappedBy: 'baz', indexBy: 'baz_id')] protected $indexedBaz; /** * @Column(type="guid") */ + #[Column(type: 'guid')] protected $guid; /** * @Column(type="time") */ + #[Column(type: 'time')] private $time; /** * @Column(type="time_immutable") */ + #[Column(type: 'time_immutable')] private $timeImmutable; /** * @Column(type="dateinterval") */ + #[Column(type: 'dateinterval')] private $dateInterval; /** * @Column(type="json_array") */ + #[Column(type: 'json_array')] private $jsonArray; /** * @Column(type="simple_array") */ + #[Column(type: 'simple_array')] private $simpleArray; /** * @Column(type="float") */ + #[Column(type: 'float')] private $float; /** * @Column(type="decimal", precision=10, scale=2) */ + #[Column(type: 'decimal', precision: 10, scale: 2)] private $decimal; /** * @Column(type="boolean") */ + #[Column(type: 'boolean')] private $bool; /** * @Column(type="binary") */ + #[Column(type: 'binary')] private $binary; /** * @Column(type="custom_foo") */ + #[Column(type: 'custom_foo')] private $customFoo; /** * @Column(type="bigint") */ + #[Column(type: 'bigint')] private $bigint; public $notMapped; @@ -126,25 +146,30 @@ class DoctrineDummy /** * @OneToMany(targetEntity="DoctrineRelation", mappedBy="dt", indexBy="dt") */ + #[OneToMany(targetEntity: DoctrineRelation::class, mappedBy: 'dt', indexBy: 'dt')] protected $indexedByDt; /** * @OneToMany(targetEntity="DoctrineRelation", mappedBy="customType", indexBy="customType") */ + #[OneToMany(targetEntity: DoctrineRelation::class, mappedBy: 'customType', indexBy: 'customType')] private $indexedByCustomType; /** * @OneToMany(targetEntity="DoctrineRelation", mappedBy="buzField", indexBy="buzField") */ + #[OneToMany(targetEntity: DoctrineRelation::class, mappedBy: 'buzField', indexBy: 'buzField')] protected $indexedBuz; /** * @Column(type="json", nullable=true) */ + #[Column(type: 'json', nullable: true)] private $json; /** * @OneToMany(targetEntity="DoctrineRelation", mappedBy="dummyRelation", indexBy="gen_value_col_id", orphanRemoval=true) */ + #[OneToMany(targetEntity: DoctrineRelation::class, mappedBy: 'dummyRelation', indexBy: 'gen_value_col_id', orphanRemoval: true)] protected $dummyGeneratedValueList; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEmbeddable.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEmbeddable.php index a00856ed7331e..23609fb1827d0 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEmbeddable.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEmbeddable.php @@ -19,10 +19,12 @@ * * @author Udaltsov Valentin */ +#[Embeddable] class DoctrineEmbeddable { /** * @Column(type="string") */ + #[Column(type: 'string')] protected $field; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEnum.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEnum.php index 8a63a4e8db248..edd6b01e9f5c6 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEnum.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEnum.php @@ -18,36 +18,43 @@ /** * @Entity */ +#[Entity] class DoctrineEnum { /** * @Id * @Column(type="smallint") */ + #[Id, Column(type: 'smallint')] public $id; /** * @Column(type="string", enumType="Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumString") */ + #[Column(type: 'string', enumType: EnumString::class)] protected $enumString; /** * @Column(type="integer", enumType="Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumInt") */ + #[Column(type: 'integer', enumType: EnumInt::class)] protected $enumInt; /** * @Column(type="array", enumType="Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumString") */ + #[Column(type: 'array', enumType: EnumString::class)] protected $enumStringArray; /** * @Column(type="simple_array", enumType="Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumInt") */ + #[Column(type: 'simple_array', enumType: EnumInt::class)] protected $enumIntArray; /** * @Column(type="custom_foo", enumType="Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumInt") */ + #[Column(type: 'custom_foo', enumType: EnumInt::class)] protected $enumCustom; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineGeneratedValue.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineGeneratedValue.php index 9e7612fa35ae4..90fec268506e8 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineGeneratedValue.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineGeneratedValue.php @@ -22,6 +22,7 @@ * * @Entity */ +#[Entity] class DoctrineGeneratedValue { /** @@ -29,21 +30,25 @@ class DoctrineGeneratedValue * @GeneratedValue(strategy="AUTO") * @Column(type="integer") */ + #[Id, GeneratedValue(strategy: 'AUTO'), Column(type: 'integer')] public $id; /** * @Column */ + #[Column] public $foo; /** * @var int * @Column(type="integer", name="gen_value_col_id") */ + #[Column(type: 'integer', name: 'gen_value_col_id')] public $valueId; /** * @OneToMany(targetEntity="DoctrineRelation", mappedBy="generatedValueRelation", indexBy="rguid_column", orphanRemoval=true) */ + #[OneToMany(targetEntity: DoctrineRelation::class, mappedBy: 'generatedValueRelation', indexBy: 'rguid_column', orphanRemoval: true)] protected $relationList; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php index 91115abb719ec..3310f9e0d06a0 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php @@ -22,54 +22,67 @@ * * @author Kévin Dunglas */ +#[Entity] class DoctrineRelation { /** * @Id * @Column(type="smallint") */ + #[Id, Column(type: 'smallint')] public $id; /** * @Column(type="guid", name="rguid_column") */ + #[Column(type: 'guid', name: 'rguid_column')] protected $rguid; /** * @Column(type="guid") * @ManyToOne(targetEntity="DoctrineDummy", inversedBy="indexedFoo") */ + #[Column(type: 'guid')] + #[ManyToOne(targetEntity: DoctrineDummy::class, inversedBy: 'indexedFoo')] protected $foo; /** * @ManyToOne(targetEntity="DoctrineDummy") */ + #[ManyToOne(targetEntity: DoctrineDummy::class)] protected $baz; /** * @Column(type="datetime") */ + #[Column(type: 'datetime')] private $dt; /** * @Column(type="foo") */ + #[Column(type: 'foo')] private $customType; /** * @Column(type="guid", name="different_than_field") * @ManyToOne(targetEntity="DoctrineDummy", inversedBy="indexedBuz") */ + #[Column(type: 'guid', name: 'different_than_field')] + #[ManyToOne(targetEntity: DoctrineDummy::class, inversedBy: 'indexedBuz')] protected $buzField; /** * @ManyToOne(targetEntity="DoctrineDummy", inversedBy="dummyGeneratedValueList") */ + #[ManyToOne(targetEntity: DoctrineDummy::class, inversedBy: 'dummyGeneratedValueList')] private $dummyRelation; /** * @ManyToOne(targetEntity="DoctrineGeneratedValue", inversedBy="relationList") * @JoinColumn(name="gen_value_col_id", referencedColumnName="gen_value_col_id") */ + #[ManyToOne(targetEntity: DoctrineGeneratedValue::class, inversedBy: 'relationList')] + #[JoinColumn(name: 'gen_value_col_id', referencedColumnName: 'gen_value_col_id')] private $generatedValueRelation; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineWithEmbedded.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineWithEmbedded.php index aace866128b0e..053f8bec0d19b 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineWithEmbedded.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineWithEmbedded.php @@ -21,16 +21,19 @@ * * @author Udaltsov Valentin */ +#[Entity] class DoctrineWithEmbedded { /** * @Id * @Column(type="smallint") */ + #[Id, Column(type: 'smallint')] public $id; /** * @Embedded(class="DoctrineEmbeddable") */ + #[Embedded(class: DoctrineEmbeddable::class)] protected $embedded; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/DoctrineDbalCacheAdapterSchemaListenerTest.php b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/DoctrineDbalCacheAdapterSchemaListenerTest.php index 6cec07f820f42..e429dca192f6d 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/DoctrineDbalCacheAdapterSchemaListenerTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/DoctrineDbalCacheAdapterSchemaListenerTest.php @@ -35,7 +35,7 @@ public function testPostGenerateSchema() $dbalAdapter = $this->createMock(DoctrineDbalAdapter::class); $dbalAdapter->expects($this->once()) ->method('configureSchema') - ->with($schema, $dbalConnection); + ->with($schema, $dbalConnection, fn () => true); $subscriber = new DoctrineDbalCacheAdapterSchemaListener([$dbalAdapter]); $subscriber->postGenerateSchema($event); diff --git a/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/MessengerTransportDoctrineSchemaListenerTest.php b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/MessengerTransportDoctrineSchemaListenerTest.php index 6c58fec7ce939..13f1c67269692 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/MessengerTransportDoctrineSchemaListenerTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/MessengerTransportDoctrineSchemaListenerTest.php @@ -38,7 +38,7 @@ public function testPostGenerateSchema() $doctrineTransport = $this->createMock(DoctrineTransport::class); $doctrineTransport->expects($this->once()) ->method('configureSchema') - ->with($schema, $dbalConnection); + ->with($schema, $dbalConnection, fn () => true); $otherTransport = $this->createMock(TransportInterface::class); $otherTransport->expects($this->never()) ->method($this->anything()); diff --git a/src/Symfony/Bridge/Doctrine/Tests/Security/RememberMe/DoctrineTokenProviderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Security/RememberMe/DoctrineTokenProviderTest.php index 9d8b9256f4c9d..eb387e424cd09 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Security/RememberMe/DoctrineTokenProviderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Security/RememberMe/DoctrineTokenProviderTest.php @@ -11,7 +11,10 @@ namespace Security\RememberMe; +use Doctrine\DBAL\Configuration; use Doctrine\DBAL\DriverManager; +use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; +use Doctrine\ORM\ORMSetup; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\Security\RememberMe\DoctrineTokenProvider; use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken; @@ -120,10 +123,15 @@ public function testVerifyOutdatedTokenAfterParallelRequestFailsAfter60Seconds() */ private function bootstrapProvider() { + $config = class_exists(ORMSetup::class) ? ORMSetup::createConfiguration(true) : new Configuration(); + if (class_exists(DefaultSchemaManagerFactory::class)) { + $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); + } + $connection = DriverManager::getConnection([ 'driver' => 'pdo_sqlite', - 'url' => 'sqlite:///:memory:', - ]); + 'memory' => true, + ], $config); $connection->{method_exists($connection, 'executeStatement') ? 'executeStatement' : 'executeUpdate'}(<<< 'SQL' CREATE TABLE rememberme_token ( series char(88) UNIQUE PRIMARY KEY NOT NULL, diff --git a/src/Symfony/Bridge/Doctrine/Tests/Types/UlidTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Types/UlidTypeTest.php index 06114aeea0050..c1db2bbe70124 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Types/UlidTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Types/UlidTypeTest.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Platforms\MariaDBPlatform; use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; @@ -140,13 +141,15 @@ public function testGetGuidTypeDeclarationSQL(AbstractPlatform $platform, string $this->assertEquals($expectedDeclaration, $this->type->getSqlDeclaration(['length' => 36], $platform)); } - public static function provideSqlDeclarations(): array + public static function provideSqlDeclarations(): \Generator { - return [ - [new PostgreSQLPlatform(), 'UUID'], - [new SqlitePlatform(), 'BLOB'], - [new MySQLPlatform(), 'BINARY(16)'], - ]; + yield [new PostgreSQLPlatform(), 'UUID']; + yield [new SqlitePlatform(), 'BLOB']; + yield [new MySQLPlatform(), 'BINARY(16)']; + + if (class_exists(MariaDBPlatform::class)) { + yield [new MariaDBPlatform(), 'BINARY(16)']; + } } public function testRequiresSQLCommentHint() diff --git a/src/Symfony/Bridge/Doctrine/Tests/Types/UuidTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Types/UuidTypeTest.php index d49afc5f97ec4..120887ef3653a 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Types/UuidTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Types/UuidTypeTest.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Platforms\MariaDBPlatform; use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; @@ -152,13 +153,15 @@ public function testGetGuidTypeDeclarationSQL(AbstractPlatform $platform, string $this->assertEquals($expectedDeclaration, $this->type->getSqlDeclaration(['length' => 36], $platform)); } - public static function provideSqlDeclarations(): array + public static function provideSqlDeclarations(): \Generator { - return [ - [new PostgreSQLPlatform(), 'UUID'], - [new SqlitePlatform(), 'BLOB'], - [new MySQLPlatform(), 'BINARY(16)'], - ]; + yield [new PostgreSQLPlatform(), 'UUID']; + yield [new SqlitePlatform(), 'BLOB']; + yield [new MySQLPlatform(), 'BINARY(16)']; + + if (class_exists(MariaDBPlatform::class)) { + yield [new MariaDBPlatform(), 'BINARY(16)']; + } } public function testRequiresSQLCommentHint() diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php index 15193d5640a87..eb49736bac5f2 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php @@ -13,11 +13,12 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\DBAL\Types\Type; +use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Tools\SchemaTool; use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ObjectManager; -use Doctrine\Persistence\ObjectRepository; +use PHPUnit\Framework\MockObject\MockObject; use Symfony\Bridge\Doctrine\Tests\DoctrineTestHelper; use Symfony\Bridge\Doctrine\Tests\Fixtures\AssociationEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\AssociationEntity2; @@ -58,7 +59,7 @@ class UniqueEntityValidatorTest extends ConstraintValidatorTestCase protected $registry; /** - * @var ObjectRepository + * @var MockObject&EntityRepository */ protected $repository; @@ -95,7 +96,8 @@ protected function createRegistryMock($em = null) protected function createRepositoryMock() { - $repository = $this->getMockBuilder(ObjectRepository::class) + $repository = $this->getMockBuilder(EntityRepository::class) + ->disableOriginalConstructor() ->onlyMethods(['find', 'findAll', 'findOneBy', 'findBy', 'getClassName']) ->addMethods(['findByCustom']) ->getMock() diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php index eb83aecbf5990..e09da3634ac7c 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php @@ -92,9 +92,6 @@ public function testLoadClassMetadata() $parentClassMetadata = $validator->getMetadataFor(new DoctrineLoaderParentEntity()); - $publicParentMaxLengthMetadata = $parentClassMetadata->getPropertyMetadata('publicParentMaxLength'); - $this->assertCount(0, $publicParentMaxLengthMetadata); - $privateParentMaxLengthMetadata = $parentClassMetadata->getPropertyMetadata('privateParentMaxLength'); $this->assertCount(1, $privateParentMaxLengthMetadata); $privateParentMaxLengthConstraints = $privateParentMaxLengthMetadata[0]->getConstraints(); diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index d17933b31a901..7829a00e75d7b 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -41,7 +41,7 @@ "symfony/stopwatch": "^5.4|^6.0", "symfony/translation": "^5.4|^6.0", "symfony/uid": "^5.4|^6.0", - "symfony/validator": "^5.4|^6.0", + "symfony/validator": "^5.4.25|~6.2.12|^6.3.1", "symfony/var-dumper": "^5.4|^6.0", "doctrine/annotations": "^1.13.1|^2", "doctrine/collections": "^1.0|^2.0", @@ -65,7 +65,7 @@ "symfony/property-info": "<5.4", "symfony/security-bundle": "<5.4", "symfony/security-core": "<6.0", - "symfony/validator": "<5.4" + "symfony/validator": "<5.4.25|>=6,<6.2.12|>=6.3,<6.3.1" }, "autoload": { "psr-4": { "Symfony\\Bridge\\Doctrine\\": "" }, diff --git a/src/Symfony/Bridge/Monolog/Handler/NotifierHandler.php b/src/Symfony/Bridge/Monolog/Handler/NotifierHandler.php index 6aedc7c36e267..20d6c0eaee00b 100644 --- a/src/Symfony/Bridge/Monolog/Handler/NotifierHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/NotifierHandler.php @@ -71,7 +71,7 @@ private function notify(array $records): void $this->notifier->send($notification, ...$this->notifier->getAdminRecipients()); } - private function getHighestRecord(array $records): array + private function getHighestRecord(array $records): array|LogRecord { $highestRecord = null; foreach ($records as $record) { diff --git a/src/Symfony/Bridge/PhpUnit/CHANGELOG.md b/src/Symfony/Bridge/PhpUnit/CHANGELOG.md index 9c664176565b7..6c42e79218910 100644 --- a/src/Symfony/Bridge/PhpUnit/CHANGELOG.md +++ b/src/Symfony/Bridge/PhpUnit/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Add support for mocking the `enum_exists` function + * Enable reporting of deprecations triggered by Doctrine by default 6.2 --- diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php index e18d9b1a289f2..f53cfc9b197a4 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\PhpUnit\DeprecationErrorHandler; +use Doctrine\Deprecations\Deprecation as DoctrineDeprecation; use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestSuite; use PHPUnit\Metadata\Api\Groups; @@ -60,9 +61,18 @@ class Deprecation */ public function __construct($message, array $trace, $file, $languageDeprecation = false) { - if (isset($trace[2]['function']) && 'trigger_deprecation' === $trace[2]['function']) { - $file = $trace[2]['file']; - array_splice($trace, 1, 1); + switch ($trace[2]['function'] ?? '') { + case 'trigger_deprecation': + $file = $trace[2]['file']; + array_splice($trace, 1, 1); + break; + + case 'delegateTriggerToBackend': + if (DoctrineDeprecation::class === ($trace[2]['class'] ?? '')) { + $file = $trace[3]['file']; + array_splice($trace, 1, 2); + } + break; } $this->trace = $trace; diff --git a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php index 50f62b9abcfef..85e4f0286e5f5 100644 --- a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php +++ b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit.php @@ -98,10 +98,8 @@ }; if (\PHP_VERSION_ID >= 80000) { - // PHP 8 requires PHPUnit 9.3+, PHP 8.1 requires PHPUnit 9.5+ - $PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '9.5') ?: '9.5'; + $PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '9.6') ?: '9.6'; } elseif (\PHP_VERSION_ID >= 70200) { - // PHPUnit 8 requires PHP 7.2+ $PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '8.5') ?: '8.5'; } else { $PHPUNIT_VERSION = $getEnvVar('SYMFONY_PHPUNIT_VERSION', '7.5') ?: '7.5'; @@ -373,7 +371,7 @@ class_exists(\SymfonyExcludeListSimplePhpunit::class, false) && PHPUnit\Util\Bla } } -$cmd[0] = sprintf('%s %s --colors=always', $PHP, escapeshellarg("$PHPUNIT_DIR/$PHPUNIT_VERSION_DIR/phpunit")); +$cmd[0] = sprintf('%s %s --colors=%s', $PHP, escapeshellarg("$PHPUNIT_DIR/$PHPUNIT_VERSION_DIR/phpunit"), false === $getEnvVar('NO_COLOR') ? 'always' : 'never'); $cmd = str_replace('%', '%%', implode(' ', $cmd)).' %1$s'; if ('\\' === \DIRECTORY_SEPARATOR) { @@ -440,7 +438,7 @@ class SymfonyExcludeListSimplePhpunit { } } - array_splice($argv, 1, 0, ['--colors=always']); + array_splice($argv, 1, 0, ['--colors='.(false === $getEnvVar('NO_COLOR') ? 'always' : 'never')]); $_SERVER['argv'] = $argv; $_SERVER['argc'] = ++$argc; include "$PHPUNIT_DIR/$PHPUNIT_VERSION_DIR/phpunit"; diff --git a/src/Symfony/Bridge/PhpUnit/bootstrap.php b/src/Symfony/Bridge/PhpUnit/bootstrap.php index f2064368f41a3..2541cdd121a0c 100644 --- a/src/Symfony/Bridge/PhpUnit/bootstrap.php +++ b/src/Symfony/Bridge/PhpUnit/bootstrap.php @@ -10,10 +10,11 @@ */ use Doctrine\Common\Annotations\AnnotationRegistry; +use Doctrine\Deprecations\Deprecation; use Symfony\Bridge\PhpUnit\DeprecationErrorHandler; // Detect if we need to serialize deprecations to a file. -if ($file = getenv('SYMFONY_DEPRECATIONS_SERIALIZE')) { +if (in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && $file = getenv('SYMFONY_DEPRECATIONS_SERIALIZE')) { DeprecationErrorHandler::collectDeprecations($file); return; @@ -24,9 +25,22 @@ return; } +if (isset($fileIdentifier)) { + unset($GLOBALS['__composer_autoload_files'][$fileIdentifier]); +} + // Enforce a consistent locale setlocale(\LC_ALL, 'C'); +if (class_exists(Deprecation::class)) { + Deprecation::withoutDeduplication(); + + if (\PHP_VERSION_ID < 80000) { + // Ignore deprecations about the annotation mapping driver when it's not possible to move to the attribute driver yet + Deprecation::ignoreDeprecations('https://github.com/doctrine/orm/issues/10098'); + } +} + if (!class_exists(AnnotationRegistry::class, false) && class_exists(AnnotationRegistry::class)) { if (method_exists(AnnotationRegistry::class, 'registerUniqueLoader')) { AnnotationRegistry::registerUniqueLoader('class_exists'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php index 4f68228ee5200..5cc13269ea301 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AboutCommand.php @@ -101,6 +101,10 @@ private static function formatFileSize(string $path): string if (is_file($path)) { $size = filesize($path) ?: 0; } else { + if (!is_dir($path)) { + return 'n/a'; + } + $size = 0; foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS | \RecursiveDirectoryIterator::FOLLOW_SYMLINKS)) as $file) { if ($file->isReadable()) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php index 5774a7c6f5906..f6f5bb23ba20e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AbstractConfigCommand.php @@ -52,6 +52,44 @@ protected function listBundles(OutputInterface|StyleInterface $output) } } + protected function listNonBundleExtensions(OutputInterface|StyleInterface $output): void + { + $title = 'Available registered non-bundle extension aliases'; + $headers = ['Extension alias']; + $rows = []; + + $kernel = $this->getApplication()->getKernel(); + + $bundleExtensions = []; + foreach ($kernel->getBundles() as $bundle) { + if ($extension = $bundle->getContainerExtension()) { + $bundleExtensions[\get_class($extension)] = true; + } + } + + $extensions = $this->getContainerBuilder($kernel)->getExtensions(); + + foreach ($extensions as $alias => $extension) { + if (isset($bundleExtensions[\get_class($extension)])) { + continue; + } + $rows[] = [$alias]; + } + + if (!$rows) { + return; + } + + if ($output instanceof StyleInterface) { + $output->title($title); + $output->table($headers, $rows); + } else { + $output->writeln($title); + $table = new Table($output); + $table->setHeaders($headers)->setRows($rows)->render(); + } + } + protected function findExtension(string $name): ExtensionInterface { $bundles = $this->initializeBundles(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/BuildDebugContainerTrait.php b/src/Symfony/Bundle/FrameworkBundle/Command/BuildDebugContainerTrait.php index 04e3c3c456194..83268ae71f1ef 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/BuildDebugContainerTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/BuildDebugContainerTrait.php @@ -50,7 +50,14 @@ protected function getContainerBuilder(KernelInterface $kernel): ContainerBuilde $container->getCompilerPassConfig()->setAfterRemovingPasses([]); $container->compile(); } else { - (new XmlFileLoader($container = new ContainerBuilder(), new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump')); + $buildContainer = \Closure::bind(function () { + $containerBuilder = $this->getContainerBuilder(); + $this->prepareContainer($containerBuilder); + + return $containerBuilder; + }, $kernel, \get_class($kernel)); + $container = $buildContainer(); + (new XmlFileLoader($container, new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump')); $locatorPass = new ServiceLocatorTagPass(); $locatorPass->process($container); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php index f3eab9d6085cc..3982d78a8be29 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDebugCommand.php @@ -49,7 +49,7 @@ protected function configure(): void new InputArgument('name', InputArgument::OPTIONAL, 'The bundle name or the extension alias'), new InputArgument('path', InputArgument::OPTIONAL, 'The configuration option path'), new InputOption('resolve-env', null, InputOption::VALUE_NONE, 'Display resolved environment variable values instead of placeholders'), - new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), class_exists(Yaml::class) ? 'yaml' : 'json'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), class_exists(Yaml::class) ? 'txt' : 'json'), ]) ->setHelp(<<%command.name% command dumps the current configuration for an @@ -81,14 +81,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (null === $name = $input->getArgument('name')) { $this->listBundles($errorIo); - - $kernel = $this->getApplication()->getKernel(); - if ($kernel instanceof ExtensionInterface - && ($kernel instanceof ConfigurationInterface || $kernel instanceof ConfigurationExtensionInterface) - && $kernel->getAlias() - ) { - $errorIo->table(['Kernel Extension'], [[$kernel->getAlias()]]); - } + $this->listNonBundleExtensions($errorIo); $errorIo->comment('Provide the name of a bundle as the first argument of this command to dump its configuration. (e.g. debug:config FrameworkBundle)'); $errorIo->comment('For dumping a specific option, add its path as the second argument of this command. (e.g. debug:config FrameworkBundle serializer to dump the framework.serializer configuration)'); @@ -104,16 +97,18 @@ protected function execute(InputInterface $input, OutputInterface $output): int $format = $input->getOption('format'); - if ('yaml' === $format && !class_exists(Yaml::class)) { - $errorIo->error('Setting the "format" option to "yaml" requires the Symfony Yaml component. Try running "composer install symfony/yaml" or use "--format=json" instead.'); + if (\in_array($format, ['txt', 'yml'], true) && !class_exists(Yaml::class)) { + $errorIo->error('Setting the "format" option to "txt" or "yaml" requires the Symfony Yaml component. Try running "composer install symfony/yaml" or use "--format=json" instead.'); return 1; } if (null === $path = $input->getArgument('path')) { - $io->title( - sprintf('Current configuration for %s', $name === $extensionAlias ? sprintf('extension with alias "%s"', $extensionAlias) : sprintf('"%s"', $name)) - ); + if ('txt' === $input->getOption('format')) { + $io->title( + sprintf('Current configuration for %s', $name === $extensionAlias ? sprintf('extension with alias "%s"', $extensionAlias) : sprintf('"%s"', $name)) + ); + } $io->writeln($this->convertToFormat([$extensionAlias => $config], $format)); @@ -138,7 +133,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int private function convertToFormat(mixed $config, string $format): string { return match ($format) { - 'yaml' => Yaml::dump($config, 10), + 'txt', 'yaml' => Yaml::dump($config, 10), 'json' => json_encode($config, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE), default => throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))), }; @@ -194,12 +189,12 @@ private function getConfigForExtension(ExtensionInterface $extension, ContainerB // Fall back to default config if the extension has one - if (!$extension instanceof ConfigurationExtensionInterface) { + if (!$extension instanceof ConfigurationExtensionInterface && !$extension instanceof ConfigurationInterface) { throw new \LogicException(sprintf('The extension with alias "%s" does not have configuration.', $extensionAlias)); } $configs = $container->getExtensionConfig($extensionAlias); - $configuration = $extension->getConfiguration($configs, $container); + $configuration = $extension instanceof ConfigurationInterface ? $extension : $extension->getConfiguration($configs, $container); $this->validateConfiguration($extension, $configuration); return (new Processor())->processConfiguration($configuration, $configs); @@ -208,7 +203,8 @@ private function getConfigForExtension(ExtensionInterface $extension, ContainerB public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { if ($input->mustSuggestArgumentValuesFor('name')) { - $suggestions->suggestValues($this->getAvailableBundles(!preg_match('/^[A-Z]/', $input->getCompletionValue()))); + $suggestions->suggestValues($this->getAvailableExtensions()); + $suggestions->suggestValues($this->getAvailableBundles()); return; } @@ -227,11 +223,23 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti } } - private function getAvailableBundles(bool $alias): array + private function getAvailableExtensions(): array + { + $kernel = $this->getApplication()->getKernel(); + + $extensions = []; + foreach ($this->getContainerBuilder($kernel)->getExtensions() as $alias => $extension) { + $extensions[] = $alias; + } + + return $extensions; + } + + private function getAvailableBundles(): array { $availableBundles = []; foreach ($this->getApplication()->getKernel()->getBundles() as $bundle) { - $availableBundles[] = $alias ? $bundle->getContainerExtension()->getAlias() : $bundle->getName(); + $availableBundles[] = $bundle->getName(); } return $availableBundles; @@ -262,6 +270,6 @@ private static function buildPathsCompletion(array $paths, string $prefix = ''): private function getAvailableFormatOptions(): array { - return ['yaml', 'json']; + return ['txt', 'yaml', 'json']; } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php index 11a0668561a22..59219905bf4cd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php @@ -23,8 +23,6 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface; -use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\Yaml\Yaml; /** @@ -83,14 +81,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (null === $name = $input->getArgument('name')) { $this->listBundles($errorIo); - - $kernel = $this->getApplication()->getKernel(); - if ($kernel instanceof ExtensionInterface - && ($kernel instanceof ConfigurationInterface || $kernel instanceof ConfigurationExtensionInterface) - && $kernel->getAlias() - ) { - $errorIo->table(['Kernel Extension'], [[$kernel->getAlias()]]); - } + $this->listNonBundleExtensions($errorIo); $errorIo->comment([ 'Provide the name of a bundle as the first argument of this command to dump its default configuration. (e.g. config:dump-reference FrameworkBundle)', @@ -158,6 +149,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { if ($input->mustSuggestArgumentValuesFor('name')) { + $suggestions->suggestValues($this->getAvailableExtensions()); $suggestions->suggestValues($this->getAvailableBundles()); } @@ -166,13 +158,24 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti } } + private function getAvailableExtensions(): array + { + $kernel = $this->getApplication()->getKernel(); + + $extensions = []; + foreach ($this->getContainerBuilder($kernel)->getExtensions() as $alias => $extension) { + $extensions[] = $alias; + } + + return $extensions; + } + private function getAvailableBundles(): array { $bundles = []; foreach ($this->getApplication()->getKernel()->getBundles() as $bundle) { $bundles[] = $bundle->getName(); - $bundles[] = $bundle->getContainerExtension()->getAlias(); } return $bundles; diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php index abfe2064f8222..8422d2c91a023 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/SecretsListCommand.php @@ -86,7 +86,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } foreach ($localSecrets ?? [] as $name => $value) { - if (isset($rows[$name]) && !\in_array($value, ['', false, null], true)) { + if (isset($rows[$name])) { $rows[$name][] = $dump($value); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php index 09f272daa940e..aed3b13404bd5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php @@ -41,6 +41,9 @@ public function process(ContainerBuilder $container) if ($id !== $target) { $renamedIds[$id] = $target; } + if ($inner = $definitions[$target]->getTag('container.decorator')[0]['inner'] ?? null) { + $renamedIds[$id] = $inner; + } } else { unset($privateServices[$id]); } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php index b40c759eb4d37..b04516410fbf4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php @@ -86,7 +86,6 @@ class UnusedTagsPass implements CompilerPassInterface 'scheduler.schedule_provider', 'security.authenticator.login_linker', 'security.expression_language_provider', - 'security.remember_me_aware', 'security.remember_me_handler', 'security.voter', 'serializer.encoder', diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index ff09beae59aa4..981a865672296 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -342,6 +342,8 @@ public function load(array $configs, ContainerBuilder $container) } $this->registerAssetMapperConfiguration($config['asset_mapper'], $container, $loader, $this->readConfigEnabled('assets', $container, $config['assets'])); + } else { + $container->removeDefinition('cache.asset_mapper'); } if ($this->readConfigEnabled('http_client', $container, $config['http_client'])) { @@ -485,6 +487,7 @@ public function load(array $configs, ContainerBuilder $container) } $this->registerSchedulerConfiguration($config['scheduler'], $container, $loader); } else { + $container->removeDefinition('cache.scheduler'); $container->removeDefinition('console.command.scheduler_debug'); } diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index 36e5cd4028010..339b28e83c695 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -98,7 +98,13 @@ public function boot() $_ENV['DOCTRINE_DEPRECATIONS'] = $_SERVER['DOCTRINE_DEPRECATIONS'] ??= 'trigger'; $handler = ErrorHandler::register(null, false); - $this->container->get('debug.error_handler_configurator')->configure($handler); + + // When upgrading an existing Symfony application from 6.2 to 6.3, and + // the cache is warmed up, the service is not available yet, so we need + // to check if it exists. + if ($this->container->has('debug.error_handler_configurator')) { + $this->container->get('debug.error_handler_configurator')->configure($handler); + } if ($this->container->getParameter('kernel.http_method_override')) { Request::enableHttpMethodParameterOverride(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php index 0096ff1e099f3..3fe593ac673ff 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.php @@ -209,6 +209,9 @@ ->tag('kernel.event_subscriber') ->tag('monolog.logger', ['channel' => 'messenger']) + ->alias('messenger.listener.stop_worker_on_sigterm_signal_listener', 'messenger.listener.stop_worker_signals_listener') + ->deprecate('6.3', 'symfony/messenger', 'The "%alias_id%" service is deprecated, use "messenger.listener.stop_worker_signals_listener" instead.') + ->set('messenger.listener.stop_worker_on_stop_exception_listener', StopWorkerOnCustomStopExceptionListener::class) ->tag('kernel.event_subscriber') diff --git a/src/Symfony/Bundle/FrameworkBundle/Secrets/DotenvVault.php b/src/Symfony/Bundle/FrameworkBundle/Secrets/DotenvVault.php index db4891c34b337..994b31d18be59 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Secrets/DotenvVault.php +++ b/src/Symfony/Bundle/FrameworkBundle/Secrets/DotenvVault.php @@ -52,9 +52,9 @@ public function reveal(string $name): ?string { $this->lastMessage = null; $this->validateName($name); - $v = \is_string($_SERVER[$name] ?? null) && !str_starts_with($name, 'HTTP_') ? $_SERVER[$name] : ($_ENV[$name] ?? null); + $v = $_ENV[$name] ?? (str_starts_with($name, 'HTTP_') ? null : ($_SERVER[$name] ?? null)); - if (null === $v) { + if ('' === ($v ?? '')) { $this->lastMessage = sprintf('Secret "%s" not found in "%s".', $name, $this->getPrettyPath($this->dotenvFile)); return null; @@ -89,13 +89,13 @@ public function list(bool $reveal = false): array $secrets = []; foreach ($_ENV as $k => $v) { - if (preg_match('/^\w+$/D', $k)) { + if ('' !== ($v ?? '') && preg_match('/^\w+$/D', $k)) { $secrets[$k] = $reveal ? $v : null; } } foreach ($_SERVER as $k => $v) { - if (\is_string($v) && preg_match('/^\w+$/D', $k)) { + if ('' !== ($v ?? '') && preg_match('/^\w+$/D', $k)) { $secrets[$k] = $reveal ? $v : null; } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/SecretsListCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/SecretsListCommandTest.php index 933cafcf7c74c..12d3ab2e8ac28 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/SecretsListCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/SecretsListCommandTest.php @@ -14,16 +14,22 @@ use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\Command\SecretsListCommand; use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; +use Symfony\Bundle\FrameworkBundle\Secrets\DotenvVault; use Symfony\Component\Console\Tester\CommandTester; class SecretsListCommandTest extends TestCase { + /** + * @backupGlobals enabled + */ public function testExecute() { $vault = $this->createMock(AbstractVault::class); $vault->method('list')->willReturn(['A' => 'a', 'B' => 'b', 'C' => null, 'D' => null, 'E' => null]); - $localVault = $this->createMock(AbstractVault::class); - $localVault->method('list')->willReturn(['A' => '', 'B' => 'A', 'C' => '', 'D' => false, 'E' => null]); + + $_ENV = ['A' => '', 'B' => 'A', 'C' => '', 'D' => false, 'E' => null]; + $localVault = new DotenvVault('/not/a/path'); + $command = new SecretsListCommand($vault, $localVault); $tester = new CommandTester($command); $this->assertSame(0, $tester->execute([])); @@ -37,9 +43,9 @@ public function testExecute() Secret Value Local Value -------- -------- ------------- A "a" - B "b" "A" + B "b" ****** C ****** - D ****** + D ****** ****** E ****** -------- -------- ------------- diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestServiceContainer/PrivateService.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestServiceContainer/PrivateService.php index 6a244cb40ce54..51168b7ea6c24 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestServiceContainer/PrivateService.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/TestServiceContainer/PrivateService.php @@ -13,4 +13,5 @@ class PrivateService { + public $inner; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDebugCommandTest.php index 307b34a395299..f81c32f662645 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDebugCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDebugCommandTest.php @@ -24,39 +24,89 @@ */ class ConfigDebugCommandTest extends AbstractWebTestCase { - private $application; + /** + * @testWith [true] + * [false] + */ + public function testShowList(bool $debug) + { + $tester = $this->createCommandTester($debug); + $ret = $tester->execute([]); + + $this->assertSame(0, $ret, 'Returns 0 in case of success'); + $this->assertStringContainsString('Available registered bundles with their extension alias if available', $tester->getDisplay()); + $this->assertStringContainsString(' DefaultConfigTestBundle default_config_test', $tester->getDisplay()); + $this->assertStringContainsString(' ExtensionWithoutConfigTestBundle extension_without_config_test', $tester->getDisplay()); + $this->assertStringContainsString(' FrameworkBundle framework', $tester->getDisplay()); + $this->assertStringContainsString(' TestBundle test', $tester->getDisplay()); + $this->assertStringContainsString('Available registered non-bundle extension aliases', $tester->getDisplay()); + $this->assertStringContainsString(' foo', $tester->getDisplay()); + $this->assertStringContainsString(' test_dump', $tester->getDisplay()); + } - protected function setUp(): void + /** + * @testWith [true] + * [false] + */ + public function testDumpKernelExtension(bool $debug) { - $kernel = static::createKernel(['test_case' => 'ConfigDump', 'root_config' => 'config.yml']); - $this->application = new Application($kernel); - $this->application->doRun(new ArrayInput([]), new NullOutput()); + $tester = $this->createCommandTester($debug); + $ret = $tester->execute(['name' => 'foo']); + + $this->assertSame(0, $ret, 'Returns 0 in case of success'); + $this->assertStringContainsString('foo:', $tester->getDisplay()); + $this->assertStringContainsString(' foo: bar', $tester->getDisplay()); } - public function testDumpBundleName() + /** + * @testWith [true] + * [false] + */ + public function testDumpBundleName(bool $debug) { - $tester = $this->createCommandTester(); + $tester = $this->createCommandTester($debug); $ret = $tester->execute(['name' => 'TestBundle']); $this->assertSame(0, $ret, 'Returns 0 in case of success'); $this->assertStringContainsString('custom: foo', $tester->getDisplay()); } - public function testDumpBundleOption() + /** + * @testWith [true] + * [false] + */ + public function testDumpBundleOption(bool $debug) { - $tester = $this->createCommandTester(); + $tester = $this->createCommandTester($debug); $ret = $tester->execute(['name' => 'TestBundle', 'path' => 'custom']); $this->assertSame(0, $ret, 'Returns 0 in case of success'); $this->assertStringContainsString('foo', $tester->getDisplay()); } - public function testDumpWithUnsupportedFormat() + /** + * @testWith [true] + * [false] + */ + public function testDumpWithoutTitleIsValidJson(bool $debug) + { + $tester = $this->createCommandTester($debug); + $ret = $tester->execute(['name' => 'TestBundle', '--format' => 'json']); + + $this->assertSame(0, $ret, 'Returns 0 in case of success'); + $this->assertJson($tester->getDisplay()); + } + + /** + * @testWith [true] + * [false] + */ + public function testDumpWithUnsupportedFormat(bool $debug) { - $tester = $this->createCommandTester(); + $tester = $this->createCommandTester($debug); $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Supported formats are "yaml", "json"'); + $this->expectExceptionMessage('Supported formats are "txt", "yaml", "json"'); $tester->execute([ 'name' => 'test', @@ -64,9 +114,13 @@ public function testDumpWithUnsupportedFormat() ]); } - public function testParametersValuesAreResolved() + /** + * @testWith [true] + * [false] + */ + public function testParametersValuesAreResolved(bool $debug) { - $tester = $this->createCommandTester(); + $tester = $this->createCommandTester($debug); $ret = $tester->execute(['name' => 'framework']); $this->assertSame(0, $ret, 'Returns 0 in case of success'); @@ -74,9 +128,13 @@ public function testParametersValuesAreResolved() $this->assertStringContainsString('secret: test', $tester->getDisplay()); } - public function testParametersValuesAreFullyResolved() + /** + * @testWith [true] + * [false] + */ + public function testParametersValuesAreFullyResolved(bool $debug) { - $tester = $this->createCommandTester(); + $tester = $this->createCommandTester($debug); $ret = $tester->execute(['name' => 'framework', '--resolve-env' => true]); $this->assertSame(0, $ret, 'Returns 0 in case of success'); @@ -86,77 +144,105 @@ public function testParametersValuesAreFullyResolved() $this->assertStringContainsString('ide: '.($_ENV['SYMFONY_IDE'] ?? $_SERVER['SYMFONY_IDE'] ?? 'null'), $tester->getDisplay()); } - public function testDefaultParameterValueIsResolvedIfConfigIsExisting() + /** + * @testWith [true] + * [false] + */ + public function testDefaultParameterValueIsResolvedIfConfigIsExisting(bool $debug) { - $tester = $this->createCommandTester(); + $tester = $this->createCommandTester($debug); $ret = $tester->execute(['name' => 'framework']); $this->assertSame(0, $ret, 'Returns 0 in case of success'); - $kernelCacheDir = $this->application->getKernel()->getContainer()->getParameter('kernel.cache_dir'); + $kernelCacheDir = self::$kernel->getContainer()->getParameter('kernel.cache_dir'); $this->assertStringContainsString(sprintf("dsn: 'file:%s/profiler'", $kernelCacheDir), $tester->getDisplay()); } - public function testDumpExtensionConfigWithoutBundle() + /** + * @testWith [true] + * [false] + */ + public function testDumpExtensionConfigWithoutBundle(bool $debug) { - $tester = $this->createCommandTester(); + $tester = $this->createCommandTester($debug); $ret = $tester->execute(['name' => 'test_dump']); $this->assertSame(0, $ret, 'Returns 0 in case of success'); $this->assertStringContainsString('enabled: true', $tester->getDisplay()); } - public function testDumpUndefinedBundleOption() + /** + * @testWith [true] + * [false] + */ + public function testDumpUndefinedBundleOption(bool $debug) { - $tester = $this->createCommandTester(); + $tester = $this->createCommandTester($debug); $tester->execute(['name' => 'TestBundle', 'path' => 'foo']); $this->assertStringContainsString('Unable to find configuration for "test.foo"', $tester->getDisplay()); } - public function testDumpWithPrefixedEnv() + /** + * @testWith [true] + * [false] + */ + public function testDumpWithPrefixedEnv(bool $debug) { - $tester = $this->createCommandTester(); + $tester = $this->createCommandTester($debug); $tester->execute(['name' => 'FrameworkBundle']); $this->assertStringContainsString("cookie_httponly: '%env(bool:COOKIE_HTTPONLY)%'", $tester->getDisplay()); } - public function testDumpFallsBackToDefaultConfigAndResolvesParameterValue() + /** + * @testWith [true] + * [false] + */ + public function testDumpFallsBackToDefaultConfigAndResolvesParameterValue(bool $debug) { - $tester = $this->createCommandTester(); + $tester = $this->createCommandTester($debug); $ret = $tester->execute(['name' => 'DefaultConfigTestBundle']); $this->assertSame(0, $ret, 'Returns 0 in case of success'); $this->assertStringContainsString('foo: bar', $tester->getDisplay()); } - public function testDumpFallsBackToDefaultConfigAndResolvesEnvPlaceholder() + /** + * @testWith [true] + * [false] + */ + public function testDumpFallsBackToDefaultConfigAndResolvesEnvPlaceholder(bool $debug) { - $tester = $this->createCommandTester(); + $tester = $this->createCommandTester($debug); $ret = $tester->execute(['name' => 'DefaultConfigTestBundle']); $this->assertSame(0, $ret, 'Returns 0 in case of success'); $this->assertStringContainsString("baz: '%env(BAZ)%'", $tester->getDisplay()); } - public function testDumpThrowsExceptionWhenDefaultConfigFallbackIsImpossible() + /** + * @testWith [true] + * [false] + */ + public function testDumpThrowsExceptionWhenDefaultConfigFallbackIsImpossible(bool $debug) { $this->expectException(\LogicException::class); $this->expectExceptionMessage('The extension with alias "extension_without_config_test" does not have configuration.'); - $tester = $this->createCommandTester(); + $tester = $this->createCommandTester($debug); $tester->execute(['name' => 'ExtensionWithoutConfigTestBundle']); } /** * @dataProvider provideCompletionSuggestions */ - public function testComplete(array $input, array $expectedSuggestions) + public function testComplete(bool $debug, array $input, array $expectedSuggestions) { - $this->application->add(new ConfigDebugCommand()); - - $tester = new CommandCompletionTester($this->application->get('debug:config')); + $application = $this->createApplication($debug); + $application->add(new ConfigDebugCommand()); + $tester = new CommandCompletionTester($application->get('debug:config')); $suggestions = $tester->complete($input); foreach ($expectedSuggestions as $expectedSuggestion) { @@ -166,19 +252,31 @@ public function testComplete(array $input, array $expectedSuggestions) public static function provideCompletionSuggestions(): \Generator { - yield 'name' => [[''], ['default_config_test', 'extension_without_config_test', 'framework', 'test']]; + $name = ['default_config_test', 'extension_without_config_test', 'framework', 'test', 'foo', 'test_dump']; + yield 'name, no debug' => [false, [''], $name]; + yield 'name, debug' => [true, [''], $name]; - yield 'name (started CamelCase)' => [['Fra'], ['DefaultConfigTestBundle', 'ExtensionWithoutConfigTestBundle', 'FrameworkBundle', 'TestBundle']]; + $nameWithPath = ['secret', 'router.resource', 'router.utf8', 'router.enabled', 'validation.enabled', 'default_locale']; + yield 'name with existing path, no debug' => [false, ['framework', ''], $nameWithPath]; + yield 'name with existing path, debug' => [true, ['framework', ''], $nameWithPath]; - yield 'name with existing path' => [['framework', ''], ['secret', 'router.resource', 'router.utf8', 'router.enabled', 'validation.enabled', 'default_locale']]; - - yield 'option --format' => [['--format', ''], ['yaml', 'json']]; + yield 'option --format, no debug' => [false, ['--format', ''], ['yaml', 'json']]; + yield 'option --format, debug' => [true, ['--format', ''], ['yaml', 'json']]; } - private function createCommandTester(): CommandTester + private function createCommandTester(bool $debug): CommandTester { - $command = $this->application->find('debug:config'); + $command = $this->createApplication($debug)->find('debug:config'); return new CommandTester($command); } + + private function createApplication(bool $debug): Application + { + $kernel = static::bootKernel(['debug' => $debug, 'test_case' => 'ConfigDump', 'root_config' => 'config.yml']); + $application = new Application($kernel); + $application->doRun(new ArrayInput([]), new NullOutput()); + + return $application; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDumpReferenceCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDumpReferenceCommandTest.php index e944960aea4e7..8f5930faac2eb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDumpReferenceCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/ConfigDumpReferenceCommandTest.php @@ -23,26 +23,47 @@ */ class ConfigDumpReferenceCommandTest extends AbstractWebTestCase { - private $application; - - protected function setUp(): void + /** + * @testWith [true] + * [false] + */ + public function testShowList(bool $debug) { - $kernel = static::createKernel(['test_case' => 'ConfigDump', 'root_config' => 'config.yml']); - $this->application = new Application($kernel); - $this->application->doRun(new ArrayInput([]), new NullOutput()); + $tester = $this->createCommandTester($debug); + $ret = $tester->execute([]); + + $this->assertSame(0, $ret, 'Returns 0 in case of success'); + $this->assertStringContainsString('Available registered bundles with their extension alias if available', $tester->getDisplay()); + $this->assertStringContainsString(' DefaultConfigTestBundle default_config_test', $tester->getDisplay()); + $this->assertStringContainsString(' ExtensionWithoutConfigTestBundle extension_without_config_test', $tester->getDisplay()); + $this->assertStringContainsString(' FrameworkBundle framework', $tester->getDisplay()); + $this->assertStringContainsString(' TestBundle test', $tester->getDisplay()); + $this->assertStringContainsString('Available registered non-bundle extension aliases', $tester->getDisplay()); + $this->assertStringContainsString(' foo', $tester->getDisplay()); + $this->assertStringContainsString(' test_dump', $tester->getDisplay()); } - public function testDumpKernelExtension() + /** + * @testWith [true] + * [false] + */ + public function testDumpKernelExtension(bool $debug) { - $tester = $this->createCommandTester(); + $tester = $this->createCommandTester($debug); $ret = $tester->execute(['name' => 'foo']); + + $this->assertSame(0, $ret, 'Returns 0 in case of success'); $this->assertStringContainsString('foo:', $tester->getDisplay()); $this->assertStringContainsString(' bar', $tester->getDisplay()); } - public function testDumpBundleName() + /** + * @testWith [true] + * [false] + */ + public function testDumpBundleName(bool $debug) { - $tester = $this->createCommandTester(); + $tester = $this->createCommandTester($debug); $ret = $tester->execute(['name' => 'TestBundle']); $this->assertSame(0, $ret, 'Returns 0 in case of success'); @@ -50,18 +71,26 @@ public function testDumpBundleName() $this->assertStringContainsString(' custom:', $tester->getDisplay()); } - public function testDumpExtensionConfigWithoutBundle() + /** + * @testWith [true] + * [false] + */ + public function testDumpExtensionConfigWithoutBundle(bool $debug) { - $tester = $this->createCommandTester(); + $tester = $this->createCommandTester($debug); $ret = $tester->execute(['name' => 'test_dump']); $this->assertSame(0, $ret, 'Returns 0 in case of success'); $this->assertStringContainsString('enabled: true', $tester->getDisplay()); } - public function testDumpAtPath() + /** + * @testWith [true] + * [false] + */ + public function testDumpAtPath(bool $debug) { - $tester = $this->createCommandTester(); + $tester = $this->createCommandTester($debug); $ret = $tester->execute([ 'name' => 'test', 'path' => 'array', @@ -79,9 +108,13 @@ public function testDumpAtPath() , $tester->getDisplay(true)); } - public function testDumpAtPathXml() + /** + * @testWith [true] + * [false] + */ + public function testDumpAtPathXml(bool $debug) { - $tester = $this->createCommandTester(); + $tester = $this->createCommandTester($debug); $ret = $tester->execute([ 'name' => 'test', 'path' => 'array', @@ -95,24 +128,40 @@ public function testDumpAtPathXml() /** * @dataProvider provideCompletionSuggestions */ - public function testComplete(array $input, array $expectedSuggestions) + public function testComplete(bool $debug, array $input, array $expectedSuggestions) { - $this->application->add(new ConfigDumpReferenceCommand()); - $tester = new CommandCompletionTester($this->application->get('config:dump-reference')); - $suggestions = $tester->complete($input, 2); + $application = $this->createApplication($debug); + + $application->add(new ConfigDumpReferenceCommand()); + $tester = new CommandCompletionTester($application->get('config:dump-reference')); + $suggestions = $tester->complete($input); $this->assertSame($expectedSuggestions, $suggestions); } public static function provideCompletionSuggestions(): iterable { - yield 'name' => [[''], ['DefaultConfigTestBundle', 'default_config_test', 'ExtensionWithoutConfigTestBundle', 'extension_without_config_test', 'FrameworkBundle', 'framework', 'TestBundle', 'test']]; - yield 'option --format' => [['--format', ''], ['yaml', 'xml']]; + $name = ['foo', 'default_config_test', 'extension_without_config_test', 'framework', 'test', 'test_dump', 'DefaultConfigTestBundle', 'ExtensionWithoutConfigTestBundle', 'FrameworkBundle', 'TestBundle']; + yield 'name, no debug' => [false, [''], $name]; + yield 'name, debug' => [true, [''], $name]; + + $optionFormat = ['yaml', 'xml']; + yield 'option --format, no debug' => [false, ['--format', ''], $optionFormat]; + yield 'option --format, debug' => [true, ['--format', ''], $optionFormat]; } - private function createCommandTester(): CommandTester + private function createCommandTester(bool $debug): CommandTester { - $command = $this->application->find('config:dump-reference'); + $command = $this->createApplication($debug)->find('config:dump-reference'); return new CommandTester($command); } + + private function createApplication(bool $debug): Application + { + $kernel = static::createKernel(['debug' => $debug, 'test_case' => 'ConfigDump', 'root_config' => 'config.yml']); + $application = new Application($kernel); + $application->doRun(new ArrayInput([]), new NullOutput()); + + return $application; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/KernelTestCaseTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/KernelTestCaseTest.php deleted file mode 100644 index aa12467829ed1..0000000000000 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/KernelTestCaseTest.php +++ /dev/null @@ -1,61 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; - -use Symfony\Bundle\FrameworkBundle\Test\TestContainer; -use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestServiceContainer\NonPublicService; -use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestServiceContainer\PrivateService; -use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestServiceContainer\PublicService; -use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestServiceContainer\UnusedPrivateService; -use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; - -class KernelTestCaseTest extends AbstractWebTestCase -{ - public function testThatPrivateServicesAreUnavailableIfTestConfigIsDisabled() - { - static::bootKernel(['test_case' => 'TestServiceContainer', 'root_config' => 'test_disabled.yml', 'environment' => 'test_disabled']); - - $this->expectException(\LogicException::class); - static::getContainer(); - } - - public function testThatPrivateServicesAreAvailableIfTestConfigIsEnabled() - { - static::bootKernel(['test_case' => 'TestServiceContainer']); - - $container = static::getContainer(); - $this->assertInstanceOf(ContainerInterface::class, $container); - $this->assertInstanceOf(TestContainer::class, $container); - $this->assertTrue($container->has(PublicService::class)); - $this->assertTrue($container->has(NonPublicService::class)); - $this->assertTrue($container->has(PrivateService::class)); - $this->assertTrue($container->has('private_service')); - $this->assertFalse($container->has(UnusedPrivateService::class)); - } - - public function testThatPrivateServicesCanBeSetIfTestConfigIsEnabled() - { - static::bootKernel(['test_case' => 'TestServiceContainer']); - - $container = static::getContainer(); - - $service = new \stdClass(); - - $container->set('private_service', $service); - $this->assertSame($service, $container->get('private_service')); - - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "private_service" service is already initialized, you cannot replace it.'); - $container->set('private_service', new \stdClass()); - } -} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/TestServiceContainerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/TestServiceContainerTest.php index 76a373c0c05a9..fe7093081509f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/TestServiceContainerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/TestServiceContainerTest.php @@ -16,6 +16,7 @@ use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestServiceContainer\PrivateService; use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestServiceContainer\PublicService; use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestServiceContainer\UnusedPrivateService; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; class TestServiceContainerTest extends AbstractWebTestCase { @@ -40,6 +41,33 @@ public function testThatPrivateServicesAreAvailableIfTestConfigIsEnabled() $this->assertFalse(static::getContainer()->has(UnusedPrivateService::class)); } + public function testThatPrivateServicesCanBeSetIfTestConfigIsEnabled() + { + static::bootKernel(['test_case' => 'TestServiceContainer']); + + $container = static::getContainer(); + + $service = new \stdClass(); + + $container->set('private_service', $service); + $this->assertSame($service, $container->get('private_service')); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The "private_service" service is already initialized, you cannot replace it.'); + $container->set('private_service', new \stdClass()); + } + + public function testSetDecoratedService() + { + static::bootKernel(['test_case' => 'TestServiceContainer']); + + $container = static::getContainer(); + + $service = new PrivateService(); + $container->set('decorated', $service); + $this->assertSame($service, $container->get('decorated')->inner); + } + /** * @doesNotPerformAssertions */ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/TestServiceContainer/services.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/TestServiceContainer/services.yml index 523cca58d0b63..46cb7be6a8f6f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/TestServiceContainer/services.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/TestServiceContainer/services.yml @@ -8,8 +8,18 @@ services: private_service: '@Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestServiceContainer\PrivateService' + decorated: + class: Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestServiceContainer\PrivateService + Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestServiceContainer\PublicService: public: true arguments: - '@Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestServiceContainer\NonPublicService' - '@Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestServiceContainer\PrivateService' + - '@decorated' + + decorator: + class: Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestServiceContainer\PrivateService + decorates: decorated + properties: + inner: '@.inner' diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 9f594f254f035..0fcc602c3975d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -21,7 +21,7 @@ "ext-xml": "*", "symfony/cache": "^5.4|^6.0", "symfony/config": "^6.1", - "symfony/dependency-injection": "^6.3", + "symfony/dependency-injection": "^6.3.1", "symfony/deprecation-contracts": "^2.5|^3", "symfony/error-handler": "^6.1", "symfony/event-dispatcher": "^5.4|^6.0", diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/OidcTokenHandlerFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/OidcTokenHandlerFactory.php index 18a5ee6ac3eef..cc51398069cdf 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/OidcTokenHandlerFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/OidcTokenHandlerFactory.php @@ -16,6 +16,7 @@ use Symfony\Component\Config\Definition\Builder\NodeBuilder; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Reference; /** @@ -34,10 +35,7 @@ public function create(ContainerBuilder $container, string $id, array|string $co ); if (!ContainerBuilder::willBeAvailable('web-token/jwt-core', Algorithm::class, ['symfony/security-bundle'])) { - $container->register('security.access_token_handler.oidc.signature', Algorithm::class) - ->addError('You cannot use the "oidc" token handler since "web-token/jwt-core" is not installed. Try running "web-token/jwt-core".'); - $container->register('security.access_token_handler.oidc.jwk', JWK::class) - ->addError('You cannot use the "oidc" token handler since "web-token/jwt-core" is not installed. Try running "web-token/jwt-core".'); + throw new LogicException('You cannot use the "oidc" token handler since "web-token/jwt-core" is not installed. Try running "composer require web-token/jwt-core".'); } // @see Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SignatureAlgorithmFactory diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/OidcUserInfoTokenHandlerFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/OidcUserInfoTokenHandlerFactory.php index 78bc45224db33..88e6372f138f8 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/OidcUserInfoTokenHandlerFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/OidcUserInfoTokenHandlerFactory.php @@ -14,6 +14,7 @@ use Symfony\Component\Config\Definition\Builder\NodeBuilder; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Reference; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -32,9 +33,7 @@ public function create(ContainerBuilder $container, string $id, array|string $co if (isset($config['client'])) { $clientDefinition->setFactory([new Reference($config['client']), 'withOptions']); } elseif (!ContainerBuilder::willBeAvailable('symfony/http-client', HttpClientInterface::class, ['symfony/security-bundle'])) { - $clientDefinition - ->setFactory(null) - ->addError('You cannot use the "oidc_user_info" token handler since the HttpClient component is not installed. Try running "composer require symfony/http-client".'); + throw new LogicException('You cannot use the "oidc_user_info" token handler since the HttpClient component is not installed. Try running "composer require symfony/http-client".'); } $container->setDefinition($id, new ChildDefinition('security.access_token_handler.oidc_user_info')) diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php index ca9f0ae1787bd..95b59c3e5c248 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php @@ -110,15 +110,6 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal ->replaceArgument(3, $config['name'] ?? $this->options['name']) ; - foreach ($container->findTaggedServiceIds('security.remember_me_aware') as $serviceId => $attributes) { - // register ContextListener - if (str_starts_with($serviceId, 'security.context_listener')) { - continue; - } - - throw new \LogicException(sprintf('Symfony Authenticator Security dropped support for the "security.remember_me_aware" tag, service "%s" will no longer work as expected.', $serviceId)); - } - return $authenticatorId; } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SignatureAlgorithmFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SignatureAlgorithmFactory.php index a81d662cd338c..e6dc8b3010d68 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SignatureAlgorithmFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SignatureAlgorithmFactory.php @@ -19,6 +19,8 @@ /** * Creates a signature algorithm for {@see OidcTokenHandler}. * + * @internal + * * @experimental */ final class SignatureAlgorithmFactory diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 37978b285f3d7..ddb1d3cc92b06 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -428,11 +428,10 @@ private function createFirewall(ContainerBuilder $container, string $id, array $ $listeners[] = new Reference('security.channel_listener'); $contextKey = null; - $contextListenerId = null; // Context serializer listener if (false === $firewall['stateless']) { $contextKey = $firewall['context'] ?? $id; - $listeners[] = new Reference($contextListenerId = $this->createContextListener($container, $contextKey, $firewallEventDispatcherId)); + $listeners[] = new Reference($this->createContextListener($container, $contextKey, $firewallEventDispatcherId)); $sessionStrategyId = 'security.authentication.session_strategy'; $container @@ -507,7 +506,7 @@ private function createFirewall(ContainerBuilder $container, string $id, array $ // Authentication listeners $firewallAuthenticationProviders = []; - [$authListeners, $defaultEntryPoint] = $this->createAuthenticationListeners($container, $id, $firewall, $firewallAuthenticationProviders, $defaultProvider, $providerIds, $configuredEntryPoint, $contextListenerId); + [$authListeners, $defaultEntryPoint] = $this->createAuthenticationListeners($container, $id, $firewall, $firewallAuthenticationProviders, $defaultProvider, $providerIds, $configuredEntryPoint); // $configuredEntryPoint is resolved into a service ID and stored in $defaultEntryPoint $configuredEntryPoint = $defaultEntryPoint; @@ -611,7 +610,7 @@ private function createContextListener(ContainerBuilder $container, string $cont return $this->contextListeners[$contextKey] = $listenerId; } - private function createAuthenticationListeners(ContainerBuilder $container, string $id, array $firewall, array &$authenticationProviders, ?string $defaultProvider, array $providerIds, ?string $defaultEntryPoint, string $contextListenerId = null): array + private function createAuthenticationListeners(ContainerBuilder $container, string $id, array $firewall, array &$authenticationProviders, ?string $defaultProvider, array $providerIds, ?string $defaultEntryPoint): array { $listeners = []; $entryPoints = []; @@ -620,7 +619,7 @@ private function createAuthenticationListeners(ContainerBuilder $container, stri $key = str_replace('-', '_', $factory->getKey()); if (isset($firewall[$key])) { - $userProvider = $this->getUserProvider($container, $id, $firewall, $key, $defaultProvider, $providerIds, $contextListenerId); + $userProvider = $this->getUserProvider($container, $id, $firewall, $key, $defaultProvider, $providerIds); if (!$factory instanceof AuthenticatorFactoryInterface) { throw new InvalidConfigurationException(sprintf('Authenticator factory "%s" ("%s") must implement "%s".', get_debug_type($factory), $key, AuthenticatorFactoryInterface::class)); @@ -656,7 +655,7 @@ private function createAuthenticationListeners(ContainerBuilder $container, stri return [$listeners, $defaultEntryPoint]; } - private function getUserProvider(ContainerBuilder $container, string $id, array $firewall, string $factoryKey, ?string $defaultProvider, array $providerIds, ?string $contextListenerId): ?string + private function getUserProvider(ContainerBuilder $container, string $id, array $firewall, string $factoryKey, ?string $defaultProvider, array $providerIds): ?string { if (isset($firewall[$factoryKey]['provider'])) { if (!isset($providerIds[$normalizedName = str_replace('-', '_', $firewall[$factoryKey]['provider'])])) { @@ -666,10 +665,6 @@ private function getUserProvider(ContainerBuilder $container, string $id, array return $providerIds[$normalizedName]; } - if ('remember_me' === $factoryKey && $contextListenerId) { - $container->getDefinition($contextListenerId)->addTag('security.remember_me_aware', ['id' => $id, 'provider' => 'none']); - } - if ($defaultProvider) { return $defaultProvider; } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AccessTokenFactoryTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AccessTokenFactoryTest.php index 4607830e73916..e733c7efc644b 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AccessTokenFactoryTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AccessTokenFactoryTest.php @@ -20,6 +20,7 @@ use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Reference; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -129,9 +130,8 @@ public function testOidcUserInfoTokenHandlerConfigurationWithBaseUri(array|strin ]; if (!interface_exists(HttpClientInterface::class)) { - $expected['index_0'] - ->setFactory(null) - ->addError('You cannot use the "oidc_user_info" token handler since the HttpClient component is not installed. Try running "composer require symfony/http-client".'); + $this->expectException(LogicException::class); + $this->expectExceptionMessage('You cannot use the "oidc_user_info" token handler since the HttpClient component is not installed. Try running "composer require symfony/http-client".'); } $this->assertEquals($expected, $container->getDefinition('security.access_token_handler.firewall1')->getArguments()); diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index c10e76ca459bb..28b130b28299a 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -40,6 +40,7 @@ "symfony/expression-language": "^5.4|^6.0", "symfony/form": "^5.4|^6.0", "symfony/framework-bundle": "^6.3", + "symfony/http-client": "^5.4|^6.0", "symfony/ldap": "^5.4|^6.0", "symfony/process": "^5.4|^6.0", "symfony/rate-limiter": "^5.4|^6.0", diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig index d1d4f0f426ed2..4bb9cb8d1e269 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig @@ -258,6 +258,11 @@ div.sf-toolbar .sf-toolbar-block .sf-toolbar-info-piece.sf-toolbar-info-php-ext position: absolute; } +.sf-toolbar-block.sf-toolbar-block-right .sf-toolbar-info { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 0; +} + .sf-toolbar-block .sf-toolbar-info:empty { visibility: hidden; } @@ -273,9 +278,10 @@ div.sf-toolbar .sf-toolbar-block .sf-toolbar-info-piece.sf-toolbar-info-php-ext text-align: center; } -.sf-toolbar-block .sf-toolbar-status-green, -.sf-toolbar-block .sf-toolbar-info .sf-toolbar-status-green { +.sf-toolbar-block .sf-toolbar-status.sf-toolbar-status-green, +.sf-toolbar-block .sf-toolbar-info .sf-toolbar-status.sf-toolbar-status-green { background-color: #059669; + color: var(--white); } .sf-toolbar-block .sf-toolbar-status.sf-toolbar-status-red, .sf-toolbar-block .sf-toolbar-info .sf-toolbar-status.sf-toolbar-status-red { @@ -288,10 +294,7 @@ div.sf-toolbar .sf-toolbar-block .sf-toolbar-info-piece.sf-toolbar-info-php-ext color: var(--sf-toolbar-yellow-800); } -.sf-toolbar-block.sf-toolbar-status-green { - background-color: #059669; - color: var(--sf-toolbar-white); -} +.sf-toolbar-block.sf-toolbar-status-green::before, .sf-toolbar-block.sf-toolbar-status-red::before, .sf-toolbar-block.sf-toolbar-status-yellow::before { background: var(--sf-toolbar-yellow-400); @@ -307,6 +310,10 @@ div.sf-toolbar .sf-toolbar-block .sf-toolbar-info-piece.sf-toolbar-info-php-ext .sf-toolbar-block.sf-toolbar-status-red::before { background: var(--sf-toolbar-red-400); } +.sf-toolbar-block.sf-toolbar-status-green::before { + background: var(--sf-toolbar-green-400); +} +.sf-toolbar-block-request.sf-toolbar-block.sf-toolbar-status-green::before, .sf-toolbar-block-request.sf-toolbar-block.sf-toolbar-status-red::before, .sf-toolbar-block-request.sf-toolbar-block.sf-toolbar-status-yellow::before { display: none; @@ -384,7 +391,7 @@ div.sf-toolbar .sf-toolbar-block .sf-toolbar-info-piece.sf-toolbar-info-php-ext box-shadow: 1px 0 0 var(--sf-toolbar-black), inset 0 -1px 0 var(--sf-toolbar-black); } .sf-toolbar-block.sf-toolbar-block-right:hover .sf-toolbar-icon { - box-shadow: -2px 0 0 var(--sf-toolbar-black), inset 0 -2px 0 var(--sf-toolbar-black); + box-shadow: -1px 0 0 var(--sf-toolbar-black), inset 0 -1px 0 var(--sf-toolbar-black); } .sf-toolbar-block-request .sf-toolbar-icon { @@ -409,9 +416,6 @@ div.sf-toolbar .sf-toolbar-block .sf-toolbar-info-piece.sf-toolbar-info-php-ext .sf-toolbar-block.sf-toolbar-block-sf-cli .sf-toolbar-label { margin-left: 0; } -.sf-toolbar-block.sf-toolbar-block-sf-cli:hover .sf-toolbar-icon { - box-shadow: 2px 0 0 var(--sf-toolbar-black), inset 0 -2px 0 var(--sf-toolbar-black); -} .sf-toolbar-block:hover, .sf-toolbar-block.hover { @@ -538,7 +542,8 @@ div.sf-toolbar .sf-toolbar-block .sf-toolbar-info-piece.sf-toolbar-info-php-ext .sf-toolbar-icon .sf-toolbar-value { display: none; } -.sf-toolbar-block-config .sf-toolbar-icon .sf-toolbar-label { +.sf-toolbar-block-config .sf-toolbar-icon .sf-toolbar-label, +.sf-cli .sf-toolbar-icon .sf-toolbar-label { display: inline-block; } diff --git a/src/Symfony/Component/AssetMapper/Factory/CachedMappedAssetFactory.php b/src/Symfony/Component/AssetMapper/Factory/CachedMappedAssetFactory.php index cc5e41be27fb8..43ec8e03bf5ae 100644 --- a/src/Symfony/Component/AssetMapper/Factory/CachedMappedAssetFactory.php +++ b/src/Symfony/Component/AssetMapper/Factory/CachedMappedAssetFactory.php @@ -13,6 +13,7 @@ use Symfony\Component\AssetMapper\MappedAsset; use Symfony\Component\Config\ConfigCache; +use Symfony\Component\Config\Resource\DirectoryResource; use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\Config\Resource\ResourceInterface; @@ -59,7 +60,7 @@ private function getCacheFilePath(string $logicalPath, string $sourcePath): stri */ private function collectResourcesFromAsset(MappedAsset $mappedAsset): array { - $resources = array_map(fn (string $path) => new FileResource($path), $mappedAsset->getFileDependencies()); + $resources = array_map(fn (string $path) => is_dir($path) ? new DirectoryResource($path) : new FileResource($path), $mappedAsset->getFileDependencies()); $resources[] = new FileResource($mappedAsset->sourcePath); foreach ($mappedAsset->getDependencies() as $dependency) { diff --git a/src/Symfony/Component/AssetMapper/Tests/Factory/CachedMappedAssetFactoryTest.php b/src/Symfony/Component/AssetMapper/Tests/Factory/CachedMappedAssetFactoryTest.php index 311fd52fdcc1e..340b4e3020a2e 100644 --- a/src/Symfony/Component/AssetMapper/Tests/Factory/CachedMappedAssetFactoryTest.php +++ b/src/Symfony/Component/AssetMapper/Tests/Factory/CachedMappedAssetFactoryTest.php @@ -17,6 +17,7 @@ use Symfony\Component\AssetMapper\Factory\MappedAssetFactoryInterface; use Symfony\Component\AssetMapper\MappedAsset; use Symfony\Component\Config\ConfigCache; +use Symfony\Component\Config\Resource\DirectoryResource; use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\Filesystem\Filesystem; @@ -103,6 +104,7 @@ public function testAssetConfigCacheResourceContainsDependencies() // just adding any file as an example $mappedAsset->addFileDependency(__DIR__.'/../fixtures/importmap.php'); + $mappedAsset->addFileDependency(__DIR__.'/../fixtures/dir3'); $factory = $this->createMock(MappedAssetFactoryInterface::class); $factory->expects($this->once()) @@ -117,13 +119,14 @@ public function testAssetConfigCacheResourceContainsDependencies() $cachedFactory->createMappedAsset('file1.css', $sourcePath); $configCacheMetadata = $this->loadConfigCacheMetadataFor($mappedAsset); - $this->assertCount(4, $configCacheMetadata); + $this->assertCount(5, $configCacheMetadata); $this->assertInstanceOf(FileResource::class, $configCacheMetadata[0]); - $this->assertInstanceOf(FileResource::class, $configCacheMetadata[1]); + $this->assertInstanceOf(DirectoryResource::class, $configCacheMetadata[1]); + $this->assertInstanceOf(FileResource::class, $configCacheMetadata[2]); $this->assertSame(realpath(__DIR__.'/../fixtures/importmap.php'), $configCacheMetadata[0]->getResource()); - $this->assertSame($mappedAsset->sourcePath, $configCacheMetadata[1]->getResource()); - $this->assertSame($dependentOnContentAsset->sourcePath, $configCacheMetadata[2]->getResource()); - $this->assertSame($deeplyNestedAsset->sourcePath, $configCacheMetadata[3]->getResource()); + $this->assertSame($mappedAsset->sourcePath, $configCacheMetadata[2]->getResource()); + $this->assertSame($dependentOnContentAsset->sourcePath, $configCacheMetadata[3]->getResource()); + $this->assertSame($deeplyNestedAsset->sourcePath, $configCacheMetadata[4]->getResource()); } private function loadConfigCacheMetadataFor(MappedAsset $mappedAsset): array diff --git a/src/Symfony/Component/Cache/Adapter/DoctrineDbalAdapter.php b/src/Symfony/Component/Cache/Adapter/DoctrineDbalAdapter.php index 1cd335b2d4539..3843af125c1b1 100644 --- a/src/Symfony/Component/Cache/Adapter/DoctrineDbalAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/DoctrineDbalAdapter.php @@ -12,13 +12,16 @@ namespace Symfony\Component\Cache\Adapter; use Doctrine\DBAL\ArrayParameterType; +use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Exception as DBALException; use Doctrine\DBAL\Exception\TableNotFoundException; use Doctrine\DBAL\ParameterType; +use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Tools\DsnParser; use Symfony\Component\Cache\Exception\InvalidArgumentException; use Symfony\Component\Cache\Marshaller\DefaultMarshaller; use Symfony\Component\Cache\Marshaller\MarshallerInterface; @@ -67,7 +70,28 @@ public function __construct(Connection|string $connOrDsn, string $namespace = '' if (!class_exists(DriverManager::class)) { throw new InvalidArgumentException('Failed to parse DSN. Try running "composer require doctrine/dbal".'); } - $this->conn = DriverManager::getConnection(['url' => $connOrDsn]); + if (class_exists(DsnParser::class)) { + $params = (new DsnParser([ + 'db2' => 'ibm_db2', + 'mssql' => 'pdo_sqlsrv', + 'mysql' => 'pdo_mysql', + 'mysql2' => 'pdo_mysql', + 'postgres' => 'pdo_pgsql', + 'postgresql' => 'pdo_pgsql', + 'pgsql' => 'pdo_pgsql', + 'sqlite' => 'pdo_sqlite', + 'sqlite3' => 'pdo_sqlite', + ]))->parse($connOrDsn); + } else { + $params = ['url' => $connOrDsn]; + } + + $config = new Configuration(); + if (class_exists(DefaultSchemaManagerFactory::class)) { + $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); + } + + $this->conn = DriverManager::getConnection($params, $config); } $this->table = $options['db_table'] ?? $this->table; diff --git a/src/Symfony/Component/Cache/Tests/Adapter/DoctrineDbalAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/DoctrineDbalAdapterTest.php index 1ca078e82cf78..7f250f7b53596 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/DoctrineDbalAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/DoctrineDbalAdapterTest.php @@ -16,6 +16,7 @@ use Doctrine\DBAL\Driver\AbstractMySQLDriver; use Doctrine\DBAL\Driver\Middleware; use Doctrine\DBAL\DriverManager; +use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; use Doctrine\DBAL\Schema\Schema; use PHPUnit\Framework\SkippedTestSuiteError; use Psr\Cache\CacheItemPoolInterface; @@ -45,12 +46,12 @@ public static function tearDownAfterClass(): void public function createCachePool(int $defaultLifetime = 0): CacheItemPoolInterface { - return new DoctrineDbalAdapter(DriverManager::getConnection(['driver' => 'pdo_sqlite', 'path' => self::$dbFile]), '', $defaultLifetime); + return new DoctrineDbalAdapter(DriverManager::getConnection(['driver' => 'pdo_sqlite', 'path' => self::$dbFile], $this->getDbalConfig()), '', $defaultLifetime); } public function testConfigureSchemaDecoratedDbalDriver() { - $connection = DriverManager::getConnection(['driver' => 'pdo_sqlite', 'path' => self::$dbFile]); + $connection = DriverManager::getConnection(['driver' => 'pdo_sqlite', 'path' => self::$dbFile], $this->getDbalConfig()); if (!interface_exists(Middleware::class)) { $this->markTestSkipped('doctrine/dbal v2 does not support custom drivers using middleware'); } @@ -60,7 +61,7 @@ public function testConfigureSchemaDecoratedDbalDriver() ->method('wrap') ->willReturn(new DriverWrapper($connection->getDriver())); - $config = new Configuration(); + $config = $this->getDbalConfig(); $config->setMiddlewares([$middleware]); $connection = DriverManager::getConnection(['driver' => 'pdo_sqlite', 'path' => self::$dbFile], $config); @@ -75,7 +76,7 @@ public function testConfigureSchemaDecoratedDbalDriver() public function testConfigureSchema() { - $connection = DriverManager::getConnection(['driver' => 'pdo_sqlite', 'path' => self::$dbFile]); + $connection = DriverManager::getConnection(['driver' => 'pdo_sqlite', 'path' => self::$dbFile], $this->getDbalConfig()); $schema = new Schema(); $adapter = new DoctrineDbalAdapter($connection); @@ -95,7 +96,7 @@ public function testConfigureSchemaDifferentDbalConnection() public function testConfigureSchemaTableExists() { - $connection = DriverManager::getConnection(['driver' => 'pdo_sqlite', 'path' => self::$dbFile]); + $connection = DriverManager::getConnection(['driver' => 'pdo_sqlite', 'path' => self::$dbFile], $this->getDbalConfig()); $schema = new Schema(); $schema->createTable('cache_items'); @@ -154,4 +155,14 @@ private function createConnectionMock() return $connection; } + + private function getDbalConfig() + { + $config = new Configuration(); + if (class_exists(DefaultSchemaManagerFactory::class)) { + $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); + } + + return $config; + } } diff --git a/src/Symfony/Component/Cache/Tests/Traits/RedisTraitTest.php b/src/Symfony/Component/Cache/Tests/Traits/RedisTraitTest.php new file mode 100644 index 0000000000000..5997968468276 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Traits/RedisTraitTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Tests\Traits; + +use PHPUnit\Framework\SkippedTestSuiteError; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Cache\Traits\RedisTrait; + +class RedisTraitTest extends TestCase +{ + public static function setUpBeforeClass(): void + { + if (!getenv('REDIS_CLUSTER_HOSTS')) { + throw new SkippedTestSuiteError('REDIS_CLUSTER_HOSTS env var is not defined.'); + } + } + + /** + * @dataProvider provideCreateConnection + */ + public function testCreateConnection(string $dsn, string $expectedClass) + { + if (!class_exists($expectedClass)) { + throw new SkippedTestSuiteError(sprintf('The "%s" class is required.', $expectedClass)); + } + if (!getenv('REDIS_CLUSTER_HOSTS')) { + throw new SkippedTestSuiteError('REDIS_CLUSTER_HOSTS env var is not defined.'); + } + + $mock = self::getObjectForTrait(RedisTrait::class); + $connection = $mock::createConnection($dsn); + + self::assertInstanceOf($expectedClass, $connection); + } + + public static function provideCreateConnection(): array + { + $hosts = array_map(fn ($host) => sprintf('host[%s]', $host), explode(' ', getenv('REDIS_CLUSTER_HOSTS'))); + + return [ + [ + sprintf('redis:?%s&redis_cluster=1', $hosts[0]), + 'RedisCluster', + ], + [ + sprintf('redis:?%s&redis_cluster=true', $hosts[0]), + 'RedisCluster', + ], + [ + sprintf('redis:?%s', $hosts[0]), + 'Redis', + ], + [ + 'dsn' => sprintf('redis:?%s', implode('&', \array_slice($hosts, 0, 2))), + 'RedisArray', + ], + ]; + } +} diff --git a/src/Symfony/Component/Cache/Traits/RedisTrait.php b/src/Symfony/Component/Cache/Traits/RedisTrait.php index fb437f40a99fe..04f81d75cf586 100644 --- a/src/Symfony/Component/Cache/Traits/RedisTrait.php +++ b/src/Symfony/Component/Cache/Traits/RedisTrait.php @@ -173,6 +173,11 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra throw new CacheException('Redis Sentinel support requires one of: "predis/predis", "ext-redis >= 5.2", "ext-relay".'); } + if (isset($params['lazy'])) { + $params['lazy'] = filter_var($params['lazy'], \FILTER_VALIDATE_BOOLEAN); + } + $params['redis_cluster'] = filter_var($params['redis_cluster'], \FILTER_VALIDATE_BOOLEAN); + if ($params['redis_cluster'] && isset($params['redis_sentinel'])) { throw new InvalidArgumentException('Cannot use both "redis_cluster" and "redis_sentinel" at the same time.'); } diff --git a/src/Symfony/Component/Clock/MockClock.php b/src/Symfony/Component/Clock/MockClock.php index 5a3a207217ba9..5bd2360355c0a 100644 --- a/src/Symfony/Component/Clock/MockClock.php +++ b/src/Symfony/Component/Clock/MockClock.php @@ -51,7 +51,12 @@ public function sleep(float|int $seconds): void public function modify(string $modifier): void { - if (false === $modifiedNow = @$this->now->modify($modifier)) { + try { + $modifiedNow = @$this->now->modify($modifier); + } catch (\DateMalformedStringException) { + $modifiedNow = false; + } + if (false === $modifiedNow) { throw new \InvalidArgumentException(sprintf('Invalid modifier: "%s". Could not modify MockClock.', $modifier)); } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php index 2d5ceacae36b6..512b28ba2a113 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php @@ -311,6 +311,10 @@ private function checkType(Definition $checkedDefinition, mixed $value, \Reflect if (false === $value) { return; } + } elseif ('true' === $type) { + if (true === $value) { + return; + } } elseif ($reflectionType->isBuiltin()) { $checkFunction = sprintf('is_%s', $type); if ($checkFunction($value)) { diff --git a/src/Symfony/Component/DependencyInjection/Compiler/DecoratorServicePass.php b/src/Symfony/Component/DependencyInjection/Compiler/DecoratorServicePass.php index c38bfa7744502..d05878fe85611 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/DecoratorServicePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/DecoratorServicePass.php @@ -42,6 +42,7 @@ public function process(ContainerBuilder $container) $definitions->insert([$id, $definition], [$decorated[2], --$order]); } $decoratingDefinitions = []; + $decoratedIds = []; $tagsToKeep = $container->hasParameter('container.behavior_describing_tags') ? $container->getParameter('container.behavior_describing_tags') @@ -58,6 +59,7 @@ public function process(ContainerBuilder $container) $renamedId = $id.'.inner'; } + $decoratedIds[$inner] ??= $renamedId; $this->currentId = $renamedId; $this->processValue($definition); @@ -114,7 +116,7 @@ public function process(ContainerBuilder $container) } foreach ($decoratingDefinitions as $inner => $definition) { - $definition->addTag('container.decorator', ['id' => $inner]); + $definition->addTag('container.decorator', ['id' => $inner, 'inner' => $decoratedIds[$inner]]); } } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/DefinitionErrorExceptionPass.php b/src/Symfony/Component/DependencyInjection/Compiler/DefinitionErrorExceptionPass.php index 759b1d22d15ba..b6ee648160b73 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/DefinitionErrorExceptionPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/DefinitionErrorExceptionPass.php @@ -11,6 +11,8 @@ namespace Symfony\Component\DependencyInjection\Compiler; +use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\RuntimeException; @@ -23,31 +25,98 @@ */ class DefinitionErrorExceptionPass extends AbstractRecursivePass { - protected function processValue(mixed $value, bool $isRoot = false): mixed + private $erroredDefinitions = []; + private $targetReferences = []; + private $sourceReferences = []; + + /** + * @return void + */ + public function process(ContainerBuilder $container) { - if (!$value instanceof Definition || !$value->hasErrors() || $value->hasTag('container.error')) { - return parent::processValue($value, $isRoot); - } + try { + parent::process($container); + + if (!$this->erroredDefinitions) { + return; + } + + $runtimeIds = []; + + foreach ($this->sourceReferences as $id => $sourceIds) { + foreach ($sourceIds as $sourceId => $isRuntime) { + if (!$isRuntime) { + continue 2; + } + } + + unset($this->erroredDefinitions[$id]); + $runtimeIds[$id] = $id; + } + + if (!$this->erroredDefinitions) { + return; + } + + foreach ($this->targetReferences as $id => $targetIds) { + if (!isset($this->sourceReferences[$id]) || isset($runtimeIds[$id]) || isset($this->erroredDefinitions[$id])) { + continue; + } + foreach ($this->targetReferences[$id] as $targetId => $isRuntime) { + foreach ($this->sourceReferences[$id] as $sourceId => $isRuntime) { + if ($sourceId !== $targetId) { + $this->sourceReferences[$targetId][$sourceId] = false; + $this->targetReferences[$sourceId][$targetId] = false; + } + } + } - if ($isRoot && !$value->isPublic()) { - $graph = $this->container->getCompiler()->getServiceReferenceGraph(); - $runtimeException = false; - foreach ($graph->getNode($this->currentId)->getInEdges() as $edge) { - if (!$edge->getValue() instanceof Reference || ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE !== $edge->getValue()->getInvalidBehavior()) { - $runtimeException = false; - break; + unset($this->sourceReferences[$id]); + } + + foreach ($this->erroredDefinitions as $id => $definition) { + if (isset($this->sourceReferences[$id])) { + continue; } - $runtimeException = true; + + // only show the first error so the user can focus on it + $errors = $definition->getErrors(); + + throw new RuntimeException(reset($errors)); } - if ($runtimeException) { - return parent::processValue($value, $isRoot); + } finally { + $this->erroredDefinitions = []; + $this->targetReferences = []; + $this->sourceReferences = []; + } + } + + protected function processValue(mixed $value, bool $isRoot = false): mixed + { + if ($value instanceof ArgumentInterface) { + parent::processValue($value->getValues()); + + return $value; + } + + if ($value instanceof Reference && $this->currentId !== $targetId = (string) $value) { + if (ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior()) { + $this->sourceReferences[$targetId][$this->currentId] ??= true; + $this->targetReferences[$this->currentId][$targetId] ??= true; + } else { + $this->sourceReferences[$targetId][$this->currentId] = false; + $this->targetReferences[$this->currentId][$targetId] = false; } + + return $value; + } + + if (!$value instanceof Definition || !$value->hasErrors() || $value->hasTag('container.error')) { + return parent::processValue($value, $isRoot); } - // only show the first error so the user can focus on it - $errors = $value->getErrors(); - $message = reset($errors); + $this->erroredDefinitions[$this->currentId] = $value; - throw new RuntimeException($message); + return parent::processValue($value); } } diff --git a/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php index f8c56d23f8273..b7633c0fea8a9 100644 --- a/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php +++ b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php @@ -146,7 +146,7 @@ public function getEnv(string $prefix, string $name, \Closure $getEnv): mixed if (false !== $i || 'string' !== $prefix) { $env = $getEnv($name); } elseif ('' === ($env = $_ENV[$name] ?? (str_starts_with($name, 'HTTP_') ? null : ($_SERVER[$name] ?? null))) - || false === ($env = $env ?? getenv($name) ?? false) // null is a possible value because of thread safety issues + || (false !== $env && false === ($env = $env ?? getenv($name) ?? false)) // null is a possible value because of thread safety issues ) { foreach ($this->loadedVars as $vars) { if (false !== ($env = ($vars[$name] ?? $env)) && '' !== $env) { @@ -196,7 +196,9 @@ public function getEnv(string $prefix, string $name, \Closure $getEnv): mixed throw new RuntimeException(sprintf('Unsupported env var prefix "%s".', $prefix)); } - return null; + if (!\in_array($prefix, ['string', 'bool', 'not', 'int', 'float'], true)) { + return null; + } } if ('shuffle' === $prefix) { @@ -205,7 +207,7 @@ public function getEnv(string $prefix, string $name, \Closure $getEnv): mixed return $env; } - if (!\is_scalar($env)) { + if (null !== $env && !\is_scalar($env)) { throw new RuntimeException(sprintf('Non-scalar env var "%s" cannot be cast to "%s".', $name, $prefix)); } @@ -220,7 +222,7 @@ public function getEnv(string $prefix, string $name, \Closure $getEnv): mixed } if ('int' === $prefix) { - if (false === $env = filter_var($env, \FILTER_VALIDATE_INT) ?: filter_var($env, \FILTER_VALIDATE_FLOAT)) { + if (null !== $env && false === $env = filter_var($env, \FILTER_VALIDATE_INT) ?: filter_var($env, \FILTER_VALIDATE_FLOAT)) { throw new RuntimeException(sprintf('Non-numeric env var "%s" cannot be cast to int.', $name)); } @@ -228,7 +230,7 @@ public function getEnv(string $prefix, string $name, \Closure $getEnv): mixed } if ('float' === $prefix) { - if (false === $env = filter_var($env, \FILTER_VALIDATE_FLOAT)) { + if (null !== $env && false === $env = filter_var($env, \FILTER_VALIDATE_FLOAT)) { throw new RuntimeException(sprintf('Non-numeric env var "%s" cannot be cast to float.', $name)); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php index c169d6c8ca7fc..4a71ad3155a48 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php @@ -32,6 +32,7 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\FooObject; use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\IntersectionConstructor; use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\UnionConstructor; +use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\UnionConstructorPHP82; use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\Waldo; use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\WaldoFoo; use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\Wobble; @@ -859,6 +860,26 @@ public function testUnionTypePassesWithFalse() $this->addToAssertionCount(1); } + /** + * @requires PHP 8.2 + */ + public function testUnionTypePassesWithTrue() + { + $container = new ContainerBuilder(); + + $container->register('unionTrue', UnionConstructorPHP82::class) + ->setFactory([UnionConstructorPHP82::class, 'createTrue']) + ->setArguments([true]); + + $container->register('unionNull', UnionConstructorPHP82::class) + ->setFactory([UnionConstructorPHP82::class, 'createNull']) + ->setArguments([null]); + + (new CheckTypeDeclarationsPass(true))->process($container); + + $this->addToAssertionCount(1); + } + public function testUnionTypeFailsWithReference() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/DecoratorServicePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/DecoratorServicePassTest.php index 8c8a158a76327..48ed32df6d63d 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/DecoratorServicePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/DecoratorServicePassTest.php @@ -161,7 +161,7 @@ public function testProcessWithInvalidDecorated() public function testProcessNoInnerAliasWithInvalidDecorated() { $container = new ContainerBuilder(); - $decoratorDefinition = $container + $container ->register('decorator') ->setDecoratedService('unknown_decorated', null, 0, ContainerInterface::NULL_ON_INVALID_REFERENCE) ; @@ -173,7 +173,7 @@ public function testProcessNoInnerAliasWithInvalidDecorated() public function testProcessWithInvalidDecoratedAndWrongBehavior() { $container = new ContainerBuilder(); - $decoratorDefinition = $container + $container ->register('decorator') ->setDecoratedService('unknown_decorated', null, 0, 12) ; @@ -198,7 +198,7 @@ public function testProcessMovesTagsFromDecoratedDefinitionToDecoratingDefinitio $this->process($container); $this->assertEmpty($container->getDefinition('baz.inner')->getTags()); - $this->assertEquals(['bar' => ['attr' => 'baz'], 'foobar' => ['attr' => 'bar'], 'container.decorator' => [['id' => 'foo']]], $container->getDefinition('baz')->getTags()); + $this->assertEquals(['bar' => ['attr' => 'baz'], 'foobar' => ['attr' => 'bar'], 'container.decorator' => [['id' => 'foo', 'inner' => 'baz.inner']]], $container->getDefinition('baz')->getTags()); } public function testProcessMovesTagsFromDecoratedDefinitionToDecoratingDefinitionMultipleTimes() @@ -221,7 +221,7 @@ public function testProcessMovesTagsFromDecoratedDefinitionToDecoratingDefinitio $this->process($container); $this->assertEmpty($container->getDefinition('deco1')->getTags()); - $this->assertEquals(['bar' => ['attr' => 'baz'], 'container.decorator' => [['id' => 'foo']]], $container->getDefinition('deco2')->getTags()); + $this->assertEquals(['bar' => ['attr' => 'baz'], 'container.decorator' => [['id' => 'foo', 'inner' => 'deco1.inner']]], $container->getDefinition('deco2')->getTags()); } public function testProcessLeavesServiceLocatorTagOnOriginalDefinition() @@ -240,7 +240,7 @@ public function testProcessLeavesServiceLocatorTagOnOriginalDefinition() $this->process($container); $this->assertEquals(['container.service_locator' => [0 => []]], $container->getDefinition('baz.inner')->getTags()); - $this->assertEquals(['bar' => ['attr' => 'baz'], 'foobar' => ['attr' => 'bar'], 'container.decorator' => [['id' => 'foo']]], $container->getDefinition('baz')->getTags()); + $this->assertEquals(['bar' => ['attr' => 'baz'], 'foobar' => ['attr' => 'bar'], 'container.decorator' => [['id' => 'foo', 'inner' => 'baz.inner']]], $container->getDefinition('baz')->getTags()); } public function testProcessLeavesServiceSubscriberTagOnOriginalDefinition() @@ -259,7 +259,7 @@ public function testProcessLeavesServiceSubscriberTagOnOriginalDefinition() $this->process($container); $this->assertEquals(['container.service_subscriber' => [], 'container.service_subscriber.locator' => []], $container->getDefinition('baz.inner')->getTags()); - $this->assertEquals(['bar' => ['attr' => 'baz'], 'foobar' => ['attr' => 'bar'], 'container.decorator' => [['id' => 'foo']]], $container->getDefinition('baz')->getTags()); + $this->assertEquals(['bar' => ['attr' => 'baz'], 'foobar' => ['attr' => 'bar'], 'container.decorator' => [['id' => 'foo', 'inner' => 'baz.inner']]], $container->getDefinition('baz')->getTags()); } public function testProcessLeavesProxyTagOnOriginalDefinition() @@ -278,7 +278,7 @@ public function testProcessLeavesProxyTagOnOriginalDefinition() $this->process($container); $this->assertEquals(['proxy' => 'foo'], $container->getDefinition('baz.inner')->getTags()); - $this->assertEquals(['bar' => ['attr' => 'baz'], 'foobar' => ['attr' => 'bar'], 'container.decorator' => [['id' => 'foo']]], $container->getDefinition('baz')->getTags()); + $this->assertEquals(['bar' => ['attr' => 'baz'], 'foobar' => ['attr' => 'bar'], 'container.decorator' => [['id' => 'foo', 'inner' => 'baz.inner']]], $container->getDefinition('baz')->getTags()); } public function testCannotDecorateSyntheticService() diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/DefinitionErrorExceptionPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/DefinitionErrorExceptionPassTest.php index a62fe0cc79153..9ab5c27fcf763 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/DefinitionErrorExceptionPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/DefinitionErrorExceptionPassTest.php @@ -16,6 +16,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Reference; class DefinitionErrorExceptionPassTest extends TestCase { @@ -50,6 +51,27 @@ public function testNoExceptionThrown() $this->assertSame($def, $container->getDefinition('foo_service_id')->getArgument(0)); } + public function testSkipNestedErrors() + { + $container = new ContainerBuilder(); + + $container->register('nested_error', 'stdClass') + ->addError('Things went wrong!'); + + $container->register('bar', 'stdClass') + ->addArgument(new Reference('nested_error')); + + $container->register('foo', 'stdClass') + ->addArgument(new Reference('bar', ContainerBuilder::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE)); + + $pass = new DefinitionErrorExceptionPass(); + $pass->process($container); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Things went wrong!'); + $container->get('foo'); + } + public function testSkipErrorFromTag() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index 737c9cd265801..f0a3bc0ca2f71 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -988,8 +988,8 @@ public function testEnvAreNullable() $container->register('foo', 'stdClass') ->setPublic(true) ->setProperties([ - 'fake' => '%env(int:FAKE)%', - ]); + 'fake' => '%env(resolve:FAKE)%', + ]); $container->compile(true); diff --git a/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php b/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php index 1b64f1bd75070..ac4ca68b10f26 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php @@ -59,6 +59,67 @@ public static function validStrings() ]; } + /** + * @dataProvider validRealEnvValues + */ + public function testGetEnvRealEnv($value, $processed) + { + $_ENV['FOO'] = $value; + + $processor = new EnvVarProcessor(new Container()); + + $result = $processor->getEnv('string', 'FOO', function () { + $this->fail('Should not be called'); + }); + + $this->assertSame($processed, $result); + + unset($_ENV['FOO']); + } + + public static function validRealEnvValues() + { + return [ + ['hello', 'hello'], + [true, '1'], + [false, ''], + [1, '1'], + [0, '0'], + [1.1, '1.1'], + [10, '10'], + ]; + } + + public function testGetEnvRealEnvInvalid() + { + $_ENV['FOO'] = null; + $this->expectException(EnvNotFoundException::class); + $this->expectExceptionMessage('Environment variable not found: "FOO".'); + + $processor = new EnvVarProcessor(new Container()); + + $processor->getEnv('string', 'FOO', function () { + $this->fail('Should not be called'); + }); + + unset($_ENV['FOO']); + } + + public function testGetEnvRealEnvNonScalar() + { + $_ENV['FOO'] = []; + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Non-scalar env var "FOO" cannot be cast to "string".'); + + $processor = new EnvVarProcessor(new Container()); + + $processor->getEnv('string', 'FOO', function () { + $this->fail('Should not be called'); + }); + + unset($_ENV['FOO']); + } + /** * @dataProvider validBools */ @@ -97,6 +158,7 @@ public static function validBools() ['true', true], ['false', false], ['null', false], + ['', false], ['1', true], ['0', false], ['1.1', true], @@ -845,4 +907,22 @@ public static function provideGetEnvUrlPath() ['blog//', 'https://symfony.com/blog//'], ]; } + + /** + * @testWith ["", "string"] + * [false, "bool"] + * [true, "not"] + * [0, "int"] + * [0.0, "float"] + */ + public function testGetEnvCastsNull($expected, string $prefix) + { + $processor = new EnvVarProcessor(new Container()); + + $this->assertSame($expected, $processor->getEnv($prefix, 'default::FOO', static function () use ($processor) { + return $processor->getEnv('default', ':FOO', static function () { + return null; + }); + })); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/UnionConstructorPHP82.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/UnionConstructorPHP82.php new file mode 100644 index 0000000000000..ea0d9e348f388 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/UnionConstructorPHP82.php @@ -0,0 +1,16 @@ +markAsSkippedIfSymlinkIsMissing(); - $file = $this->workspace.'/file'; - $link = $this->workspace.'/link'; + $file = Path::join($this->workspace, 'file'); + $link = Path::join($this->workspace, 'link'); touch($file); $this->filesystem->symlink($file, $link); $this->filesystem->remove($file); - $this->assertEquals($file, $this->filesystem->readlink($link)); + $this->assertEquals($file, Path::normalize($this->filesystem->readlink($link))); $this->assertNull($this->filesystem->readlink($link, true)); touch($file); - $this->assertEquals($file, $this->filesystem->readlink($link, true)); + $this->assertEquals($file, Path::normalize($this->filesystem->readlink($link, true))); } public function testReadLinkDefaultPathDoesNotExist() diff --git a/src/Symfony/Component/HttpClient/CurlHttpClient.php b/src/Symfony/Component/HttpClient/CurlHttpClient.php index ccb2a5707e87e..bbaa4de28893c 100644 --- a/src/Symfony/Component/HttpClient/CurlHttpClient.php +++ b/src/Symfony/Component/HttpClient/CurlHttpClient.php @@ -462,6 +462,7 @@ private function validateExtraCurlOptions(array $options): void \CURLOPT_TIMEOUT_MS => 'max_duration', \CURLOPT_TIMEOUT => 'max_duration', \CURLOPT_MAXREDIRS => 'max_redirects', + \CURLOPT_POSTREDIR => 'max_redirects', \CURLOPT_PROXY => 'proxy', \CURLOPT_NOPROXY => 'no_proxy', \CURLOPT_SSL_VERIFYPEER => 'verify_peer', diff --git a/src/Symfony/Component/HttpClient/HttpClientTrait.php b/src/Symfony/Component/HttpClient/HttpClientTrait.php index 48782153b4076..3364d32acd41a 100644 --- a/src/Symfony/Component/HttpClient/HttpClientTrait.php +++ b/src/Symfony/Component/HttpClient/HttpClientTrait.php @@ -659,7 +659,7 @@ private static function parseUrl(string $url, array $query = [], array $allowedS } // https://tools.ietf.org/html/rfc3986#section-3.3 - $parts[$part] = preg_replace_callback("#[^-A-Za-z0-9._~!$&/'()[\]*+,;=:@\\\\^`{|}%]++#", fn ($m) => rawurlencode($m[0]), $parts[$part]); + $parts[$part] = preg_replace_callback("#[^-A-Za-z0-9._~!$&/'()[\]*+,;=:@{}%]++#", fn ($m) => rawurlencode($m[0]), $parts[$part]); } return [ @@ -748,11 +748,7 @@ private static function mergeQueryString(?string $queryString, array $queryArray '%3B' => ';', '%40' => '@', '%5B' => '[', - '%5C' => '\\', '%5D' => ']', - '%5E' => '^', - '%60' => '`', - '%7C' => '|', ]); } diff --git a/src/Symfony/Component/HttpClient/Response/AsyncResponse.php b/src/Symfony/Component/HttpClient/Response/AsyncResponse.php index 66152e3d90c7e..e37278f79f711 100644 --- a/src/Symfony/Component/HttpClient/Response/AsyncResponse.php +++ b/src/Symfony/Component/HttpClient/Response/AsyncResponse.php @@ -27,7 +27,7 @@ * * @author Nicolas Grekas */ -final class AsyncResponse implements ResponseInterface, StreamableInterface +class AsyncResponse implements ResponseInterface, StreamableInterface { use CommonResponseTrait; diff --git a/src/Symfony/Component/HttpClient/Retry/GenericRetryStrategy.php b/src/Symfony/Component/HttpClient/Retry/GenericRetryStrategy.php index 9d1473c34a9b3..edbf2c066a766 100644 --- a/src/Symfony/Component/HttpClient/Retry/GenericRetryStrategy.php +++ b/src/Symfony/Component/HttpClient/Retry/GenericRetryStrategy.php @@ -102,7 +102,7 @@ public function getDelay(AsyncContext $context, ?string $responseContent, ?Trans $delay = $this->delayMs * $this->multiplier ** $context->getInfo('retry_count'); if ($this->jitter > 0) { - $randomness = $delay * $this->jitter; + $randomness = (int) ($delay * $this->jitter); $delay = $delay + random_int(-$randomness, +$randomness); } diff --git a/src/Symfony/Component/HttpClient/Tests/HttpClientTraitTest.php b/src/Symfony/Component/HttpClient/Tests/HttpClientTraitTest.php index ecb5107693c64..613d80cb1d3a7 100644 --- a/src/Symfony/Component/HttpClient/Tests/HttpClientTraitTest.php +++ b/src/Symfony/Component/HttpClient/Tests/HttpClientTraitTest.php @@ -269,12 +269,13 @@ public static function provideParseUrl(): iterable yield [['http:', '//example.com', null, null, null], 'http://Example.coM:80']; yield [['https:', '//xn--dj-kia8a.example.com:8000', '/', null, null], 'https://DÉjà.Example.com:8000/']; yield [[null, null, '/f%20o.o', '?a=b', '#c'], '/f o%2Eo?a=b#c']; + yield [[null, null, '/custom%7C2010-01-01%2000:00:00%7C2023-06-15%2005:50:35', '?a=b', '#c'], '/custom|2010-01-01 00:00:00|2023-06-15 05:50:35?a=b#c']; yield [[null, '//a:b@foo', '/bar', null, null], '//a:b@foo/bar']; yield [[null, '//a:b@foo', '/b{}', null, null], '//a:b@foo/b{}']; yield [['http:', null, null, null, null], 'http:']; yield [['http:', null, 'bar', null, null], 'http:bar']; yield [[null, null, 'bar', '?a=1&c=c', null], 'bar?a=a&b=b', ['b' => null, 'c' => 'c', 'a' => 1]]; - yield [[null, null, 'bar', '?a=b+c&b=b-._~!$%26/%27()[]*%2B%2C;%3D:@%25\\^`%7B|%7D', null], 'bar?a=b+c', ['b' => 'b-._~!$&/\'()[]*+,;=:@%\\^`{|}']]; + yield [[null, null, 'bar', '?a=b+c&b=b-._~!$%26/%27()[]*%2B%2C;%3D:@%25%5C%5E%60%7B%7C%7D', null], 'bar?a=b+c', ['b' => 'b-._~!$&/\'()[]*+,;=:@%\\^`{|}']]; yield [[null, null, 'bar', '?a=b%2B%20c', null], 'bar?a=b+c', ['a' => 'b+ c']]; yield [[null, null, 'bar', '?a[b]=c', null], 'bar', ['a' => ['b' => 'c']]]; yield [[null, null, 'bar', '?a[b[c]=d', null], 'bar?a[b[c]=d', []]; diff --git a/src/Symfony/Component/HttpClient/Tests/Retry/GenericRetryStrategyTest.php b/src/Symfony/Component/HttpClient/Tests/Retry/GenericRetryStrategyTest.php index 79fc37588bd0f..8219bbe57c0a8 100644 --- a/src/Symfony/Component/HttpClient/Tests/Retry/GenericRetryStrategyTest.php +++ b/src/Symfony/Component/HttpClient/Tests/Retry/GenericRetryStrategyTest.php @@ -67,7 +67,7 @@ public function testGetDelay(int $delay, int $multiplier, int $maxDelay, int $pr public static function provideDelay(): iterable { - // delay, multiplier, maxDelay, retries, expectedDelay + // delay, multiplier, maxDelay, previousRetries, expectedDelay yield [1000, 1, 5000, 0, 1000]; yield [1000, 1, 5000, 1, 1000]; yield [1000, 1, 5000, 2, 1000]; @@ -90,13 +90,16 @@ public static function provideDelay(): iterable yield [0, 2, 10000, 1, 0]; } - public function testJitter() + /** + * @dataProvider provideJitter + */ + public function testJitter(float $multiplier, int $previousRetries) { - $strategy = new GenericRetryStrategy([], 1000, 1, 0, 1); + $strategy = new GenericRetryStrategy([], 1000, $multiplier, 0, 1); $min = 2000; $max = 0; for ($i = 0; $i < 50; ++$i) { - $delay = $strategy->getDelay($this->getContext(0, 'GET', 'http://example.com/', 200), null, null); + $delay = $strategy->getDelay($this->getContext($previousRetries, 'GET', 'http://example.com/', 200), null, null); $min = min($min, $delay); $max = max($max, $delay); } @@ -105,6 +108,13 @@ public function testJitter() $this->assertLessThanOrEqual(1000, $min); } + public static function provideJitter(): iterable + { + // multiplier, previousRetries + yield [1, 0]; + yield [1.1, 2]; + } + private function getContext($retryCount, $method, $url, $statusCode): AsyncContext { $passthru = null; diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 633b4a63e2e43..0bef6f8d70796 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -1508,10 +1508,30 @@ public function getContent(bool $asResource = false) /** * Gets the decoded form or json request body. + * + * @throws JsonException When the body cannot be decoded to an array */ public function getPayload(): InputBag { - return $this->request->count() ? clone $this->request : new InputBag($this->toArray()); + if ($this->request->count()) { + return clone $this->request; + } + + if ('' === $content = $this->getContent()) { + return new InputBag([]); + } + + try { + $content = json_decode($content, true, 512, \JSON_BIGINT_AS_STRING | \JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + throw new JsonException('Could not decode request body.', $e->getCode(), $e); + } + + if (!\is_array($content)) { + throw new JsonException(sprintf('JSON content was expected to decode to an array, "%s" returned.', get_debug_type($content))); + } + + return new InputBag($content); } /** diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php index 65452a5207374..a40a7bc77be23 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php @@ -380,8 +380,8 @@ public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $updateStmt = $this->pdo->prepare( "UPDATE $this->table SET $this->lifetimeCol = :expiry, $this->timeCol = :time WHERE $this->idCol = :id" ); - $updateStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); - $updateStmt->bindParam(':expiry', $expiry, \PDO::PARAM_INT); + $updateStmt->bindValue(':id', $sessionId, \PDO::PARAM_STR); + $updateStmt->bindValue(':expiry', $expiry, \PDO::PARAM_INT); $updateStmt->bindValue(':time', time(), \PDO::PARAM_INT); $updateStmt->execute(); } catch (\PDOException $e) { diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php index dbbe7dc880e23..aac62296ef5c7 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php @@ -11,7 +11,10 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; +use Doctrine\DBAL\Configuration; use Doctrine\DBAL\DriverManager; +use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; +use Doctrine\DBAL\Tools\DsnParser; use Relay\Relay; use Symfony\Component\Cache\Adapter\AbstractAdapter; @@ -67,7 +70,15 @@ public static function createHandler(object|string $connection, array $options = if (!class_exists(DriverManager::class)) { throw new \InvalidArgumentException('Unsupported PDO OCI DSN. Try running "composer require doctrine/dbal".'); } - $connection = DriverManager::getConnection(['url' => $connection])->getWrappedConnection(); + $connection[3] = '-'; + $params = class_exists(DsnParser::class) ? (new DsnParser())->parse($connection) : ['url' => $connection]; + $config = new Configuration(); + if (class_exists(DefaultSchemaManagerFactory::class)) { + $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); + } + + $connection = DriverManager::getConnection($params, $config); + $connection = method_exists($connection, 'getNativeConnection') ? $connection->getNativeConnection() : $connection->getWrappedConnection(); // no break; case str_starts_with($connection, 'mssql://'): diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index 5e8df28bb5713..308e9e6fd8863 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -1309,6 +1309,9 @@ public function testGetPayload() $req = new Request([], ['foo' => 'bar'], [], [], [], [], json_encode(['baz' => 'qux'])); $this->assertSame(['foo' => 'bar'], $req->getPayload()->all()); + + $req = new Request([], [], [], [], [], [], ''); + $this->assertSame([], $req->getPayload()->all()); } /** diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php index 9ae65c7dfdbf0..370097cda4b08 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php @@ -72,6 +72,10 @@ public function resolve(Request $request, ArgumentMetadata $argument): iterable return []; } + if ($argument->isVariadic()) { + throw new \LogicException(sprintf('Mapping variadic argument "$%s" is not supported.', $argument->getName())); + } + $attribute->metadata = $argument; return [$attribute]; @@ -130,8 +134,12 @@ public function onKernelControllerArguments(ControllerArgumentsEvent $event): vo } } - if (null === $payload && !$argument->metadata->isNullable()) { - throw new HttpException($validationFailedCode); + if (null === $payload) { + $payload = match (true) { + $argument->metadata->hasDefaultValue() => $argument->metadata->getDefaultValue(), + $argument->metadata->isNullable() => null, + default => throw new HttpException($validationFailedCode) + }; } $arguments[$i] = $payload; diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index c98705163d086..beff9f2e7c655 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -76,11 +76,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl */ private static array $freshCache = []; - public const VERSION = '6.3.0'; - public const VERSION_ID = 60300; + public const VERSION = '6.3.1'; + public const VERSION_ID = 60301; public const MAJOR_VERSION = 6; public const MINOR_VERSION = 3; - public const RELEASE_VERSION = 0; + public const RELEASE_VERSION = 1; public const EXTRA_VERSION = ''; public const END_OF_MAINTENANCE = '01/2024'; diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php index 0f6fd60b75a74..4ca326392be56 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php @@ -57,6 +57,148 @@ public function testNotTypedArgument() $resolver->onKernelControllerArguments($event); } + public function testDefaultValueArgument() + { + $payload = new RequestPayload(50); + + $validator = $this->createMock(ValidatorInterface::class); + $validator->expects($this->never()) + ->method('validate'); + + $resolver = new RequestPayloadValueResolver(new Serializer(), $validator); + + $argument = new ArgumentMetadata('valid', RequestPayload::class, false, true, $payload, false, [ + MapRequestPayload::class => new MapRequestPayload(), + ]); + $request = Request::create('/', 'POST', server: ['CONTENT_TYPE' => 'application/json']); + + $kernel = $this->createMock(HttpKernelInterface::class); + $arguments = $resolver->resolve($request, $argument); + $event = new ControllerArgumentsEvent($kernel, fn () => null, $arguments, $request, HttpKernelInterface::MAIN_REQUEST); + + $resolver->onKernelControllerArguments($event); + + $this->assertEquals([$payload], $event->getArguments()); + } + + public function testQueryDefaultValueArgument() + { + $payload = new RequestPayload(50); + + $validator = $this->createMock(ValidatorInterface::class); + $validator->expects($this->never()) + ->method('validate'); + + $resolver = new RequestPayloadValueResolver(new Serializer(), $validator); + + $argument = new ArgumentMetadata('valid', RequestPayload::class, false, true, $payload, false, [ + MapQueryString::class => new MapQueryString(), + ]); + $request = Request::create('/', 'GET'); + + $kernel = $this->createMock(HttpKernelInterface::class); + $arguments = $resolver->resolve($request, $argument); + $event = new ControllerArgumentsEvent($kernel, fn () => null, $arguments, $request, HttpKernelInterface::MAIN_REQUEST); + + $resolver->onKernelControllerArguments($event); + + $this->assertEquals([$payload], $event->getArguments()); + } + + public function testNullableValueArgument() + { + $validator = $this->createMock(ValidatorInterface::class); + $validator->expects($this->never()) + ->method('validate'); + + $resolver = new RequestPayloadValueResolver(new Serializer(), $validator); + + $argument = new ArgumentMetadata('valid', RequestPayload::class, false, false, null, true, [ + MapRequestPayload::class => new MapRequestPayload(), + ]); + $request = Request::create('/', 'POST', server: ['CONTENT_TYPE' => 'application/json']); + + $kernel = $this->createMock(HttpKernelInterface::class); + $arguments = $resolver->resolve($request, $argument); + $event = new ControllerArgumentsEvent($kernel, fn () => null, $arguments, $request, HttpKernelInterface::MAIN_REQUEST); + + $resolver->onKernelControllerArguments($event); + + $this->assertEquals([null], $event->getArguments()); + } + + public function testQueryNullableValueArgument() + { + $validator = $this->createMock(ValidatorInterface::class); + $validator->expects($this->never()) + ->method('validate'); + + $resolver = new RequestPayloadValueResolver(new Serializer(), $validator); + + $argument = new ArgumentMetadata('valid', RequestPayload::class, false, false, null, true, [ + MapQueryString::class => new MapQueryString(), + ]); + $request = Request::create('/', 'GET'); + + $kernel = $this->createMock(HttpKernelInterface::class); + $arguments = $resolver->resolve($request, $argument); + $event = new ControllerArgumentsEvent($kernel, fn () => null, $arguments, $request, HttpKernelInterface::MAIN_REQUEST); + + $resolver->onKernelControllerArguments($event); + + $this->assertSame([null], $event->getArguments()); + } + + public function testNullPayloadAndNotDefaultOrNullableArgument() + { + $validator = $this->createMock(ValidatorInterface::class); + $validator->expects($this->never()) + ->method('validate'); + + $resolver = new RequestPayloadValueResolver(new Serializer(), $validator); + + $argument = new ArgumentMetadata('valid', RequestPayload::class, false, false, null, false, [ + MapRequestPayload::class => new MapRequestPayload(), + ]); + $request = Request::create('/', 'POST', server: ['CONTENT_TYPE' => 'application/json']); + + $kernel = $this->createMock(HttpKernelInterface::class); + $arguments = $resolver->resolve($request, $argument); + $event = new ControllerArgumentsEvent($kernel, fn () => null, $arguments, $request, HttpKernelInterface::MAIN_REQUEST); + + try { + $resolver->onKernelControllerArguments($event); + $this->fail(sprintf('Expected "%s" to be thrown.', HttpException::class)); + } catch (HttpException $e) { + $this->assertSame(422, $e->getStatusCode()); + } + } + + public function testQueryNullPayloadAndNotDefaultOrNullableArgument() + { + $validator = $this->createMock(ValidatorInterface::class); + $validator->expects($this->never()) + ->method('validate'); + + $resolver = new RequestPayloadValueResolver(new Serializer(), $validator); + + $argument = new ArgumentMetadata('valid', RequestPayload::class, false, false, null, false, [ + MapQueryString::class => new MapQueryString(), + ]); + $request = Request::create('/', 'GET'); + + $kernel = $this->createMock(HttpKernelInterface::class); + $arguments = $resolver->resolve($request, $argument); + $event = new ControllerArgumentsEvent($kernel, fn () => null, $arguments, $request, HttpKernelInterface::MAIN_REQUEST); + + try { + $resolver->onKernelControllerArguments($event); + $this->fail(sprintf('Expected "%s" to be thrown.', HttpException::class)); + } catch (HttpException $e) { + $this->assertSame(404, $e->getStatusCode()); + } + } + public function testWithoutValidatorAndCouldNotDenormalize() { $content = '{"price": 50, "title": ["not a string"]}'; @@ -221,6 +363,22 @@ public function testRequestInputValidationPassed() $this->assertEquals([$payload], $event->getArguments()); } + public function testItThrowsOnVariadicArgument() + { + $serializer = new Serializer(); + $validator = $this->createMock(ValidatorInterface::class); + $resolver = new RequestPayloadValueResolver($serializer, $validator); + + $argument = new ArgumentMetadata('variadic', RequestPayload::class, true, false, null, false, [ + MapRequestPayload::class => new MapRequestPayload(), + ]); + $request = Request::create('/', 'POST'); + + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('Mapping variadic argument "$variadic" is not supported.'); + $resolver->resolve($request, $argument); + } + /** * @dataProvider provideMatchedFormatContext */ diff --git a/src/Symfony/Component/Intl/Intl.php b/src/Symfony/Component/Intl/Intl.php index ddf97a418e432..a810adf7407ce 100644 --- a/src/Symfony/Component/Intl/Intl.php +++ b/src/Symfony/Component/Intl/Intl.php @@ -106,7 +106,7 @@ public static function getIcuDataVersion(): string */ public static function getIcuStubVersion(): string { - return '73.1'; + return '73.2'; } /** diff --git a/src/Symfony/Component/Intl/Resources/data/git-info.txt b/src/Symfony/Component/Intl/Resources/data/git-info.txt index c9973ca5da2cd..55d3c90d5a6fb 100644 --- a/src/Symfony/Component/Intl/Resources/data/git-info.txt +++ b/src/Symfony/Component/Intl/Resources/data/git-info.txt @@ -2,6 +2,6 @@ Git information =============== URL: https://github.com/unicode-org/icu.git -Revision: 5861e1fd52f1d7673eee38bc3c965aa18b336062 +Revision: 680f521746a3bd6a86f25f25ee50a62d88b489cf Author: Peter Edberg -Date: 2023-04-11T10:32:35-07:00 +Date: 2023-06-07T19:19:55-07:00 diff --git a/src/Symfony/Component/Intl/Resources/data/version.txt b/src/Symfony/Component/Intl/Resources/data/version.txt index 62bf7fe234f35..ae6f4d78d2129 100644 --- a/src/Symfony/Component/Intl/Resources/data/version.txt +++ b/src/Symfony/Component/Intl/Resources/data/version.txt @@ -1 +1 @@ -73.1 +73.2 diff --git a/src/Symfony/Component/Lock/Store/DoctrineDbalPostgreSqlStore.php b/src/Symfony/Component/Lock/Store/DoctrineDbalPostgreSqlStore.php index 4344a6f41f35d..d87a7d5667ba6 100644 --- a/src/Symfony/Component/Lock/Store/DoctrineDbalPostgreSqlStore.php +++ b/src/Symfony/Component/Lock/Store/DoctrineDbalPostgreSqlStore.php @@ -11,9 +11,12 @@ namespace Symfony\Component\Lock\Store; +use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Connection; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; +use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; +use Doctrine\DBAL\Tools\DsnParser; use Symfony\Component\Lock\BlockingSharedLockStoreInterface; use Symfony\Component\Lock\BlockingStoreInterface; use Symfony\Component\Lock\Exception\InvalidArgumentException; @@ -49,7 +52,28 @@ public function __construct(#[\SensitiveParameter] Connection|string $connOrUrl) if (!class_exists(DriverManager::class)) { throw new InvalidArgumentException('Failed to parse DSN. Try running "composer require doctrine/dbal".'); } - $this->conn = DriverManager::getConnection(['url' => $this->filterDsn($connOrUrl)]); + if (class_exists(DsnParser::class)) { + $params = (new DsnParser([ + 'db2' => 'ibm_db2', + 'mssql' => 'pdo_sqlsrv', + 'mysql' => 'pdo_mysql', + 'mysql2' => 'pdo_mysql', + 'postgres' => 'pdo_pgsql', + 'postgresql' => 'pdo_pgsql', + 'pgsql' => 'pdo_pgsql', + 'sqlite' => 'pdo_sqlite', + 'sqlite3' => 'pdo_sqlite', + ]))->parse($this->filterDsn($connOrUrl)); + } else { + $params = ['url' => $this->filterDsn($connOrUrl)]; + } + + $config = new Configuration(); + if (class_exists(DefaultSchemaManagerFactory::class)) { + $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); + } + + $this->conn = DriverManager::getConnection($params, $config); } } diff --git a/src/Symfony/Component/Lock/Store/DoctrineDbalStore.php b/src/Symfony/Component/Lock/Store/DoctrineDbalStore.php index 7c749bfec0a04..0fce5d900cb6d 100644 --- a/src/Symfony/Component/Lock/Store/DoctrineDbalStore.php +++ b/src/Symfony/Component/Lock/Store/DoctrineDbalStore.php @@ -11,12 +11,15 @@ namespace Symfony\Component\Lock\Store; +use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Connection; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Exception as DBALException; use Doctrine\DBAL\Exception\TableNotFoundException; use Doctrine\DBAL\ParameterType; +use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Tools\DsnParser; use Symfony\Component\Lock\Exception\InvalidArgumentException; use Symfony\Component\Lock\Exception\InvalidTtlException; use Symfony\Component\Lock\Exception\LockConflictedException; @@ -68,7 +71,28 @@ public function __construct(Connection|string $connOrUrl, array $options = [], f if (!class_exists(DriverManager::class)) { throw new InvalidArgumentException('Failed to parse the DSN. Try running "composer require doctrine/dbal".'); } - $this->conn = DriverManager::getConnection(['url' => $connOrUrl]); + if (class_exists(DsnParser::class)) { + $params = (new DsnParser([ + 'db2' => 'ibm_db2', + 'mssql' => 'pdo_sqlsrv', + 'mysql' => 'pdo_mysql', + 'mysql2' => 'pdo_mysql', + 'postgres' => 'pdo_pgsql', + 'postgresql' => 'pdo_pgsql', + 'pgsql' => 'pdo_pgsql', + 'sqlite' => 'pdo_sqlite', + 'sqlite3' => 'pdo_sqlite', + ]))->parse($connOrUrl); + } else { + $params = ['url' => $connOrUrl]; + } + + $config = new Configuration(); + if (class_exists(DefaultSchemaManagerFactory::class)) { + $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); + } + + $this->conn = DriverManager::getConnection($params, $config); } } diff --git a/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalPostgreSqlStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalPostgreSqlStoreTest.php index 132903b92da93..c156d95b807b0 100644 --- a/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalPostgreSqlStoreTest.php +++ b/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalPostgreSqlStoreTest.php @@ -11,9 +11,12 @@ namespace Symfony\Component\Lock\Tests\Store; +use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Connection; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Exception as DBALException; +use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; +use Doctrine\DBAL\Tools\DsnParser; use Symfony\Component\Lock\Exception\InvalidArgumentException; use Symfony\Component\Lock\Exception\LockConflictedException; use Symfony\Component\Lock\Key; @@ -38,7 +41,7 @@ public function createPostgreSqlConnection(): Connection $this->markTestSkipped('Missing POSTGRES_HOST env variable'); } - return DriverManager::getConnection(['url' => 'pgsql://postgres:password@'.getenv('POSTGRES_HOST')]); + return self::getDbalConnection('pdo-pgsql://postgres:password@'.getenv('POSTGRES_HOST')); } public function getStore(): PersistingStoreInterface @@ -65,7 +68,7 @@ public function testInvalidDriver($connOrDsn) public static function getInvalidDrivers() { yield ['sqlite:///tmp/foo.db']; - yield [DriverManager::getConnection(['url' => 'sqlite:///tmp/foo.db'])]; + yield [self::getDbalConnection('sqlite:///tmp/foo.db')]; } public function testSaveAfterConflict() @@ -165,4 +168,15 @@ public function testWaitAndSaveReadAfterConflictReleasesLockFromInternalStore() $this->assertTrue($store2->exists($store2Key)); } + + private static function getDbalConnection(string $dsn): Connection + { + $params = class_exists(DsnParser::class) ? (new DsnParser(['sqlite' => 'pdo_sqlite']))->parse($dsn) : ['url' => $dsn]; + $config = new Configuration(); + if (class_exists(DefaultSchemaManagerFactory::class)) { + $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); + } + + return DriverManager::getConnection($params, $config); + } } diff --git a/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalStoreTest.php index edc483ed32c94..514e140530f89 100644 --- a/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalStoreTest.php +++ b/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalStoreTest.php @@ -11,10 +11,12 @@ namespace Symfony\Component\Lock\Tests\Store; +use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Connection; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Exception\TableNotFoundException; use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; use Doctrine\DBAL\Schema\Schema; use Symfony\Component\Lock\Key; use Symfony\Component\Lock\PersistingStoreInterface; @@ -37,7 +39,12 @@ public static function setUpBeforeClass(): void { self::$dbFile = tempnam(sys_get_temp_dir(), 'sf_sqlite_lock'); - $store = new DoctrineDbalStore(DriverManager::getConnection(['driver' => 'pdo_sqlite', 'path' => self::$dbFile])); + $config = new Configuration(); + if (class_exists(DefaultSchemaManagerFactory::class)) { + $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); + } + + $store = new DoctrineDbalStore(DriverManager::getConnection(['driver' => 'pdo_sqlite', 'path' => self::$dbFile], $config)); $store->createTable(); } @@ -53,7 +60,12 @@ protected function getClockDelay() public function getStore(): PersistingStoreInterface { - return new DoctrineDbalStore(DriverManager::getConnection(['driver' => 'pdo_sqlite', 'path' => self::$dbFile])); + $config = new Configuration(); + if (class_exists(DefaultSchemaManagerFactory::class)) { + $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); + } + + return new DoctrineDbalStore(DriverManager::getConnection(['driver' => 'pdo_sqlite', 'path' => self::$dbFile], $config)); } public function testAbortAfterExpiration() diff --git a/src/Symfony/Component/Mailer/Bridge/MailPace/Tests/Transport/MailPaceApiTransportTest.php b/src/Symfony/Component/Mailer/Bridge/MailPace/Tests/Transport/MailPaceApiTransportTest.php index 4c83a21e1e254..f575a2b7d7aeb 100644 --- a/src/Symfony/Component/Mailer/Bridge/MailPace/Tests/Transport/MailPaceApiTransportTest.php +++ b/src/Symfony/Component/Mailer/Bridge/MailPace/Tests/Transport/MailPaceApiTransportTest.php @@ -119,6 +119,57 @@ public function testSendThrowsForErrorResponse() $transport->send($mail); } + public function testSendThrowsForErrorsResponse() + { + $client = new MockHttpClient(static function (string $method, string $url, array $options): ResponseInterface { + return new MockResponse(json_encode([ + 'errors' => [ + 'to' => [ + 'contains a blocked address', + 'number of email addresses exceeds maximum volume', + ], + 'attachments.name' => ['Extension file type blocked, see Docs for full list of allowed file types'], + ], + ]), [ + 'http_code' => 400, + 'response_headers' => [ + 'content-type' => 'application/json', + ], + ]); + }); + $transport = new MailPaceApiTransport('KEY', $client); + $transport->setPort(8984); + + $mail = new Email(); + $mail->subject('Hello!') + ->to(new Address('saif.gmati@symfony.com', 'Saif Eddin')) + ->from(new Address('fabpot@symfony.com', 'Fabien')) + ->text('Hello There!'); + + $this->expectException(HttpTransportException::class); + $this->expectExceptionMessage('Unable to send an email: to: contains a blocked address & number of email addresses exceeds maximum volume; attachments.name: Extension file type blocked, see Docs for full list of allowed file types (code 400).'); + $transport->send($mail); + } + + public function testSendThrowsForInternalServerErrorResponse() + { + $client = new MockHttpClient(static function (string $method, string $url, array $options): ResponseInterface { + return new MockResponse('', ['http_code' => 500]); + }); + $transport = new MailPaceApiTransport('KEY', $client); + $transport->setPort(8984); + + $mail = new Email(); + $mail->subject('Hello!') + ->to(new Address('saif.gmati@symfony.com', 'Saif Eddin')) + ->from(new Address('fabpot@symfony.com', 'Fabien')) + ->text('Hello There!'); + + $this->expectException(HttpTransportException::class); + $this->expectExceptionMessage('Unable to send an email: (code 500).'); + $transport->send($mail); + } + public function testTagAndMetadataHeaders() { $email = new Email(); diff --git a/src/Symfony/Component/Mailer/Bridge/MailPace/Transport/MailPaceApiTransport.php b/src/Symfony/Component/Mailer/Bridge/MailPace/Transport/MailPaceApiTransport.php index 8b578612afa8e..9489b41bb7889 100644 --- a/src/Symfony/Component/Mailer/Bridge/MailPace/Transport/MailPaceApiTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/MailPace/Transport/MailPaceApiTransport.php @@ -67,7 +67,20 @@ protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $e } if (200 !== $statusCode) { - throw new HttpTransportException('Unable to send an email: '.$result['error'].sprintf(' (code %d).', $statusCode), $response); + $errorMessage = 'Unable to send an email: '; + if (isset($result['error'])) { + $errorMessage .= $result['error']; + } elseif (isset($result['errors'])) { + $errors = []; + foreach ($result['errors'] as $key => $val) { + $errors[] = $key.': '.implode(' & ', $val); + } + $errorMessage .= implode('; ', $errors); + } else { + $errorMessage .= 'unknown error'; + } + $errorMessage .= sprintf(' (code %d).', $statusCode); + throw new HttpTransportException($errorMessage, $response); } $sentMessage->setMessageId($result['id']); diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php index 12f48b22f305e..3dae29a4cbd66 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php @@ -15,6 +15,7 @@ use Doctrine\DBAL\Driver\ResultStatement; use Doctrine\DBAL\Exception as DBALException; use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Platforms\MariaDBPlatform; use Doctrine\DBAL\Platforms\MySQL57Platform; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Platforms\SQLServer2012Platform; @@ -388,6 +389,13 @@ public static function providePlatformSql(): iterable 'SELECT m.* FROM messenger_messages m WHERE (m.delivered_at is null OR m.delivered_at < ?) AND (m.available_at <= ?) AND (m.queue_name = ?) ORDER BY available_at ASC LIMIT 1 FOR UPDATE', ]; + if (class_exists(MariaDBPlatform::class)) { + yield 'MariaDB' => [ + new MariaDBPlatform(), + 'SELECT m.* FROM messenger_messages m WHERE (m.delivered_at is null OR m.delivered_at < ?) AND (m.available_at <= ?) AND (m.queue_name = ?) ORDER BY available_at ASC LIMIT 1 FOR UPDATE', + ]; + } + yield 'SQL Server' => [ new SQLServer2012Platform(), 'SELECT m.* FROM messenger_messages m WITH (UPDLOCK, ROWLOCK) WHERE (m.delivered_at is null OR m.delivered_at < ?) AND (m.available_at <= ?) AND (m.queue_name = ?) ORDER BY available_at ASC OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY ', @@ -469,6 +477,13 @@ public function provideFindAllSqlGeneratedByPlatform(): iterable 'SELECT m.* FROM messenger_messages m WHERE (m.delivered_at is null OR m.delivered_at < ?) AND (m.available_at <= ?) AND (m.queue_name = ?) LIMIT 50', ]; + if (class_exists(MariaDBPlatform::class)) { + yield 'MariaDB' => [ + new MariaDBPlatform(), + 'SELECT m.* FROM messenger_messages m WHERE (m.delivered_at is null OR m.delivered_at < ?) AND (m.available_at <= ?) AND (m.queue_name = ?) LIMIT 50', + ]; + } + yield 'SQL Server' => [ new SQLServer2012Platform(), 'SELECT m.* FROM messenger_messages m WHERE (m.delivered_at is null OR m.delivered_at < ?) AND (m.available_at <= ?) AND (m.queue_name = ?) ORDER BY (SELECT 0) OFFSET 0 ROWS FETCH NEXT 50 ROWS ONLY', diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineIntegrationTest.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineIntegrationTest.php index cdf08030a36d9..c9ff7c851e845 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineIntegrationTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineIntegrationTest.php @@ -11,9 +11,12 @@ namespace Symfony\Component\Messenger\Bridge\Doctrine\Tests\Transport; +use Doctrine\DBAL\Configuration; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Result; use Doctrine\DBAL\Schema\AbstractSchemaManager; +use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; +use Doctrine\DBAL\Tools\DsnParser; use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Bridge\Doctrine\Tests\Fixtures\DummyMessage; use Symfony\Component\Messenger\Bridge\Doctrine\Transport\Connection; @@ -30,8 +33,14 @@ class DoctrineIntegrationTest extends TestCase protected function setUp(): void { - $dsn = getenv('MESSENGER_DOCTRINE_DSN') ?: 'sqlite://:memory:'; - $this->driverConnection = DriverManager::getConnection(['url' => $dsn]); + $dsn = getenv('MESSENGER_DOCTRINE_DSN') ?: 'pdo-sqlite://:memory:'; + $params = class_exists(DsnParser::class) ? (new DsnParser())->parse($dsn) : ['url' => $dsn]; + $config = new Configuration(); + if (class_exists(DefaultSchemaManagerFactory::class)) { + $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); + } + + $this->driverConnection = DriverManager::getConnection($params, $config); $this->connection = new Connection([], $this->driverConnection); } @@ -56,8 +65,12 @@ public function testSendWithDelay() ->select('m.available_at') ->from('messenger_messages', 'm') ->where('m.body = :body') - ->setParameter('body', '{"message": "Hi i am delayed"}') - ->execute(); + ->setParameter('body', '{"message": "Hi i am delayed"}'); + if (method_exists($stmt, 'executeQuery')) { + $stmt = $stmt->executeQuery(); + } else { + $stmt = $stmt->execute(); + } $available_at = new \DateTimeImmutable($stmt instanceof Result ? $stmt->fetchOne() : $stmt->fetchColumn()); @@ -167,7 +180,7 @@ public function testItRetrieveTheMessageThatIsOlderThanRedeliverTimeout() public function testTheTransportIsSetupOnGet() { - $this->assertFalse($this->createSchemaManager()->tablesExist('messenger_messages')); + $this->assertFalse($this->createSchemaManager()->tablesExist(['messenger_messages'])); $this->assertNull($this->connection->get()); $this->connection->send('the body', ['my' => 'header']); diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrinePostgreSqlIntegrationTest.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrinePostgreSqlIntegrationTest.php index 5b4f8a248e30a..63fe3ebd4b513 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrinePostgreSqlIntegrationTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrinePostgreSqlIntegrationTest.php @@ -11,9 +11,12 @@ namespace Symfony\Component\Messenger\Bridge\Doctrine\Tests\Transport; +use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Connection; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Schema\AbstractSchemaManager; +use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; +use Doctrine\DBAL\Tools\DsnParser; use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Bridge\Doctrine\Tests\Fixtures\DummyMessage; use Symfony\Component\Messenger\Bridge\Doctrine\Transport\PostgreSqlConnection; @@ -36,7 +39,14 @@ protected function setUp(): void $this->markTestSkipped('Missing POSTGRES_HOST env variable'); } - $this->driverConnection = DriverManager::getConnection(['url' => "pgsql://postgres:password@$host"]); + $url = "pdo-pgsql://postgres:password@$host"; + $params = class_exists(DsnParser::class) ? (new DsnParser())->parse($url) : ['url' => $url]; + $config = new Configuration(); + if (class_exists(DefaultSchemaManagerFactory::class)) { + $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); + } + + $this->driverConnection = DriverManager::getConnection($params, $config); $this->connection = new PostgreSqlConnection(['table_name' => 'queue_table'], $this->driverConnection); $this->connection->setup(); } diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineTransportTest.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineTransportTest.php index d6c5b1f619b79..fea497d14409c 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineTransportTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineTransportTest.php @@ -64,9 +64,9 @@ public function testConfigureSchema() $connection->expects($this->once()) ->method('configureSchema') - ->with($schema, $dbalConnection); + ->with($schema, $dbalConnection, static fn () => true); - $transport->configureSchema($schema, $dbalConnection); + $transport->configureSchema($schema, $dbalConnection, static fn () => true); } private function getTransport(SerializerInterface $serializer = null, Connection $connection = null): DoctrineTransport diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php index 9285c7efc0a03..85f95e088ffe5 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php @@ -145,9 +145,9 @@ public function send(string $body, array $headers, int $delay = 0): string $now, $availableAt, ], [ - null, - null, - null, + Types::STRING, + Types::STRING, + Types::STRING, Types::DATETIME_IMMUTABLE, Types::DATETIME_IMMUTABLE, ]); @@ -274,7 +274,7 @@ public function setup(): void { $configuration = $this->driverConnection->getConfiguration(); $assetFilter = $configuration->getSchemaAssetsFilter(); - $configuration->setSchemaAssetsFilter(null); + $configuration->setSchemaAssetsFilter(static function () { return true; }); $this->updateSchema(); $configuration->setSchemaAssetsFilter($assetFilter); $this->autoSetup = false; @@ -478,13 +478,41 @@ private function updateSchema(): void $schemaManager = $this->createSchemaManager(); $comparator = $this->createComparator($schemaManager); - $schemaDiff = $this->compareSchemas($comparator, $schemaManager->createSchema(), $this->getSchema()); + $schemaDiff = $this->compareSchemas($comparator, method_exists($schemaManager, 'introspectSchema') ? $schemaManager->introspectSchema() : $schemaManager->createSchema(), $this->getSchema()); + $platform = $this->driverConnection->getDatabasePlatform(); + $exec = method_exists($this->driverConnection, 'executeStatement') ? 'executeStatement' : 'exec'; - foreach ($schemaDiff->toSaveSql($this->driverConnection->getDatabasePlatform()) as $sql) { - if (method_exists($this->driverConnection, 'executeStatement')) { - $this->driverConnection->executeStatement($sql); - } else { - $this->driverConnection->exec($sql); + if (!method_exists(SchemaDiff::class, 'getCreatedSchemas')) { + foreach ($schemaDiff->toSaveSql($platform) as $sql) { + $this->driverConnection->$exec($sql); + } + + return; + } + + if ($platform->supportsSchemas()) { + foreach ($schemaDiff->getCreatedSchemas() as $schema) { + $this->driverConnection->$exec($platform->getCreateSchemaSQL($schema)); + } + } + + if ($platform->supportsSequences()) { + foreach ($schemaDiff->getAlteredSequences() as $sequence) { + $this->driverConnection->$exec($platform->getAlterSequenceSQL($sequence)); + } + + foreach ($schemaDiff->getCreatedSequences() as $sequence) { + $this->driverConnection->$exec($platform->getCreateSequenceSQL($sequence)); + } + } + + foreach ($platform->getCreateTablesSQL($schemaDiff->getCreatedTables()) as $sql) { + $this->driverConnection->$exec($sql); + } + + foreach ($schemaDiff->getAlteredTables() as $tableDiff) { + foreach ($platform->getAlterTableSQL($tableDiff) as $sql) { + $this->driverConnection->$exec($sql); } } } @@ -505,7 +533,7 @@ private function createComparator(AbstractSchemaManager $schemaManager): Compara private function compareSchemas(Comparator $comparator, Schema $from, Schema $to): SchemaDiff { - return method_exists($comparator, 'compareSchemas') + return method_exists($comparator, 'compareSchemas') || method_exists($comparator, 'doCompareSchemas') ? $comparator->compareSchemas($from, $to) : $comparator->compare($from, $to); } diff --git a/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php b/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php index 73c4013b6bf3f..ba775b0c1784b 100644 --- a/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php +++ b/src/Symfony/Component/Messenger/EventListener/SendFailedMessageForRetryListener.php @@ -120,8 +120,7 @@ public static function getSubscribedEvents(): array private function shouldRetry(\Throwable $e, Envelope $envelope, RetryStrategyInterface $retryStrategy): bool { - $isRetryable = $retryStrategy->isRetryable($envelope, $e); - if ($isRetryable && $e instanceof RecoverableExceptionInterface) { + if ($e instanceof RecoverableExceptionInterface) { return true; } @@ -130,7 +129,7 @@ private function shouldRetry(\Throwable $e, Envelope $envelope, RetryStrategyInt if ($e instanceof HandlerFailedException) { $shouldNotRetry = true; foreach ($e->getNestedExceptions() as $nestedException) { - if ($isRetryable && $nestedException instanceof RecoverableExceptionInterface) { + if ($nestedException instanceof RecoverableExceptionInterface) { return true; } @@ -148,7 +147,7 @@ private function shouldRetry(\Throwable $e, Envelope $envelope, RetryStrategyInt return false; } - return $isRetryable; + return $retryStrategy->isRetryable($envelope, $e); } private function getRetryStrategyForTransport(string $alias): ?RetryStrategyInterface diff --git a/src/Symfony/Component/Messenger/EventListener/StopWorkerOnSignalsListener.php b/src/Symfony/Component/Messenger/EventListener/StopWorkerOnSignalsListener.php index e49e80fe295a2..368c5c5b61862 100644 --- a/src/Symfony/Component/Messenger/EventListener/StopWorkerOnSignalsListener.php +++ b/src/Symfony/Component/Messenger/EventListener/StopWorkerOnSignalsListener.php @@ -29,7 +29,7 @@ public function __construct(array $signals = null, LoggerInterface $logger = nul if (null === $signals && \defined('SIGTERM')) { $signals = [SIGTERM, SIGINT]; } - $this->signals = $signals; + $this->signals = $signals ?? []; $this->logger = $logger; } diff --git a/src/Symfony/Component/Messenger/Message/RedispatchMessage.php b/src/Symfony/Component/Messenger/Message/RedispatchMessage.php index 6a6b8056ab3aa..31ba78a9acf74 100644 --- a/src/Symfony/Component/Messenger/Message/RedispatchMessage.php +++ b/src/Symfony/Component/Messenger/Message/RedispatchMessage.php @@ -13,9 +13,6 @@ use Symfony\Component\Messenger\Envelope; -/** - * @internal - */ final class RedispatchMessage { /** diff --git a/src/Symfony/Component/Messenger/Tests/EventListener/SendFailedMessageForRetryListenerTest.php b/src/Symfony/Component/Messenger/Tests/EventListener/SendFailedMessageForRetryListenerTest.php index cb79f539dc325..fccdae9bfa215 100644 --- a/src/Symfony/Component/Messenger/Tests/EventListener/SendFailedMessageForRetryListenerTest.php +++ b/src/Symfony/Component/Messenger/Tests/EventListener/SendFailedMessageForRetryListenerTest.php @@ -63,7 +63,7 @@ public function testRecoverableStrategyCausesRetry() $senderLocator->expects($this->once())->method('has')->willReturn(true); $senderLocator->expects($this->once())->method('get')->willReturn($sender); $retryStategy = $this->createMock(RetryStrategyInterface::class); - $retryStategy->expects($this->once())->method('isRetryable')->willReturn(true); + $retryStategy->expects($this->never())->method('isRetryable'); $retryStategy->expects($this->once())->method('getWaitingTime')->willReturn(1000); $retryStrategyLocator = $this->createMock(ContainerInterface::class); $retryStrategyLocator->expects($this->once())->method('has')->willReturn(true); @@ -78,27 +78,6 @@ public function testRecoverableStrategyCausesRetry() $listener->onMessageFailed($event); } - public function testRetryIsOnlyAllowedWhenPermittedByRetryStrategy() - { - $senderLocator = $this->createMock(ContainerInterface::class); - $senderLocator->expects($this->never())->method('has'); - $senderLocator->expects($this->never())->method('get'); - $retryStrategy = $this->createMock(RetryStrategyInterface::class); - $retryStrategy->expects($this->once())->method('isRetryable')->willReturn(false); - $retryStrategy->expects($this->never())->method('getWaitingTime'); - $retryStrategyLocator = $this->createMock(ContainerInterface::class); - $retryStrategyLocator->expects($this->once())->method('has')->willReturn(true); - $retryStrategyLocator->expects($this->once())->method('get')->willReturn($retryStrategy); - - $listener = new SendFailedMessageForRetryListener($senderLocator, $retryStrategyLocator); - - $exception = new RecoverableMessageHandlingException('retry'); - $envelope = new Envelope(new \stdClass()); - $event = new WorkerMessageFailedEvent($envelope, 'my_receiver', $exception); - - $listener->onMessageFailed($event); - } - public function testEnvelopeIsSentToTransportOnRetry() { $exception = new \Exception('no!'); diff --git a/src/Symfony/Component/Notifier/Bridge/Slack/README.md b/src/Symfony/Component/Notifier/Bridge/Slack/README.md index b4062fe460da3..1471ba95cec43 100644 --- a/src/Symfony/Component/Notifier/Bridge/Slack/README.md +++ b/src/Symfony/Component/Notifier/Bridge/Slack/README.md @@ -197,6 +197,34 @@ $chatMessage->options($options); $chatter->send($chatMessage); ``` +Updating a Slack Message +------------------------ + +First, save the full message ID when sending a message: + +```php +use Symfony\Component\Notifier\Bridge\Slack\SlackSentMessage; +use Symfony\Component\Notifier\Message\ChatMessage; + +$sentMessage = $chatter->send(new ChatMessage('Original message')); + +// Make sure that Slack transport was used +if ($sentMessage instanceOf SlackSentMessage) { + $fullMessageId = $sentMessage->getFullMessageId(); +} +``` + +Then, use that full message ID to create a new +``UpdateMessageSlackOptions`` class: + +```php +use Symfony\Component\Notifier\Bridge\Slack\UpdateMessageSlackOptions; +use Symfony\Component\Notifier\Message\ChatMessage; + +$options = new UpdateMessageSlackOptions($fullMessageId); +$chatter->send(new ChatMessage('Updated message', $options)); +``` + Resources --------- diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/CompiledUrlMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/CompiledUrlMatcherDumper.php index e92a5ea3d7823..0e740bdf6c3cd 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/CompiledUrlMatcherDumper.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/CompiledUrlMatcherDumper.php @@ -139,7 +139,8 @@ private function generateCompiledRoutes(): string foreach ($staticRoutes as $path => $routes) { $code .= sprintf(" %s => [\n", self::export($path)); foreach ($routes as $route) { - $code .= sprintf(" [%s, %s, %s, %s, %s, %s, %s],\n", ...array_map([__CLASS__, 'export'], $route)); + $r = array_map([__CLASS__, 'export'], $route); + $code .= sprintf(" [%s, %s, %s, %s, %s, %s, %s],\n", $r[0], $r[1], $r[2], $r[3], $r[4], $r[5], $r[6]); } $code .= " ],\n"; } @@ -151,7 +152,8 @@ private function generateCompiledRoutes(): string foreach ($dynamicRoutes as $path => $routes) { $code .= sprintf(" %s => [\n", self::export($path)); foreach ($routes as $route) { - $code .= sprintf(" [%s, %s, %s, %s, %s, %s, %s],\n", ...array_map([__CLASS__, 'export'], $route)); + $r = array_map([__CLASS__, 'export'], $route); + $code .= sprintf(" [%s, %s, %s, %s, %s, %s, %s],\n", $r[0], $r[1], $r[2], $r[3], $r[4], $r[5], $r[6]); } $code .= " ],\n"; } diff --git a/src/Symfony/Component/Runtime/Internal/ComposerPlugin.php b/src/Symfony/Component/Runtime/Internal/ComposerPlugin.php index a48ff46ff00b1..1861676ad30ab 100644 --- a/src/Symfony/Component/Runtime/Internal/ComposerPlugin.php +++ b/src/Symfony/Component/Runtime/Internal/ComposerPlugin.php @@ -105,7 +105,12 @@ public function updateAutoloadFile(): void '%runtime_options%' => '['.substr(var_export($extra, true), 7, -1)." 'project_dir' => {$projectDir},\n]", ]); - file_put_contents(substr_replace($autoloadFile, '_runtime', -4, 0), $code); + // 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 diff --git a/src/Symfony/Component/Runtime/composer.json b/src/Symfony/Component/Runtime/composer.json index a9f91def34935..7d0ac18a282fa 100644 --- a/src/Symfony/Component/Runtime/composer.json +++ b/src/Symfony/Component/Runtime/composer.json @@ -21,7 +21,7 @@ }, "require-dev": { "composer/composer": "^1.0.2|^2.0", - "symfony/console": "^5.4|^6.0", + "symfony/console": "^5.4.9|^6.0.9", "symfony/dotenv": "^5.4|^6.0", "symfony/http-foundation": "^5.4|^6.0", "symfony/http-kernel": "^5.4|^6.0" diff --git a/src/Symfony/Component/Security/Http/AccessToken/Oidc/OidcTokenHandler.php b/src/Symfony/Component/Security/Http/AccessToken/Oidc/OidcTokenHandler.php index 0623a60016a0d..d595bfa88d4c9 100644 --- a/src/Symfony/Component/Security/Http/AccessToken/Oidc/OidcTokenHandler.php +++ b/src/Symfony/Component/Security/Http/AccessToken/Oidc/OidcTokenHandler.php @@ -95,7 +95,7 @@ public function getUserBadgeFrom(string $accessToken): UserBadge // UserLoader argument can be overridden by a UserProvider on AccessTokenAuthenticator::authenticate return new UserBadge($claims[$this->claim], fn () => $this->createUser($claims), $claims); } catch (\Exception $e) { - $this->logger?->error('An error while decoding and validating the token.', [ + $this->logger?->error('An error occurred while decoding and validating the token.', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ]); diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index a741bd3d9f034..0dba039bc0651 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -794,6 +794,10 @@ private function removeNestedValue(array $path, array $data): array */ private function getMappedClass(array $data, string $class, array $context): string { + if (null !== $object = $this->extractObjectToPopulate($class, $context, self::OBJECT_TO_POPULATE)) { + return $object::class; + } + if (!$mapping = $this->classDiscriminatorResolver?->getMappingForClass($class)) { return $class; } diff --git a/src/Symfony/Component/Serializer/Normalizer/CustomNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/CustomNormalizer.php index dd64c9dcab193..7e67a31a91ce7 100644 --- a/src/Symfony/Component/Serializer/Normalizer/CustomNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/CustomNormalizer.php @@ -28,6 +28,7 @@ public function getSupportedTypes(?string $format): array { return [ NormalizableInterface::class => __CLASS__ === static::class || $this->hasCacheableSupportsMethod(), + DenormalizableInterface::class => __CLASS__ === static::class || $this->hasCacheableSupportsMethod(), ]; } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php index ed5dcb97dd463..78046229eb329 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -34,6 +34,7 @@ use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; +use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; @@ -44,6 +45,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummy; use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummyFirstChild; use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummySecondChild; +use Symfony\Component\Serializer\Tests\Fixtures\DummyFirstChildQuux; use Symfony\Component\Serializer\Tests\Fixtures\DummySecondChildQuux; use Symfony\Component\Serializer\Tests\Normalizer\Features\ObjectDummyWithContextAttribute; @@ -480,6 +482,61 @@ public function hasMetadataFor($value): bool $this->assertInstanceOf(DummySecondChildQuux::class, $normalizedData->quux); } + public function testDenormalizeWithDiscriminatorMapAndObjectToPopulateUsesCorrectClassname() + { + $factory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + + $loaderMock = new class() implements ClassMetadataFactoryInterface { + public function getMetadataFor($value): ClassMetadataInterface + { + if (AbstractDummy::class === $value) { + return new ClassMetadata( + AbstractDummy::class, + new ClassDiscriminatorMapping('type', [ + 'first' => AbstractDummyFirstChild::class, + 'second' => AbstractDummySecondChild::class, + ]) + ); + } + + throw new InvalidArgumentException(); + } + + public function hasMetadataFor($value): bool + { + return AbstractDummy::class === $value; + } + }; + + $discriminatorResolver = new ClassDiscriminatorFromClassMetadata($loaderMock); + $normalizer = new AbstractObjectNormalizerDummy($factory, null, new PhpDocExtractor(), $discriminatorResolver); + $serializer = new Serializer([$normalizer]); + $normalizer->setSerializer($serializer); + + $data = [ + 'foo' => 'foo', + 'quux' => ['value' => 'quux'], + ]; + + $normalizedData1 = $normalizer->denormalize($data + ['bar' => 'bar'], AbstractDummy::class, 'any', [ + AbstractNormalizer::OBJECT_TO_POPULATE => new AbstractDummyFirstChild('notfoo', 'notbar'), + ]); + $this->assertInstanceOf(AbstractDummyFirstChild::class, $normalizedData1); + $this->assertSame('foo', $normalizedData1->foo); + $this->assertSame('notbar', $normalizedData1->bar); + $this->assertInstanceOf(DummyFirstChildQuux::class, $normalizedData1->quux); + $this->assertSame('quux', $normalizedData1->quux->getValue()); + + $normalizedData2 = $normalizer->denormalize($data + ['baz' => 'baz'], AbstractDummy::class, 'any', [ + AbstractNormalizer::OBJECT_TO_POPULATE => new AbstractDummySecondChild('notfoo', 'notbaz'), + ]); + $this->assertInstanceOf(AbstractDummySecondChild::class, $normalizedData2); + $this->assertSame('foo', $normalizedData2->foo); + $this->assertSame('baz', $normalizedData2->baz); + $this->assertInstanceOf(DummySecondChildQuux::class, $normalizedData2->quux); + $this->assertSame('quux', $normalizedData2->quux->getValue()); + } + public function testDenormalizeWithNestedDiscriminatorMap() { $classDiscriminatorResolver = new class() implements ClassDiscriminatorResolverInterface { @@ -667,6 +724,27 @@ public function testNormalizeUsesContextAttributeForProperties() $this->assertSame(['propertyWithoutNullSkipNullValues' => 'foo'], $data); } + + public function testDefaultExcludeFromCacheKey() + { + $object = new DummyChild(); + $object->bar = 'not called'; + + $normalizer = new class(null, null, null, null, null, [AbstractObjectNormalizer::EXCLUDE_FROM_CACHE_KEY => ['foo']]) extends AbstractObjectNormalizerDummy { + public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool + { + AbstractObjectNormalizerTest::assertContains('foo', $this->defaultContext[ObjectNormalizer::EXCLUDE_FROM_CACHE_KEY]); + $data->bar = 'called'; + + return true; + } + }; + + $serializer = new Serializer([$normalizer]); + $serializer->normalize($object); + + $this->assertSame('called', $object->bar); + } } class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php index 5eba0707c67ea..d87b7a67a6f80 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php @@ -680,19 +680,6 @@ public function testNormalizeNotSerializableContext() }])); } - public function testDefaultExcludeFromCacheKey() - { - $normalizer = new class(null, null, null, null, null, null, [ObjectNormalizer::EXCLUDE_FROM_CACHE_KEY => ['foo']]) extends ObjectNormalizer { - protected function isCircularReference($object, &$context): bool - { - ObjectNormalizerTest::assertContains('foo', $this->defaultContext[ObjectNormalizer::EXCLUDE_FROM_CACHE_KEY]); - - return false; - } - }; - $normalizer->normalize(new ObjectDummy()); - } - public function testThrowUnexpectedValueException() { $this->expectException(UnexpectedValueException::class); diff --git a/src/Symfony/Component/Serializer/Tests/SerializerTest.php b/src/Symfony/Component/Serializer/Tests/SerializerTest.php index 914458fb4225c..f4a164dc5b3f1 100644 --- a/src/Symfony/Component/Serializer/Tests/SerializerTest.php +++ b/src/Symfony/Component/Serializer/Tests/SerializerTest.php @@ -50,6 +50,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummy; use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummyFirstChild; use Symfony\Component\Serializer\Tests\Fixtures\Annotations\AbstractDummySecondChild; +use Symfony\Component\Serializer\Tests\Fixtures\DenormalizableDummy; use Symfony\Component\Serializer\Tests\Fixtures\DummyFirstChildQuux; use Symfony\Component\Serializer\Tests\Fixtures\DummyMessageInterface; use Symfony\Component\Serializer\Tests\Fixtures\DummyMessageNumberOne; @@ -120,6 +121,14 @@ public function testDenormalizeNoMatch() $serializer->denormalize('foo', 'stdClass'); } + public function testDenormalizeOnObjectThatOnlySupportsDenormalization() + { + $serializer = new Serializer([new CustomNormalizer()]); + + $obj = $serializer->denormalize('foo', (new DenormalizableDummy())::class, 'xml'); + $this->assertInstanceOf(DenormalizableDummy::class, $obj); + } + public function testDenormalizeOnNormalizer() { $this->expectException(UnexpectedValueException::class); diff --git a/src/Symfony/Component/Validator/Constraints/PasswordStrength.php b/src/Symfony/Component/Validator/Constraints/PasswordStrength.php index b41965879f5eb..816708d67ea91 100644 --- a/src/Symfony/Component/Validator/Constraints/PasswordStrength.php +++ b/src/Symfony/Component/Validator/Constraints/PasswordStrength.php @@ -40,13 +40,14 @@ final class PasswordStrength extends Constraint public int $minScore; - public function __construct(array $options = null, int $minScore = null, array $groups = null, mixed $payload = null) + public function __construct(array $options = null, int $minScore = null, array $groups = null, mixed $payload = null, string $message = null) { $options['minScore'] ??= self::STRENGTH_MEDIUM; parent::__construct($options, $groups, $payload); $this->minScore = $minScore ?? $this->minScore; + $this->message = $message ?? $this->message; if ($this->minScore < 1 || 4 < $this->minScore) { throw new ConstraintDefinitionException(sprintf('The parameter "minScore" of the "%s" constraint must be an integer between 1 and 4.', self::class)); diff --git a/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php b/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php index 305a597665b17..b78b39b42cf83 100644 --- a/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php +++ b/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php @@ -119,97 +119,83 @@ public function getValidator(): ValidatorInterface; public function getObject(): ?object; /** - * Sets the currently validated value. + * Warning: Should not be called by user code, to be used by the validator engine only. * * @param object|null $object The currently validated object * @param string $propertyPath The property path to the current value * - * @internal Used by the validator engine. Should not be called by user - * code. + * @return void */ - public function setNode(mixed $value, ?object $object, MetadataInterface $metadata = null, string $propertyPath): void; + public function setNode(mixed $value, ?object $object, MetadataInterface $metadata = null, string $propertyPath); /** - * Sets the currently validated group. + * Warning: Should not be called by user code, to be used by the validator engine only. * * @param string|null $group The validated group * - * @internal Used by the validator engine. Should not be called by user - * code. + * @return void */ - public function setGroup(?string $group): void; + public function setGroup(?string $group); /** - * Sets the currently validated constraint. + * Warning: Should not be called by user code, to be used by the validator engine only. * - * @internal Used by the validator engine. Should not be called by user - * code. + * @return void */ - public function setConstraint(Constraint $constraint): void; + public function setConstraint(Constraint $constraint); /** - * Marks an object as validated in a specific validation group. + * Warning: Should not be called by user code, to be used by the validator engine only. * * @param string $cacheKey The hash of the object * @param string $groupHash The group's name or hash, if it is group * sequence * - * @internal Used by the validator engine. Should not be called by user - * code. + * @return void */ - public function markGroupAsValidated(string $cacheKey, string $groupHash): void; + public function markGroupAsValidated(string $cacheKey, string $groupHash); /** - * Returns whether an object was validated in a specific validation group. + * Warning: Should not be called by user code, to be used by the validator engine only. * * @param string $cacheKey The hash of the object * @param string $groupHash The group's name or hash, if it is group * sequence - * - * @internal Used by the validator engine */ public function isGroupValidated(string $cacheKey, string $groupHash): bool; /** - * Marks a constraint as validated for an object. + * Warning: Should not be called by user code, to be used by the validator engine only. * * @param string $cacheKey The hash of the object * @param string $constraintHash The hash of the constraint * - * @internal Used by the validator engine. Should not be called by user - * code. + * @return void */ - public function markConstraintAsValidated(string $cacheKey, string $constraintHash): void; + public function markConstraintAsValidated(string $cacheKey, string $constraintHash); /** - * Returns whether a constraint was validated for an object. + * Warning: Should not be called by user code, to be used by the validator engine only. * * @param string $cacheKey The hash of the object * @param string $constraintHash The hash of the constraint - * - * @internal Used by the validator engine */ public function isConstraintValidated(string $cacheKey, string $constraintHash): bool; /** - * Marks that an object was initialized. + * Warning: Should not be called by user code, to be used by the validator engine only. * * @param string $cacheKey The hash of the object * - * @internal Used by the validator engine. Should not be called by user - * code. - * * @see ObjectInitializerInterface */ public function markObjectAsInitialized(string $cacheKey): void; /** - * Returns whether an object was initialized. + * Warning: Should not be called by user code, to be used by the validator engine only. * * @param string $cacheKey The hash of the object * - * @internal Used by the validator engine - * * @see ObjectInitializerInterface */ public function isObjectInitialized(string $cacheKey): bool; diff --git a/src/Symfony/Component/Validator/Mapping/ClassMetadata.php b/src/Symfony/Component/Validator/Mapping/ClassMetadata.php index f5f084f2ed21c..17f58466ce9a8 100644 --- a/src/Symfony/Component/Validator/Mapping/ClassMetadata.php +++ b/src/Symfony/Component/Validator/Mapping/ClassMetadata.php @@ -346,16 +346,17 @@ public function mergeConstraints(self $source) $constraint->addImplicitGroupName($this->getDefaultGroup()); } - $this->addPropertyMetadata($member); - if ($member instanceof MemberMetadata && !$member->isPrivate($this->name)) { $property = $member->getPropertyName(); + $this->members[$property] = [$member]; - if ($member instanceof PropertyMetadata && !isset($this->properties[$property])) { + if ($member instanceof PropertyMetadata) { $this->properties[$property] = $member; - } elseif ($member instanceof GetterMetadata && !isset($this->getters[$property])) { + } elseif ($member instanceof GetterMetadata) { $this->getters[$property] = $member; } + } else { + $this->addPropertyMetadata($member); } } } diff --git a/src/Symfony/Component/Validator/Mapping/Loader/AnnotationLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/AnnotationLoader.php index a2842343f2be0..b0252afc3d208 100644 --- a/src/Symfony/Component/Validator/Mapping/Loader/AnnotationLoader.php +++ b/src/Symfony/Component/Validator/Mapping/Loader/AnnotationLoader.php @@ -92,27 +92,49 @@ public function loadClassMetadata(ClassMetadata $metadata): bool */ private function getAnnotations(object $reflection): iterable { + $dedup = []; + foreach ($reflection->getAttributes(GroupSequence::class) as $attribute) { + $dedup[] = $attribute->newInstance(); yield $attribute->newInstance(); } foreach ($reflection->getAttributes(GroupSequenceProvider::class) as $attribute) { + $dedup[] = $attribute->newInstance(); yield $attribute->newInstance(); } foreach ($reflection->getAttributes(Constraint::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) { + $dedup[] = $attribute->newInstance(); yield $attribute->newInstance(); } if (!$this->reader) { return; } + $annotations = []; + if ($reflection instanceof \ReflectionClass) { - yield from $this->reader->getClassAnnotations($reflection); + $annotations = $this->reader->getClassAnnotations($reflection); } if ($reflection instanceof \ReflectionMethod) { - yield from $this->reader->getMethodAnnotations($reflection); + $annotations = $this->reader->getMethodAnnotations($reflection); } if ($reflection instanceof \ReflectionProperty) { - yield from $this->reader->getPropertyAnnotations($reflection); + $annotations = $this->reader->getPropertyAnnotations($reflection); + } + + foreach ($dedup as $annotation) { + if ($annotation instanceof Constraint) { + $annotation->groups; // trigger initialization of the "groups" property + } + } + + foreach ($annotations as $annotation) { + if ($annotation instanceof Constraint) { + $annotation->groups; // trigger initialization of the "groups" property + } + if (!\in_array($annotation, $dedup, false)) { + yield $annotation; + } } } } diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf index f6772fdfb67ba..aaf6ada6fc089 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf @@ -406,6 +406,26 @@ 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. 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. + + The password strength is too low. Please use a stronger password. + The password strength is too low. Please use a stronger password. + + + This value contains characters that are not allowed by the current restriction-level. + This value contains characters that are not allowed by the current restriction-level. + + + Using invisible characters is not allowed. + Using invisible characters is not allowed. + + + Mixing numbers from different scripts is not allowed. + Mixing numbers from different scripts is not allowed. + + + Using hidden overlay characters is not allowed. + Using hidden overlay characters is not allowed. + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf index 92127773178e7..a1186891f4ad2 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf @@ -402,6 +402,30 @@ The value of the netmask should be between {{ min }} and {{ max }}. La valeur du masque de réseau doit être comprise entre {{ min }} et {{ 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. + Le nom du fichier est trop long. Il doit contenir au maximum {{ filename_max_length }} caractère.|Le nom de fichier est trop long. Il doit contenir au maximum {{ filename_max_length }} caractères. + + + The password strength is too low. Please use a stronger password. + La force du mot de passe est trop faible. Veuillez utiliser un mot de passe plus fort. + + + This value contains characters that are not allowed by the current restriction-level. + Cette valeur contient des caractères qui ne sont pas autorisés par le niveau de restriction actuel. + + + Using invisible characters is not allowed. + Utiliser des caractères invisibles n'est pas autorisé. + + + Mixing numbers from different scripts is not allowed. + Mélanger des chiffres provenant de différents scripts n'est pas autorisé. + + + Using hidden overlay characters is not allowed. + Utiliser des caractères de superposition cachés n'est pas autorisé. + diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.pl.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.pl.xlf index b983b2d6c877f..e20f490970958 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.pl.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.pl.xlf @@ -402,6 +402,30 @@ The value of the netmask should be between {{ min }} and {{ max }}. Wartość maski podsieci powinna być pomiędzy {{ min }} i {{ 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. + Nazwa pliku jest za długa. Powinna mieć {{ filename_max_length }} znak lub mniej.|Nazwa pliku jest za długa. Powinna mieć {{ filename_max_length }} znaków lub mniej. + + + The password strength is too low. Please use a stronger password. + Siła hasła jest zbyt niska. Użyj mocniejszego hasła. + + + This value contains characters that are not allowed by the current restriction-level. + Ta wartość zawiera znaki, które nie są dozwolone przez aktualny poziom ograniczeń. + + + Using invisible characters is not allowed. + Używanie niewidzialnych znaków jest niedozwolone. + + + Mixing numbers from different scripts is not allowed. + Mieszanie liczb z różnych skryptów jest niedozwolone. + + + Using hidden overlay characters is not allowed. + Używanie ukrytych znaków nakładki jest niedozwolone. + diff --git a/src/Symfony/Component/Validator/Tests/Constraints/PasswordStrengthTest.php b/src/Symfony/Component/Validator/Tests/Constraints/PasswordStrengthTest.php index 2cfe6a3abb697..713c30deec7cf 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/PasswordStrengthTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/PasswordStrengthTest.php @@ -25,9 +25,10 @@ public function testConstructor() public function testConstructorWithParameters() { - $constraint = new PasswordStrength(minScore: PasswordStrength::STRENGTH_STRONG); + $constraint = new PasswordStrength(minScore: PasswordStrength::STRENGTH_STRONG, message: 'This password should be strong.'); $this->assertSame(PasswordStrength::STRENGTH_STRONG, $constraint->minScore); + $this->assertSame('This password should be strong.', $constraint->message); } public function testInvalidScoreOfZero() diff --git a/src/Symfony/Component/Validator/Tests/Constraints/PasswordStrengthValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/PasswordStrengthValidatorTest.php index 474dd889c3686..21dabcad738a4 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/PasswordStrengthValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/PasswordStrengthValidatorTest.php @@ -77,5 +77,11 @@ public static function provideInvalidConstraints(): iterable 'The password strength is too low. Please use a stronger password.', PasswordStrength::PASSWORD_STRENGTH_ERROR, ]; + yield [ + new PasswordStrength(message: 'This password should be strong.'), + 'password', + 'This password should be strong.', + PasswordStrength::PASSWORD_STRENGTH_ERROR, + ]; } } diff --git a/src/Symfony/Component/VarDumper/Caster/DateCaster.php b/src/Symfony/Component/VarDumper/Caster/DateCaster.php index 1394a78132a17..a0cbddb76f114 100644 --- a/src/Symfony/Component/VarDumper/Caster/DateCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/DateCaster.php @@ -30,7 +30,7 @@ class DateCaster public static function castDateTime(\DateTimeInterface $d, array $a, Stub $stub, bool $isNested, int $filter) { $prefix = Caster::PREFIX_VIRTUAL; - $location = $d->getTimezone()->getLocation(); + $location = $d->getTimezone() ? $d->getTimezone()->getLocation() : null; $fromNow = (new \DateTimeImmutable())->diff($d); $title = $d->format('l, F j, Y') diff --git a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php index 345a89cf726e0..8a2570b2c4fb9 100644 --- a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php @@ -163,9 +163,7 @@ protected function getDumpHeader()