From 90d1b059fddfbe4e12e63c297194ce1b5871c8ad Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Mon, 8 Jul 2019 13:53:30 -0400 Subject: [PATCH 01/19] Adding missing event_dispatcher wiring for messenger.middleware.send_message --- .../Bundle/FrameworkBundle/Resources/config/messenger.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.xml index a060b723b8..5ef47e751e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/messenger.xml @@ -15,6 +15,7 @@ + From 6eee2a814484b5aa0668a60fe54e916004fc6a1f Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 11 Jul 2019 12:21:37 +0200 Subject: [PATCH 02/19] [HttpKernel] fix tests --- .../HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php b/src/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php index 60ad1b89c4..c66732a37c 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php @@ -25,7 +25,7 @@ class TraceableEventDispatcherTest extends TestCase public function testStopwatchSections() { $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), $stopwatch = new Stopwatch()); - $kernel = $this->getHttpKernel($dispatcher, function () { return new Response(); }); + $kernel = $this->getHttpKernel($dispatcher, function () { return new Response('', 200, ['X-Debug-Token' => '292e1e']); }); $request = Request::create('/'); $response = $kernel->handle($request); $kernel->terminate($request, $response); From c7141c82d1e62ef1320a4652283ddff4fdd5bd83 Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Thu, 11 Jul 2019 20:09:53 +0200 Subject: [PATCH 03/19] [Debug][DebugClassLoader] Include found files instead of requiring them --- src/Symfony/Component/Debug/DebugClassLoader.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Debug/DebugClassLoader.php b/src/Symfony/Component/Debug/DebugClassLoader.php index 9ceac9af16..b63c6adee9 100644 --- a/src/Symfony/Component/Debug/DebugClassLoader.php +++ b/src/Symfony/Component/Debug/DebugClassLoader.php @@ -149,11 +149,11 @@ class DebugClassLoader if (!$file = $this->classLoader[0]->findFile($class) ?: false) { // no-op } elseif (\function_exists('opcache_is_script_cached') && @opcache_is_script_cached($file)) { - require $file; + include $file; return; } else { - require $file; + include $file; } } else { \call_user_func($this->classLoader, $class); From c5ee4bedc2bd4208b6677078029c40332ef73ee5 Mon Sep 17 00:00:00 2001 From: Paulo Ribeiro Date: Thu, 11 Jul 2019 16:09:41 -0300 Subject: [PATCH 04/19] [FrameworkBundle] Fix descriptor of routes described as callable array --- .../FrameworkBundle/Console/Descriptor/TextDescriptor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php index 796beaee01..18b13a215c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php @@ -540,7 +540,7 @@ class TextDescriptor extends Descriptor try { if (\is_array($controller)) { - $r = new \ReflectionMethod($controller); + $r = new \ReflectionMethod($controller[0], $controller[1]); } elseif ($controller instanceof \Closure) { $r = new \ReflectionFunction($controller); } elseif (method_exists($controller, '__invoke')) { From ee5e5de9e031fa037bf099ef12e485d583e36fcd Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 12 Jul 2019 08:50:39 +0300 Subject: [PATCH 05/19] fixed CS --- .../Tests/DependencyInjection/FrameworkExtensionTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index b0356b3c14..c114f38177 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -23,7 +23,6 @@ use Symfony\Component\Cache\Adapter\DoctrineAdapter; use Symfony\Component\Cache\Adapter\FilesystemAdapter; use Symfony\Component\Cache\Adapter\ProxyAdapter; use Symfony\Component\Cache\Adapter\RedisAdapter; -use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; From 871ca3713af4714db6e6cf292d857f1ab2304d63 Mon Sep 17 00:00:00 2001 From: JoppeDC Date: Fri, 12 Jul 2019 10:08:35 +0200 Subject: [PATCH 06/19] Added Nl translations Added NL translations for the validator when both `min` and `max` are set. --- .../Validator/Resources/translations/validators.nl.xlf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.nl.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.nl.xlf index 478ca19753..3b2eb4131b 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.nl.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.nl.xlf @@ -362,6 +362,10 @@ This password has been leaked in a data breach, it must not be used. Please use another password. Dit wachtwoord is gelekt vanwege een data-inbreuk, het moet niet worden gebruikt. Kies een ander wachtwoord. + + This value should be between {{ min }} and {{ max }}. + Deze waarde moet zich tussen {{ min }} en {{ max }} bevinden. + From 24b7feff91b3da06f9eafb97b017ca635c50260d Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 13 Jul 2019 12:01:31 +0200 Subject: [PATCH 07/19] fix typo --- src/Symfony/Component/Validator/CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Validator/CHANGELOG.md b/src/Symfony/Component/Validator/CHANGELOG.md index 9170f96c93..33b2ba00b7 100644 --- a/src/Symfony/Component/Validator/CHANGELOG.md +++ b/src/Symfony/Component/Validator/CHANGELOG.md @@ -17,9 +17,9 @@ CHANGELOG from an array or object * added the `min_limit_path` and `max_limit_path` parameters in violations when using `Range` constraint with respectively the `minPropertyPath` and - `maxPropertyPath` options. - * added a new `notInRangeMessage` options to the `Range` constraint that will - be used in the violation builder when both `min` and `max` are not null. + `maxPropertyPath` options + * added a new `notInRangeMessage` option to the `Range` constraint that will + be used in the violation builder when both `min` and `max` are not null 4.3.0 ----- From fa317f23f53dd737c8a037f8cf367d89a6eb373d Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 13 Jul 2019 18:25:19 +0200 Subject: [PATCH 08/19] fix some deprecations and add upgrade instructions --- UPGRADE-4.4.md | 14 ++++++++++++++ UPGRADE-5.0.md | 14 ++++++++++++++ .../Bundle/FrameworkBundle/Test/WebTestCase.php | 2 +- src/Symfony/Bundle/WebServerBundle/CHANGELOG.md | 5 +++++ .../WebServerBundle/Command/ServerLogCommand.php | 4 ++-- .../WebServerBundle/Command/ServerRunCommand.php | 4 ++-- .../WebServerBundle/Command/ServerStartCommand.php | 4 ++-- .../Command/ServerStatusCommand.php | 4 ++-- .../WebServerBundle/Command/ServerStopCommand.php | 4 ++-- .../DependencyInjection/WebServerExtension.php | 4 ++-- src/Symfony/Bundle/WebServerBundle/WebServer.php | 2 +- .../Bundle/WebServerBundle/WebServerBundle.php | 4 ++-- .../Bundle/WebServerBundle/WebServerConfig.php | 2 +- src/Symfony/Component/HttpKernel/CHANGELOG.md | 4 +++- src/Symfony/Component/Lock/Store/CombinedStore.php | 2 +- .../Component/Lock/Store/MemcachedStore.php | 2 +- src/Symfony/Component/Lock/Store/PdoStore.php | 2 +- src/Symfony/Component/Lock/Store/RedisStore.php | 2 +- .../Component/Lock/Store/ZookeeperStore.php | 2 +- 19 files changed, 58 insertions(+), 23 deletions(-) diff --git a/UPGRADE-4.4.md b/UPGRADE-4.4.md index 8ffb10d409..216dd7ab83 100644 --- a/UPGRADE-4.4.md +++ b/UPGRADE-4.4.md @@ -71,6 +71,7 @@ Form FrameworkBundle --------------- + * Deprecated booting the kernel before running `WebTestCase::createClient()`. * Deprecated support for `templating` engine in `TemplateController`, use Twig instead * The `$parser` argument of `ControllerResolver::__construct()` and `DelegatingLoader::__construct()` has been deprecated. @@ -90,8 +91,16 @@ HttpFoundation HttpKernel ---------- + * Implementing the `BundleInterface` without implementing the `getPublicDir()` method is deprecated. + This method will be added to the interface in 5.0. * The `DebugHandlersListener` class has been marked as `final` +Lock +---- + + * Deprecated `Symfony\Component\Lock\StoreInterface` in favor of `Symfony\Component\Lock\BlockingStoreInterface` and + `Symfony\Component\Lock\PersistStoreInterface`. + Messenger --------- @@ -143,3 +152,8 @@ WebProfilerBundle * Deprecated the `ExceptionController::templateExists()` method * Deprecated the `TemplateManager::templateExists()` method + +WebServerBundle +--------------- + + * The bundle is deprecated and will be removed in 5.0. diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md index 36ed24ff69..81c9ed02f1 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -208,6 +208,8 @@ Form FrameworkBundle --------------- + * Dropped support for booting the kernel before running `WebTestCase::createClient()`. `createClient()` will throw an + exception if the kernel was already booted before. * Removed the `framework.templating` option, use Twig instead. * The project dir argument of the constructor of `AssetsInstallCommand` is required. * Removed support for `bundle:controller:action` syntax to reference controllers. Use `serviceOrFqcn::method` @@ -274,6 +276,7 @@ HttpFoundation HttpKernel ---------- + * The `getPublicDir()` method has been added to the `BundleInterface`. * Removed `Client`, use `HttpKernelBrowser` instead * The `Kernel::getRootDir()` and the `kernel.root_dir` parameter have been removed * The `KernelInterface::getName()` and the `kernel.name` parameter have been removed @@ -299,6 +302,12 @@ Intl * Removed `Intl::getLocaleBundle()`, use `Locales` instead * Removed `Intl::getRegionBundle()`, use `Countries` instead +Lock +---- + + * Removed `Symfony\Component\Lock\StoreInterface` in favor of `Symfony\Component\Lock\BlockingStoreInterface` and + `Symfony\Component\Lock\PersistStoreInterface`. + Messenger --------- @@ -560,3 +569,8 @@ Yaml * The parser is now stricter and will throw a `ParseException` when a mapping is found inside a multi-line string. + +WebServerBundle +--------------- + + * The bundle has been removed. diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php index 1bcf762c47..5da58fa5b8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php @@ -40,7 +40,7 @@ abstract class WebTestCase extends KernelTestCase protected static function createClient(array $options = [], array $server = []) { if (true === static::$booted) { - @trigger_error(sprintf('Booting the kernel before calling %s::%s is deprecated and will throw in Symfony 5.0, the kernel should only be booted once.', __CLASS__, __METHOD__), E_USER_DEPRECATED); + @trigger_error(sprintf('Booting the kernel before calling %s() is deprecated and will throw in Symfony 5.0, the kernel should only be booted once.', __METHOD__), E_USER_DEPRECATED); } $kernel = static::bootKernel($options); diff --git a/src/Symfony/Bundle/WebServerBundle/CHANGELOG.md b/src/Symfony/Bundle/WebServerBundle/CHANGELOG.md index edc7417cea..b3843ca6c9 100644 --- a/src/Symfony/Bundle/WebServerBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/WebServerBundle/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +4.4.0 +--------------- + + * The bundle is deprecated and will be removed in 5.0. + 4.2.0 ----- diff --git a/src/Symfony/Bundle/WebServerBundle/Command/ServerLogCommand.php b/src/Symfony/Bundle/WebServerBundle/Command/ServerLogCommand.php index b841bd1805..e8d51108cc 100644 --- a/src/Symfony/Bundle/WebServerBundle/Command/ServerLogCommand.php +++ b/src/Symfony/Bundle/WebServerBundle/Command/ServerLogCommand.php @@ -26,7 +26,7 @@ use Symfony\Component\ExpressionLanguage\ExpressionLanguage; /** * @author Grégoire Pineau * - * @deprecated since version 4.4, to be removed in 5.0; the new Symfony local server has more features, you can use it instead. + * @deprecated since Symfony 4.4, to be removed in 5.0; the new Symfony local server has more features, you can use it instead. */ class ServerLogCommand extends Command { @@ -80,7 +80,7 @@ EOF protected function execute(InputInterface $input, OutputInterface $output) { - @trigger_error('Using the WebserverBundle is deprecated since 4.4. The new Symfony local server has more features, you can use it instead.', E_USER_DEPRECATED); + @trigger_error('Using the WebserverBundle is deprecated since Symfony 4.4. The new Symfony local server has more features, you can use it instead.', E_USER_DEPRECATED); $filter = $input->getOption('filter'); if ($filter) { diff --git a/src/Symfony/Bundle/WebServerBundle/Command/ServerRunCommand.php b/src/Symfony/Bundle/WebServerBundle/Command/ServerRunCommand.php index 53c9c807ad..a33f88829f 100644 --- a/src/Symfony/Bundle/WebServerBundle/Command/ServerRunCommand.php +++ b/src/Symfony/Bundle/WebServerBundle/Command/ServerRunCommand.php @@ -27,7 +27,7 @@ use Symfony\Component\Process\Process; * * @author Michał Pipa * - * @deprecated since version 4.4, to be removed in 5.0; the new Symfony local server has more features, you can use it instead. + * @deprecated since Symfony 4.4, to be removed in 5.0; the new Symfony local server has more features, you can use it instead. */ class ServerRunCommand extends Command { @@ -92,7 +92,7 @@ EOF */ protected function execute(InputInterface $input, OutputInterface $output) { - @trigger_error('Using the WebserverBundle is deprecated since 4.4. The new Symfony local server has more features, you can use it instead.', E_USER_DEPRECATED); + @trigger_error('Using the WebserverBundle is deprecated since Symfony 4.4. The new Symfony local server has more features, you can use it instead.', E_USER_DEPRECATED); $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); diff --git a/src/Symfony/Bundle/WebServerBundle/Command/ServerStartCommand.php b/src/Symfony/Bundle/WebServerBundle/Command/ServerStartCommand.php index 3e17c87bce..44008f4438 100644 --- a/src/Symfony/Bundle/WebServerBundle/Command/ServerStartCommand.php +++ b/src/Symfony/Bundle/WebServerBundle/Command/ServerStartCommand.php @@ -27,7 +27,7 @@ use Symfony\Component\EventDispatcher\EventDispatcher; * * @author Christian Flothmann * - * @deprecated since version 4.4, to be removed in 5.0; the new Symfony local server has more features, you can use it instead. + * @deprecated since Symfony 4.4, to be removed in 5.0; the new Symfony local server has more features, you can use it instead. */ class ServerStartCommand extends Command { @@ -92,7 +92,7 @@ EOF */ protected function execute(InputInterface $input, OutputInterface $output) { - @trigger_error('Using the WebserverBundle is deprecated since 4.4. The new Symfony local server has more features, you can use it instead.', E_USER_DEPRECATED); + @trigger_error('Using the WebserverBundle is deprecated since Symfony 4.4. The new Symfony local server has more features, you can use it instead.', E_USER_DEPRECATED); $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); diff --git a/src/Symfony/Bundle/WebServerBundle/Command/ServerStatusCommand.php b/src/Symfony/Bundle/WebServerBundle/Command/ServerStatusCommand.php index 5912b9d883..e49a6c5c10 100644 --- a/src/Symfony/Bundle/WebServerBundle/Command/ServerStatusCommand.php +++ b/src/Symfony/Bundle/WebServerBundle/Command/ServerStatusCommand.php @@ -26,7 +26,7 @@ use Symfony\Component\Console\Style\SymfonyStyle; * * @author Christian Flothmann * - * @deprecated since version 4.4, to be removed in 5.0; the new Symfony local server has more features, you can use it instead. + * @deprecated since Symfony 4.4, to be removed in 5.0; the new Symfony local server has more features, you can use it instead. */ class ServerStatusCommand extends Command { @@ -74,7 +74,7 @@ EOF */ protected function execute(InputInterface $input, OutputInterface $output) { - @trigger_error('Using the WebserverBundle is deprecated since 4.4. The new Symfony local server has more features, you can use it instead.', E_USER_DEPRECATED); + @trigger_error('Using the WebserverBundle is deprecated since Symfony 4.4. The new Symfony local server has more features, you can use it instead.', E_USER_DEPRECATED); $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); $server = new WebServer($this->pidFileDirectory); diff --git a/src/Symfony/Bundle/WebServerBundle/Command/ServerStopCommand.php b/src/Symfony/Bundle/WebServerBundle/Command/ServerStopCommand.php index 762bf6d869..fe3a6b65f7 100644 --- a/src/Symfony/Bundle/WebServerBundle/Command/ServerStopCommand.php +++ b/src/Symfony/Bundle/WebServerBundle/Command/ServerStopCommand.php @@ -24,7 +24,7 @@ use Symfony\Component\Console\Style\SymfonyStyle; * * @author Christian Flothmann * - * @deprecated since version 4.4, to be removed in 5.0; the new Symfony local server has more features, you can use it instead. + * @deprecated since Symfony 4.4, to be removed in 5.0; the new Symfony local server has more features, you can use it instead. */ class ServerStopCommand extends Command { @@ -63,7 +63,7 @@ EOF */ protected function execute(InputInterface $input, OutputInterface $output) { - @trigger_error('Using the WebserverBundle is deprecated since 4.4. The new Symfony local server has more features, you can use it instead.', E_USER_DEPRECATED); + @trigger_error('Using the WebserverBundle is deprecated since Symfony 4.4. The new Symfony local server has more features, you can use it instead.', E_USER_DEPRECATED); $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); diff --git a/src/Symfony/Bundle/WebServerBundle/DependencyInjection/WebServerExtension.php b/src/Symfony/Bundle/WebServerBundle/DependencyInjection/WebServerExtension.php index b7d5878e8c..4b4a245bd0 100644 --- a/src/Symfony/Bundle/WebServerBundle/DependencyInjection/WebServerExtension.php +++ b/src/Symfony/Bundle/WebServerBundle/DependencyInjection/WebServerExtension.php @@ -20,7 +20,7 @@ use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; /** * @author Robin Chalas * - * @deprecated since version 4.4, to be removed in 5.0; the new Symfony local server has more features, you can use it instead. + * @deprecated since Symfony 4.4, to be removed in 5.0; the new Symfony local server has more features, you can use it instead. */ class WebServerExtension extends Extension { @@ -43,7 +43,7 @@ class WebServerExtension extends Extension $container->removeDefinition('web_server.command.server_log'); } - @trigger_error('Using the WebserverBundle is deprecated since 4.3, the new symfony local server has more feature, you should use it instead.'); + @trigger_error('Using the WebserverBundle is deprecated since Symfony 4.4, the new symfony local server has more feature, you should use it instead.', E_USER_DEPRECATED); } private function getPublicDirectory(ContainerBuilder $container) diff --git a/src/Symfony/Bundle/WebServerBundle/WebServer.php b/src/Symfony/Bundle/WebServerBundle/WebServer.php index 978c5bb17a..51efcc0be8 100644 --- a/src/Symfony/Bundle/WebServerBundle/WebServer.php +++ b/src/Symfony/Bundle/WebServerBundle/WebServer.php @@ -20,7 +20,7 @@ use Symfony\Component\Process\Process; * * @author Fabien Potencier * - * @deprecated since version 4.4, to be removed in 5.0; the new Symfony local server has more features, you can use it instead. + * @deprecated since Symfony 4.4, to be removed in 5.0; the new Symfony local server has more features, you can use it instead. */ class WebServer { diff --git a/src/Symfony/Bundle/WebServerBundle/WebServerBundle.php b/src/Symfony/Bundle/WebServerBundle/WebServerBundle.php index 22b541fca3..59cd1c9104 100644 --- a/src/Symfony/Bundle/WebServerBundle/WebServerBundle.php +++ b/src/Symfony/Bundle/WebServerBundle/WebServerBundle.php @@ -14,12 +14,12 @@ namespace Symfony\Bundle\WebServerBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; /** - * @deprecated since version 4.4, to be removed in 5.0; the new Symfony local server has more features, you can use it instead. + * @deprecated since Symfony 4.4, to be removed in 5.0; the new Symfony local server has more features, you can use it instead. */ class WebServerBundle extends Bundle { public function boot() { - @trigger_error('Using the WebserverBundle is deprecated since 4.4. The new Symfony local server has more features, you can use it instead.', E_USER_DEPRECATED); + @trigger_error('Using the WebserverBundle is deprecated since Symfony 4.4. The new Symfony local server has more features, you can use it instead.', E_USER_DEPRECATED); } } diff --git a/src/Symfony/Bundle/WebServerBundle/WebServerConfig.php b/src/Symfony/Bundle/WebServerBundle/WebServerConfig.php index e5ae3eed71..a3140bd92e 100644 --- a/src/Symfony/Bundle/WebServerBundle/WebServerConfig.php +++ b/src/Symfony/Bundle/WebServerBundle/WebServerConfig.php @@ -14,7 +14,7 @@ namespace Symfony\Bundle\WebServerBundle; /** * @author Fabien Potencier * - * @deprecated since version 4.4, to be removed in 5.0; the new Symfony local server has more features, you can use it instead. + * @deprecated since Symfony 4.4, to be removed in 5.0; the new Symfony local server has more features, you can use it instead. */ class WebServerConfig { diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md index 841b8c6635..feae351734 100644 --- a/src/Symfony/Component/HttpKernel/CHANGELOG.md +++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md @@ -4,7 +4,9 @@ CHANGELOG 4.4.0 ----- -* The `DebugHandlersListener` class has been marked as `final` + * Implementing the `BundleInterface` without implementing the `getPublicDir()` method is deprecated. + This method will be added to the interface in 5.0. + * The `DebugHandlersListener` class has been marked as `final` 4.3.0 ----- diff --git a/src/Symfony/Component/Lock/Store/CombinedStore.php b/src/Symfony/Component/Lock/Store/CombinedStore.php index f2a2c4dc57..b5891af830 100644 --- a/src/Symfony/Component/Lock/Store/CombinedStore.php +++ b/src/Symfony/Component/Lock/Store/CombinedStore.php @@ -98,7 +98,7 @@ class CombinedStore implements StoreInterface, LoggerAwareInterface */ public function waitAndSave(Key $key) { - @trigger_error(sprintf('%s::%s has been deprecated since Symfony 4.4 and will be removed in Symfony 5.0.', \get_class($this), __METHOD__), E_USER_DEPRECATED); + @trigger_error(sprintf('%s() is deprecated since Symfony 4.4 and will be removed in Symfony 5.0.', __METHOD__), E_USER_DEPRECATED); throw new NotSupportedException(sprintf('The store "%s" does not supports blocking locks.', \get_class($this))); } diff --git a/src/Symfony/Component/Lock/Store/MemcachedStore.php b/src/Symfony/Component/Lock/Store/MemcachedStore.php index 67b7d8eefb..962f143c76 100644 --- a/src/Symfony/Component/Lock/Store/MemcachedStore.php +++ b/src/Symfony/Component/Lock/Store/MemcachedStore.php @@ -74,7 +74,7 @@ class MemcachedStore implements StoreInterface */ public function waitAndSave(Key $key) { - @trigger_error(sprintf('%s has been deprecated since Symfony 4.4 and will be removed in Symfony 5.0.', __METHOD__)); + @trigger_error(sprintf('%s() is deprecated since Symfony 4.4 and will be removed in Symfony 5.0.', __METHOD__)); throw new InvalidArgumentException(sprintf('The store "%s" does not supports blocking locks.', \get_class($this))); } diff --git a/src/Symfony/Component/Lock/Store/PdoStore.php b/src/Symfony/Component/Lock/Store/PdoStore.php index 70e041f292..dc97630810 100644 --- a/src/Symfony/Component/Lock/Store/PdoStore.php +++ b/src/Symfony/Component/Lock/Store/PdoStore.php @@ -145,7 +145,7 @@ class PdoStore implements StoreInterface */ public function waitAndSave(Key $key) { - @trigger_error(sprintf('%s has been deprecated since Symfony 4.4 and will be removed in Symfony 5.0.', __METHOD__)); + @trigger_error(sprintf('%s() is deprecated since Symfony 4.4 and will be removed in Symfony 5.0.', __METHOD__)); throw new NotSupportedException(sprintf('The store "%s" does not supports blocking locks.', __METHOD__)); } diff --git a/src/Symfony/Component/Lock/Store/RedisStore.php b/src/Symfony/Component/Lock/Store/RedisStore.php index c0ae6ea9b3..4ac41ac347 100644 --- a/src/Symfony/Component/Lock/Store/RedisStore.php +++ b/src/Symfony/Component/Lock/Store/RedisStore.php @@ -77,7 +77,7 @@ class RedisStore implements StoreInterface */ public function waitAndSave(Key $key) { - @trigger_error(sprintf('%s::%s has been deprecated since Symfony 4.4 and will be removed in Symfony 5.0.', \get_class($this), __METHOD__), E_USER_DEPRECATED); + @trigger_error(sprintf('%s() is deprecated since Symfony 4.4 and will be removed in Symfony 5.0.', __METHOD__), E_USER_DEPRECATED); throw new InvalidArgumentException(sprintf('The store "%s" does not supports blocking locks.', \get_class($this))); } diff --git a/src/Symfony/Component/Lock/Store/ZookeeperStore.php b/src/Symfony/Component/Lock/Store/ZookeeperStore.php index 0de0a372f7..d089590f88 100644 --- a/src/Symfony/Component/Lock/Store/ZookeeperStore.php +++ b/src/Symfony/Component/Lock/Store/ZookeeperStore.php @@ -87,7 +87,7 @@ class ZookeeperStore implements StoreInterface */ public function waitAndSave(Key $key) { - @trigger_error(sprintf('%s::%s has been deprecated since Symfony 4.4 and will be removed in Symfony 5.0.', \get_class($this), __METHOD__), E_USER_DEPRECATED); + @trigger_error(sprintf('%s() is deprecated since Symfony 4.4 and will be removed in Symfony 5.0.', __METHOD__), E_USER_DEPRECATED); throw new NotSupportedException(); } From 95e8a651c25de45a35690c43a88010f24a6f701e Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 15 Jul 2019 08:34:13 +0200 Subject: [PATCH 09/19] fixed CS --- src/Symfony/Bundle/WebServerBundle/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/WebServerBundle/CHANGELOG.md b/src/Symfony/Bundle/WebServerBundle/CHANGELOG.md index b3843ca6c9..c056f597a4 100644 --- a/src/Symfony/Bundle/WebServerBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/WebServerBundle/CHANGELOG.md @@ -2,7 +2,7 @@ CHANGELOG ========= 4.4.0 ---------------- +----- * The bundle is deprecated and will be removed in 5.0. From 5f301688ac68b0cdb46e069efb4413574602cb13 Mon Sep 17 00:00:00 2001 From: Amrouche Hamza Date: Fri, 12 Jul 2019 09:14:40 +0200 Subject: [PATCH 10/19] [Lock] add aliases for LockFactory --- UPGRADE-4.4.md | 1 + UPGRADE-5.0.md | 1 + .../FrameworkBundle/DependencyInjection/FrameworkExtension.php | 3 +++ src/Symfony/Bundle/FrameworkBundle/Resources/config/lock.xml | 2 +- src/Symfony/Bundle/FrameworkBundle/composer.json | 3 ++- 5 files changed, 8 insertions(+), 2 deletions(-) diff --git a/UPGRADE-4.4.md b/UPGRADE-4.4.md index 216dd7ab83..67a2c8ba84 100644 --- a/UPGRADE-4.4.md +++ b/UPGRADE-4.4.md @@ -100,6 +100,7 @@ Lock * Deprecated `Symfony\Component\Lock\StoreInterface` in favor of `Symfony\Component\Lock\BlockingStoreInterface` and `Symfony\Component\Lock\PersistStoreInterface`. + * `Factory` is deprecated, use `LockFactory` instead Messenger --------- diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md index 81c9ed02f1..40c38088e5 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -307,6 +307,7 @@ Lock * Removed `Symfony\Component\Lock\StoreInterface` in favor of `Symfony\Component\Lock\BlockingStoreInterface` and `Symfony\Component\Lock\PersistStoreInterface`. + * Removed `Factory`, use `LockFactory` instead Messenger --------- diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 7ed04a5bb1..2c7f8732f5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -71,6 +71,7 @@ use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\Lock\Factory; use Symfony\Component\Lock\Lock; +use Symfony\Component\Lock\LockFactory; use Symfony\Component\Lock\LockInterface; use Symfony\Component\Lock\PersistStoreInterface; use Symfony\Component\Lock\Store\FlockStore; @@ -1644,11 +1645,13 @@ class FrameworkExtension extends Extension $container->setAlias(StoreInterface::class, new Alias('lock.store', false)); $container->setAlias(PersistStoreInterface::class, new Alias('lock.store', false)); $container->setAlias(Factory::class, new Alias('lock.factory', false)); + $container->setAlias(LockFactory::class, new Alias('lock.factory', false)); $container->setAlias(LockInterface::class, new Alias('lock', false)); } else { $container->registerAliasForArgument('lock.'.$resourceName.'.store', StoreInterface::class, $resourceName.'.lock.store'); $container->registerAliasForArgument('lock.'.$resourceName.'.store', PersistStoreInterface::class, $resourceName.'.lock.store'); $container->registerAliasForArgument('lock.'.$resourceName.'.factory', Factory::class, $resourceName.'.lock.factory'); + $container->registerAliasForArgument('lock.'.$resourceName.'.factory', LockFactory::class, $resourceName.'.lock.factory'); $container->registerAliasForArgument('lock.'.$resourceName, LockInterface::class, $resourceName.'.lock'); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/lock.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/lock.xml index ce5b9c8d30..cdefecd176 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/lock.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/lock.xml @@ -26,7 +26,7 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index cb4bec153c..f6abce9d4a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -57,7 +57,7 @@ "symfony/workflow": "^4.3|^5.0", "symfony/yaml": "^3.4|^4.0|^5.0", "symfony/property-info": "^3.4|^4.0|^5.0", - "symfony/lock": "^3.4|^4.0|^5.0", + "symfony/lock": "^4.4|^5.0", "symfony/web-link": "^3.4|^4.0|^5.0", "doctrine/annotations": "~1.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0", @@ -74,6 +74,7 @@ "symfony/dotenv": "<4.2", "symfony/dom-crawler": "<4.3", "symfony/form": "<4.3", + "symfony/lock": "<4.4", "symfony/messenger": "<4.3", "symfony/property-info": "<3.4", "symfony/serializer": "<4.2", From f90a9fd771edfc4c59ae68d10e3cdc4ecd9c8b92 Mon Sep 17 00:00:00 2001 From: Pierre du Plessis Date: Mon, 22 Apr 2019 08:42:21 +0200 Subject: [PATCH 11/19] Improve errors when trying to find a writable property --- .../PropertyAccess/PropertyAccessor.php | 117 +++++++++++++----- .../TestAdderRemoverInvalidArgumentLength.php | 27 ++++ .../TestAdderRemoverInvalidMethods.php | 23 ++++ .../Tests/Fixtures/TestClassSetValue.php | 4 + .../Tests/PropertyAccessorCollectionTest.php | 2 +- .../Tests/PropertyAccessorTest.php | 52 ++++++++ 6 files changed, 194 insertions(+), 31 deletions(-) create mode 100644 src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestAdderRemoverInvalidArgumentLength.php create mode 100644 src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestAdderRemoverInvalidMethods.php diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php index 5c8c2736a3..3c59f74b84 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php +++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php @@ -636,14 +636,24 @@ class PropertyAccessor implements PropertyAccessorInterface $access[self::ACCESS_HAS_PROPERTY] = $reflClass->hasProperty($property); $camelized = $this->camelize($property); $singulars = (array) Inflector::singularize($camelized); + $errors = []; if ($useAdderAndRemover) { - $methods = $this->findAdderAndRemover($reflClass, $singulars); + foreach ($this->findAdderAndRemover($reflClass, $singulars) as $methods) { + if (3 === \count($methods)) { + $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_ADDER_AND_REMOVER; + $access[self::ACCESS_ADDER] = $methods[self::ACCESS_ADDER]; + $access[self::ACCESS_REMOVER] = $methods[self::ACCESS_REMOVER]; + break; + } - if (null !== $methods) { - $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_ADDER_AND_REMOVER; - $access[self::ACCESS_ADDER] = $methods[0]; - $access[self::ACCESS_REMOVER] = $methods[1]; + if (isset($methods[self::ACCESS_ADDER])) { + $errors[] = sprintf('The add method "%s" in class "%s" was found, but the corresponding remove method "%s" was not found', $methods['methods'][self::ACCESS_ADDER], $reflClass->name, $methods['methods'][self::ACCESS_REMOVER]); + } + + if (isset($methods[self::ACCESS_REMOVER])) { + $errors[] = sprintf('The remove method "%s" in class "%s" was found, but the corresponding add method "%s" was not found', $methods['methods'][self::ACCESS_REMOVER], $reflClass->name, $methods['methods'][self::ACCESS_ADDER]); + } } } @@ -667,30 +677,69 @@ class PropertyAccessor implements PropertyAccessorInterface // we call the getter and hope the __call do the job $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_MAGIC; $access[self::ACCESS_NAME] = $setter; - } elseif (null !== $methods = $this->findAdderAndRemover($reflClass, $singulars)) { - $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_NOT_FOUND; - $access[self::ACCESS_NAME] = sprintf( - 'The property "%s" in class "%s" can be defined with the methods "%s()" but '. - 'the new value must be an array or an instance of \Traversable, '. - '"%s" given.', - $property, - $reflClass->name, - implode('()", "', $methods), - \is_object($value) ? \get_class($value) : \gettype($value) - ); } else { - $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_NOT_FOUND; - $access[self::ACCESS_NAME] = sprintf( - 'Neither the property "%s" nor one of the methods %s"%s()", "%s()", '. - '"__set()" or "__call()" exist and have public access in class "%s".', - $property, - implode('', array_map(function ($singular) { - return '"add'.$singular.'()"/"remove'.$singular.'()", '; - }, $singulars)), - $setter, - $getsetter, - $reflClass->name - ); + foreach ($this->findAdderAndRemover($reflClass, $singulars) as $methods) { + if (3 === \count($methods)) { + $errors[] = sprintf( + 'The property "%s" in class "%s" can be defined with the methods "%s()" but '. + 'the new value must be an array or an instance of \Traversable, '. + '"%s" given.', + $property, + $reflClass->name, + implode('()", "', [$methods[self::ACCESS_ADDER], $methods[self::ACCESS_REMOVER]]), + \is_object($value) ? \get_class($value) : \gettype($value) + ); + } + } + + if (!isset($access[self::ACCESS_NAME])) { + $access[self::ACCESS_TYPE] = self::ACCESS_TYPE_NOT_FOUND; + + $triedMethods = [ + $setter => 1, + $getsetter => 1, + '__set' => 2, + '__call' => 2, + ]; + + foreach ($singulars as $singular) { + $triedMethods['add'.$singular] = 1; + $triedMethods['remove'.$singular] = 1; + } + + foreach ($triedMethods as $methodName => $parameters) { + if (!$reflClass->hasMethod($methodName)) { + continue; + } + + $method = $reflClass->getMethod($methodName); + + if (!$method->isPublic()) { + $errors[] = sprintf('The method "%s" in class "%s" was found but does not have public access', $methodName, $reflClass->name); + continue; + } + + if ($method->getNumberOfRequiredParameters() > $parameters || $method->getNumberOfParameters() < $parameters) { + $errors[] = sprintf('The method "%s" in class "%s" requires %d arguments, but should accept only %d', $methodName, $reflClass->name, $method->getNumberOfRequiredParameters(), $parameters); + } + } + + if (\count($errors)) { + $access[self::ACCESS_NAME] = implode('. ', $errors).'.'; + } else { + $access[self::ACCESS_NAME] = sprintf( + 'Neither the property "%s" nor one of the methods %s"%s()", "%s()", '. + '"__set()" or "__call()" exist and have public access in class "%s".', + $property, + implode('', array_map(function ($singular) { + return '"add'.$singular.'()"/"remove'.$singular.'()", '; + }, $singulars)), + $setter, + $getsetter, + $reflClass->name + ); + } + } } } @@ -754,13 +803,21 @@ class PropertyAccessor implements PropertyAccessorInterface foreach ($singulars as $singular) { $addMethod = 'add'.$singular; $removeMethod = 'remove'.$singular; + $result = ['methods' => [self::ACCESS_ADDER => $addMethod, self::ACCESS_REMOVER => $removeMethod]]; $addMethodFound = $this->isMethodAccessible($reflClass, $addMethod, 1); + + if ($addMethodFound) { + $result[self::ACCESS_ADDER] = $addMethod; + } + $removeMethodFound = $this->isMethodAccessible($reflClass, $removeMethod, 1); - if ($addMethodFound && $removeMethodFound) { - return [$addMethod, $removeMethod]; + if ($removeMethodFound) { + $result[self::ACCESS_REMOVER] = $removeMethod; } + + yield $result; } } diff --git a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestAdderRemoverInvalidArgumentLength.php b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestAdderRemoverInvalidArgumentLength.php new file mode 100644 index 0000000000..4676bbcafe --- /dev/null +++ b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestAdderRemoverInvalidArgumentLength.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess\Tests\Fixtures; + +class TestAdderRemoverInvalidArgumentLength +{ + public function addFoo() + { + } + + public function removeFoo($var1, $var2) + { + } + + public function setBar($var1, $var2) + { + } +} diff --git a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestAdderRemoverInvalidMethods.php b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestAdderRemoverInvalidMethods.php new file mode 100644 index 0000000000..5c23f8b188 --- /dev/null +++ b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestAdderRemoverInvalidMethods.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess\Tests\Fixtures; + +class TestAdderRemoverInvalidMethods +{ + public function addFoo($foo) + { + } + + public function removeBar($foo) + { + } +} diff --git a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestClassSetValue.php b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestClassSetValue.php index f0a7f1f47c..9161f120ff 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestClassSetValue.php +++ b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TestClassSetValue.php @@ -29,4 +29,8 @@ class TestClassSetValue { $this->value = $value; } + + private function setFoo() + { + } } diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php index b91d1e62eb..2593f61118 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php +++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorCollectionTest.php @@ -189,7 +189,7 @@ abstract class PropertyAccessorCollectionTest extends PropertyAccessorArrayAcces /** * @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException - * expectedExceptionMessageRegExp /The property "axes" in class "Mock_PropertyAccessorCollectionTest_Car[^"]*" can be defined with the methods "addAxis()", "removeAxis()" but the new value must be an array or an instance of \Traversable, "string" given./ + * @expectedExceptionMessageRegExp /Could not determine access type for property "axes" in class "Mock_PropertyAccessorCollectionTest_Car[^"]*": The property "axes" in class "Mock_PropertyAccessorCollectionTest_Car[^"]*" can be defined with the methods "addAxis\(\)", "removeAxis\(\)" but the new value must be an array or an instance of \\Traversable, "string" given./ */ public function testSetValueFailsIfAdderAndRemoverExistButValueIsNotTraversable() { diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php index d0cbccf1ec..dfd16d9f51 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php +++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php @@ -17,6 +17,8 @@ use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccessor; use Symfony\Component\PropertyAccess\Tests\Fixtures\ReturnTyped; +use Symfony\Component\PropertyAccess\Tests\Fixtures\TestAdderRemoverInvalidArgumentLength; +use Symfony\Component\PropertyAccess\Tests\Fixtures\TestAdderRemoverInvalidMethods; use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClass; use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassIsWritable; use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassMagicCall; @@ -762,4 +764,54 @@ class PropertyAccessorTest extends TestCase $this->assertEquals(['aeroplane'], $object->getAircraft()); } + + /** + * @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException + * @expectedExceptionMessageRegExp /.*The add method "addFoo" in class "Symfony\\Component\\PropertyAccess\\Tests\\Fixtures\\TestAdderRemoverInvalidMethods" was found, but the corresponding remove method "removeFoo" was not found\./ + */ + public function testAdderWithoutRemover() + { + $object = new TestAdderRemoverInvalidMethods(); + $this->propertyAccessor->setValue($object, 'foos', [1, 2]); + } + + /** + * @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException + * @expectedExceptionMessageRegExp /.*The remove method "removeBar" in class "Symfony\\Component\\PropertyAccess\\Tests\\Fixtures\\TestAdderRemoverInvalidMethods" was found, but the corresponding add method "addBar" was not found\./ + */ + public function testRemoverWithoutAdder() + { + $object = new TestAdderRemoverInvalidMethods(); + $this->propertyAccessor->setValue($object, 'bars', [1, 2]); + } + + /** + * @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException + * @expectedExceptionMessageRegExp /.*The method "addFoo" in class "Symfony\\Component\\PropertyAccess\\Tests\\Fixtures\\TestAdderRemoverInvalidArgumentLength" requires 0 arguments, but should accept only 1\. The method "removeFoo" in class "Symfony\\Component\\PropertyAccess\\Tests\\Fixtures\\TestAdderRemoverInvalidArgumentLength" requires 2 arguments, but should accept only 1\./ + */ + public function testAdderAndRemoveNeedsTheExactParametersDefined() + { + $object = new TestAdderRemoverInvalidArgumentLength(); + $this->propertyAccessor->setValue($object, 'foo', [1, 2]); + } + + /** + * @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException + * @expectedExceptionMessageRegExp /.*The method "setBar" in class "Symfony\\Component\\PropertyAccess\\Tests\\Fixtures\\TestAdderRemoverInvalidArgumentLength" requires 2 arguments, but should accept only 1\./ + */ + public function testSetterNeedsTheExactParametersDefined() + { + $object = new TestAdderRemoverInvalidArgumentLength(); + $this->propertyAccessor->setValue($object, 'bar', [1, 2]); + } + + /** + * @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException + * @expectedExceptionMessageRegExp /.*The method "setFoo" in class "Symfony\\Component\\PropertyAccess\\Tests\\Fixtures\\TestClassSetValue" was found but does not have public access./ + */ + public function testSetterNeedsPublicAccess() + { + $object = new TestClassSetValue(0); + $this->propertyAccessor->setValue($object, 'foo', 1); + } } From 613dbb267dfa0e6a2735743e00f9d55480ae14d2 Mon Sep 17 00:00:00 2001 From: Maxime Steinhausser Date: Tue, 9 Jul 2019 17:43:55 +0200 Subject: [PATCH 12/19] [VarDumper] Allow to configure VarDumperTestTrait casters & flags --- .../VarDumper/Test/VarDumperTestTrait.php | 32 +++++++++++++++++-- .../Tests/Test/VarDumperTestTraitTest.php | 32 +++++++++++++++++++ 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/VarDumper/Test/VarDumperTestTrait.php b/src/Symfony/Component/VarDumper/Test/VarDumperTestTrait.php index 11c9b92659..4eb7a43a84 100644 --- a/src/Symfony/Component/VarDumper/Test/VarDumperTestTrait.php +++ b/src/Symfony/Component/VarDumper/Test/VarDumperTestTrait.php @@ -19,6 +19,29 @@ use Symfony\Component\VarDumper\Dumper\CliDumper; */ trait VarDumperTestTrait { + /** + * @internal + */ + private $varDumperConfig = [ + 'casters' => [], + 'flags' => null, + ]; + + protected function setUpVarDumper(array $casters, int $flags = null): void + { + $this->varDumperConfig['casters'] = $casters; + $this->varDumperConfig['flags'] = $flags; + } + + /** + * @after + */ + protected function tearDownVarDumper(): void + { + $this->varDumperConfig['casters'] = []; + $this->varDumperConfig['flags'] = null; + } + public function assertDumpEquals($expected, $data, $filter = 0, $message = '') { $this->assertSame($this->prepareExpectation($expected, $filter), $this->getDump($data, null, $filter), $message); @@ -31,11 +54,14 @@ trait VarDumperTestTrait protected function getDump($data, $key = null, $filter = 0) { - $flags = getenv('DUMP_LIGHT_ARRAY') ? CliDumper::DUMP_LIGHT_ARRAY : 0; - $flags |= getenv('DUMP_STRING_LENGTH') ? CliDumper::DUMP_STRING_LENGTH : 0; - $flags |= getenv('DUMP_COMMA_SEPARATOR') ? CliDumper::DUMP_COMMA_SEPARATOR : 0; + if (null === $flags = $this->varDumperConfig['flags']) { + $flags = getenv('DUMP_LIGHT_ARRAY') ? CliDumper::DUMP_LIGHT_ARRAY : 0; + $flags |= getenv('DUMP_STRING_LENGTH') ? CliDumper::DUMP_STRING_LENGTH : 0; + $flags |= getenv('DUMP_COMMA_SEPARATOR') ? CliDumper::DUMP_COMMA_SEPARATOR : 0; + } $cloner = new VarCloner(); + $cloner->addCasters($this->varDumperConfig['casters']); $cloner->setMaxItems(-1); $dumper = new CliDumper(null, null, $flags); $dumper->setColors(false); diff --git a/src/Symfony/Component/VarDumper/Tests/Test/VarDumperTestTraitTest.php b/src/Symfony/Component/VarDumper/Tests/Test/VarDumperTestTraitTest.php index a4d489cf34..d055c75090 100644 --- a/src/Symfony/Component/VarDumper/Tests/Test/VarDumperTestTraitTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Test/VarDumperTestTraitTest.php @@ -12,6 +12,8 @@ namespace Symfony\Component\VarDumper\Tests\Test; use PHPUnit\Framework\TestCase; +use Symfony\Component\VarDumper\Cloner\Stub; +use Symfony\Component\VarDumper\Dumper\CliDumper; use Symfony\Component\VarDumper\Test\VarDumperTestTrait; class VarDumperTestTraitTest extends TestCase @@ -43,4 +45,34 @@ EODUMP; { $this->assertDumpEquals(new \ArrayObject(['bim' => 'bam']), new \ArrayObject(['bim' => 'bam'])); } + + public function testItCanBeConfigured() + { + $this->setUpVarDumper($casters = [ + \DateTimeInterface::class => static function (\DateTimeInterface $date, array $a, Stub $stub): array { + $stub->class = 'DateTime'; + + return ['date' => $date->format('d/m/Y')]; + }, + ], CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR); + + $this->assertSame(CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR, $this->varDumperConfig['flags']); + $this->assertSame($casters, $this->varDumperConfig['casters']); + + $this->assertDumpEquals(<<tearDownVarDumper(); + + $this->assertNull($this->varDumperConfig['flags']); + $this->assertSame([], $this->varDumperConfig['casters']); + } } From 2f3b47a9e4513e301bdac2d1f793e2adad001d0f Mon Sep 17 00:00:00 2001 From: Konstantin Myakshin Date: Mon, 15 Jul 2019 22:23:03 +0300 Subject: [PATCH 13/19] [Mailer] Allow register mailer configuration in xml format --- .../Resources/config/schema/symfony-1.0.xsd | 5 +++++ .../DependencyInjection/Fixtures/php/mailer.php | 7 +++++++ .../DependencyInjection/Fixtures/xml/mailer.xml | 12 ++++++++++++ .../DependencyInjection/Fixtures/yml/mailer.yml | 3 +++ .../DependencyInjection/FrameworkExtensionTest.php | 9 ++++++++- 5 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer.xml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer.yml diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index 04157511dc..69deb492a2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -33,6 +33,7 @@ + @@ -543,4 +544,8 @@ + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer.php new file mode 100644 index 0000000000..74f6856c29 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer.php @@ -0,0 +1,7 @@ +loadFromExtension('framework', [ + 'mailer' => [ + 'dsn' => 'smtp://example.com', + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer.xml new file mode 100644 index 0000000000..5faa09d36d --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer.yml new file mode 100644 index 0000000000..0fca3e83e0 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer.yml @@ -0,0 +1,3 @@ +framework: + mailer: + dsn: 'smtp://example.com' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 877e79c889..76e4a086f4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -55,7 +55,6 @@ use Symfony\Component\Translation\TranslatorInterface; use Symfony\Component\Validator\DependencyInjection\AddConstraintValidatorsPass; use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader; use Symfony\Component\Validator\Util\LegacyTranslatorProxy; -use Symfony\Component\Validator\Validation; use Symfony\Component\Workflow; abstract class FrameworkExtensionTest extends TestCase @@ -1552,6 +1551,14 @@ abstract class FrameworkExtensionTest extends TestCase ], $defaultOptions['peer_fingerprint']); } + public function testMailer(): void + { + $container = $this->createContainerFromFile('mailer'); + + $this->assertTrue($container->hasAlias('mailer')); + $this->assertTrue($container->hasDefinition('mailer.default_transport')); + } + protected function createContainer(array $data = []) { return new ContainerBuilder(new ParameterBag(array_merge([ From a0d2c429b1711c1dc9fce965266a967af449ae93 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 16 Jul 2019 08:12:19 +0200 Subject: [PATCH 14/19] added missing test --- .../Tests/DependencyInjection/FrameworkExtensionTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 76e4a086f4..b850d7c8c3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -1557,6 +1557,7 @@ abstract class FrameworkExtensionTest extends TestCase $this->assertTrue($container->hasAlias('mailer')); $this->assertTrue($container->hasDefinition('mailer.default_transport')); + $this->assertSame('smtp://example.com', $container->getDefinition('mailer.default_transport')->getArgument(0)); } protected function createContainer(array $data = []) From 2e03f9dfa5a08b8997f2e060409738ae91934e36 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 16 Jul 2019 08:28:50 +0200 Subject: [PATCH 15/19] [Mailer] added XML configuration for the mailer envelope --- .../Resources/config/schema/symfony-1.0.xsd | 10 ++++++++++ .../Tests/DependencyInjection/Fixtures/php/mailer.php | 4 ++++ .../Tests/DependencyInjection/Fixtures/xml/mailer.xml | 8 +++++++- .../Tests/DependencyInjection/Fixtures/yml/mailer.yml | 5 +++++ .../DependencyInjection/FrameworkExtensionTest.php | 4 ++++ 5 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index 2df4c53cb7..532b9339e1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -555,6 +555,16 @@ + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer.php index 74f6856c29..ef8cdd385c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer.php @@ -3,5 +3,9 @@ $container->loadFromExtension('framework', [ 'mailer' => [ 'dsn' => 'smtp://example.com', + 'envelope' => [ + 'sender' => 'sender@example.org', + 'recipients' => ['redirected@example.org', 'redirected1@example.org'], + ], ], ]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer.xml index 5faa09d36d..ff4d75c825 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer.xml @@ -7,6 +7,12 @@ http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> - + + + sender@example.org + redirected@example.org + redirected1@example.org + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer.yml index 0fca3e83e0..07d435d9df 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer.yml @@ -1,3 +1,8 @@ framework: mailer: dsn: 'smtp://example.com' + envelope: + sender: sender@example.org + recipients: + - redirected@example.org + - redirected1@example.org diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 32cea3ba67..1a5e3da187 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -1588,6 +1588,10 @@ abstract class FrameworkExtensionTest extends TestCase $this->assertTrue($container->hasAlias('mailer')); $this->assertTrue($container->hasDefinition('mailer.default_transport')); $this->assertSame('smtp://example.com', $container->getDefinition('mailer.default_transport')->getArgument(0)); + $this->assertTrue($container->hasDefinition('mailer.envelope_listener')); + $l = $container->getDefinition('mailer.envelope_listener'); + $this->assertSame('sender@example.org', $l->getArgument(0)); + $this->assertSame(['redirected@example.org', 'redirected1@example.org'], $l->getArgument(1)); } protected function createContainer(array $data = []) From 90e46ab13b77e535bbea1b6ec48ee32566bf91cf Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 16 Jul 2019 08:40:46 +0200 Subject: [PATCH 16/19] [HttpClient] make toStream() throw by default --- src/Symfony/Component/HttpClient/Psr18Client.php | 2 +- .../Component/HttpClient/Response/ResponseTrait.php | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/HttpClient/Psr18Client.php b/src/Symfony/Component/HttpClient/Psr18Client.php index 9d3628afb0..ee8c813b46 100644 --- a/src/Symfony/Component/HttpClient/Psr18Client.php +++ b/src/Symfony/Component/HttpClient/Psr18Client.php @@ -92,7 +92,7 @@ final class Psr18Client implements ClientInterface, RequestFactoryInterface, Str } } - $body = isset(class_uses($response)[ResponseTrait::class]) ? $response->toStream() : StreamWrapper::createResource($response, $this->client); + $body = isset(class_uses($response)[ResponseTrait::class]) ? $response->toStream(false) : StreamWrapper::createResource($response, $this->client); return $psrResponse->withBody($this->streamFactory->createStreamFromResource($body)); } catch (TransportExceptionInterface $e) { diff --git a/src/Symfony/Component/HttpClient/Response/ResponseTrait.php b/src/Symfony/Component/HttpClient/Response/ResponseTrait.php index 10c2d505c3..ed7281e1be 100644 --- a/src/Symfony/Component/HttpClient/Response/ResponseTrait.php +++ b/src/Symfony/Component/HttpClient/Response/ResponseTrait.php @@ -21,6 +21,10 @@ use Symfony\Component\HttpClient\Exception\RedirectionException; use Symfony\Component\HttpClient\Exception\ServerException; use Symfony\Component\HttpClient\Exception\TransportException; use Symfony\Component\HttpClient\Internal\ClientState; +use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; /** * Implements the common logic for response classes. @@ -182,11 +186,16 @@ trait ResponseTrait * Casts the response to a PHP stream resource. * * @return resource|null + * + * @throws TransportExceptionInterface When a network error occurs + * @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached + * @throws ClientExceptionInterface On a 4xx when $throw is true + * @throws ServerExceptionInterface On a 5xx when $throw is true */ - public function toStream() + public function toStream(bool $throw = true) { // Ensure headers arrived - $this->getStatusCode(); + $this->getHeaders($throw); return StreamWrapper::createResource($this, null, $this->content, $this->handle && 'stream' === get_resource_type($this->handle) ? $this->handle : null); } From 9988844eb45d7fb414304308c3408ed8e43fb698 Mon Sep 17 00:00:00 2001 From: Amrouche Hamza Date: Tue, 16 Jul 2019 08:55:46 +0200 Subject: [PATCH 17/19] [Lock] remove uusage of the StoreInterface --- .../DependencyInjection/FrameworkExtension.php | 2 +- src/Symfony/Component/Lock/Tests/LockTest.php | 17 ++++++++--------- .../Lock/Tests/Store/AbstractStoreTest.php | 4 ++-- .../Lock/Tests/Store/BlockingStoreTestTrait.php | 4 ++-- .../Lock/Tests/Store/CombinedStoreTest.php | 5 ++--- 5 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 2c7f8732f5..d933b60cb1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1600,7 +1600,7 @@ class FrameworkExtension extends Extension $container->setDefinition($connectionDefinitionId, $connectionDefinition); } - $storeDefinition = new Definition(StoreInterface::class); + $storeDefinition = new Definition(PersistStoreInterface::class); $storeDefinition->setPublic(false); $storeDefinition->setFactory([StoreFactory::class, 'createStore']); $storeDefinition->setArguments([new Reference($connectionDefinitionId)]); diff --git a/src/Symfony/Component/Lock/Tests/LockTest.php b/src/Symfony/Component/Lock/Tests/LockTest.php index 3fddbc3eca..9faf05d12c 100644 --- a/src/Symfony/Component/Lock/Tests/LockTest.php +++ b/src/Symfony/Component/Lock/Tests/LockTest.php @@ -18,7 +18,6 @@ use Symfony\Component\Lock\Exception\LockConflictedException; use Symfony\Component\Lock\Key; use Symfony\Component\Lock\Lock; use Symfony\Component\Lock\PersistStoreInterface; -use Symfony\Component\Lock\StoreInterface; /** * @author Jérémy Derussé @@ -41,7 +40,7 @@ class LockTest extends TestCase public function testAcquireNoBlockingStoreInterface() { $key = new Key(uniqid(__METHOD__, true)); - $store = $this->getMockBuilder(StoreInterface::class)->getMock(); + $store = $this->getMockBuilder(PersistStoreInterface::class)->getMock(); $lock = new Lock($key, $store); $store @@ -59,7 +58,7 @@ class LockTest extends TestCase public function testPassingOldStoreInterface() { $key = new Key(uniqid(__METHOD__, true)); - $store = $this->getMockBuilder(StoreInterface::class)->getMock(); + $store = $this->getMockBuilder(PersistStoreInterface::class)->getMock(); $lock = new Lock($key, $store); $store @@ -86,7 +85,7 @@ class LockTest extends TestCase public function testAcquireReturnsFalseStoreInterface() { $key = new Key(uniqid(__METHOD__, true)); - $store = $this->getMockBuilder(StoreInterface::class)->getMock(); + $store = $this->getMockBuilder(PersistStoreInterface::class)->getMock(); $lock = new Lock($key, $store); $store @@ -121,7 +120,7 @@ class LockTest extends TestCase public function testAcquireSetsTtl() { $key = new Key(uniqid(__METHOD__, true)); - $store = $this->getMockBuilder(StoreInterface::class)->getMock(); + $store = $this->getMockBuilder(PersistStoreInterface::class)->getMock(); $lock = new Lock($key, $store, 10); $store @@ -138,7 +137,7 @@ class LockTest extends TestCase public function testRefresh() { $key = new Key(uniqid(__METHOD__, true)); - $store = $this->getMockBuilder(StoreInterface::class)->getMock(); + $store = $this->getMockBuilder(PersistStoreInterface::class)->getMock(); $lock = new Lock($key, $store, 10); $store @@ -152,7 +151,7 @@ class LockTest extends TestCase public function testRefreshCustom() { $key = new Key(uniqid(__METHOD__, true)); - $store = $this->getMockBuilder(StoreInterface::class)->getMock(); + $store = $this->getMockBuilder(PersistStoreInterface::class)->getMock(); $lock = new Lock($key, $store, 10); $store @@ -201,7 +200,7 @@ class LockTest extends TestCase public function testReleaseStoreInterface() { $key = new Key(uniqid(__METHOD__, true)); - $store = $this->getMockBuilder(StoreInterface::class)->getMock(); + $store = $this->getMockBuilder(PersistStoreInterface::class)->getMock(); $lock = new Lock($key, $store, 10); $store @@ -356,7 +355,7 @@ class LockTest extends TestCase public function testExpirationStoreInterface($ttls, $expected) { $key = new Key(uniqid(__METHOD__, true)); - $store = $this->getMockBuilder(StoreInterface::class)->getMock(); + $store = $this->getMockBuilder(PersistStoreInterface::class)->getMock(); $lock = new Lock($key, $store, 10); foreach ($ttls as $ttl) { diff --git a/src/Symfony/Component/Lock/Tests/Store/AbstractStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/AbstractStoreTest.php index 2ab030b200..4039d8fd95 100644 --- a/src/Symfony/Component/Lock/Tests/Store/AbstractStoreTest.php +++ b/src/Symfony/Component/Lock/Tests/Store/AbstractStoreTest.php @@ -14,7 +14,7 @@ namespace Symfony\Component\Lock\Tests\Store; use PHPUnit\Framework\TestCase; use Symfony\Component\Lock\Exception\LockConflictedException; use Symfony\Component\Lock\Key; -use Symfony\Component\Lock\StoreInterface; +use Symfony\Component\Lock\PersistStoreInterface; /** * @author Jérémy Derussé @@ -22,7 +22,7 @@ use Symfony\Component\Lock\StoreInterface; abstract class AbstractStoreTest extends TestCase { /** - * @return StoreInterface + * @return PersistStoreInterface */ abstract protected function getStore(); diff --git a/src/Symfony/Component/Lock/Tests/Store/BlockingStoreTestTrait.php b/src/Symfony/Component/Lock/Tests/Store/BlockingStoreTestTrait.php index a22224215c..1ac99980b5 100644 --- a/src/Symfony/Component/Lock/Tests/Store/BlockingStoreTestTrait.php +++ b/src/Symfony/Component/Lock/Tests/Store/BlockingStoreTestTrait.php @@ -14,7 +14,7 @@ namespace Symfony\Component\Lock\Tests\Store; use Symfony\Component\Lock\Exception\LockConflictedException; use Symfony\Component\Lock\Exception\NotSupportedException; use Symfony\Component\Lock\Key; -use Symfony\Component\Lock\StoreInterface; +use Symfony\Component\Lock\PersistStoreInterface; /** * @author Jérémy Derussé @@ -24,7 +24,7 @@ trait BlockingStoreTestTrait /** * @see AbstractStoreTest::getStore() * - * @return StoreInterface + * @return PersistStoreInterface */ abstract protected function getStore(); diff --git a/src/Symfony/Component/Lock/Tests/Store/CombinedStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/CombinedStoreTest.php index 9c38f97ece..3292247fde 100644 --- a/src/Symfony/Component/Lock/Tests/Store/CombinedStoreTest.php +++ b/src/Symfony/Component/Lock/Tests/Store/CombinedStoreTest.php @@ -17,7 +17,6 @@ use Symfony\Component\Lock\Key; use Symfony\Component\Lock\PersistStoreInterface; use Symfony\Component\Lock\Store\CombinedStore; use Symfony\Component\Lock\Store\RedisStore; -use Symfony\Component\Lock\StoreInterface; use Symfony\Component\Lock\Strategy\StrategyInterface; use Symfony\Component\Lock\Strategy\UnanimousStrategy; @@ -268,8 +267,8 @@ class CombinedStoreTest extends AbstractStoreTest public function testPutOffExpirationIgnoreNonExpiringStorage() { - $store1 = $this->getMockBuilder(StoreInterface::class)->getMock(); - $store2 = $this->getMockBuilder(StoreInterface::class)->getMock(); + $store1 = $this->getMockBuilder(PersistStoreInterface::class)->getMock(); + $store2 = $this->getMockBuilder(PersistStoreInterface::class)->getMock(); $store = new CombinedStore([$store1, $store2], $this->strategy); From 5b9cded2760343dfe0687e1ce916c5a08d604f63 Mon Sep 17 00:00:00 2001 From: Konstantin Myakshin Date: Thu, 6 Jun 2019 02:11:36 +0300 Subject: [PATCH 18/19] Add transport factories (closes #31385, closes #32523) --- .../FrameworkExtension.php | 22 + .../Resources/config/mailer.xml | 9 +- .../Resources/config/mailer_transports.xml | 50 +++ .../Amazon/Factory/SesTransportFactory.php | 51 +++ .../Tests/Factory/SesTransportFactoryTest.php | 98 +++++ .../Google/Factory/GmailTransportFactory.php | 38 ++ .../Factory/GmailTransportFactoryTest.php | 50 +++ .../Factory/MandrillTransportFactory.php | 51 +++ .../Factory/MandrillTransportFactoryTest.php | 83 ++++ .../Factory/MailgunTransportFactory.php | 51 +++ .../Factory/MailgunTransportFactoryTest.php | 79 ++++ .../Factory/PostmarkTransportFactory.php | 45 ++ .../Factory/PostmarkTransportFactoryTest.php | 70 ++++ .../Factory/SendgridTransportFactory.php | 44 ++ .../Factory/SendgridTransportFactoryTest.php | 65 +++ src/Symfony/Component/Mailer/CHANGELOG.md | 2 + .../Exception/IncompleteDsnException.php | 19 + .../Exception/UnsupportedHostException.php | 61 +++ .../Exception/UnsupportedSchemeException.php | 25 ++ .../Mailer/Tests/Transport/DsnTest.php | 88 ++++ .../Transport/NullTransportFactoryTest.php | 52 +++ .../SendmailTransportFactoryTest.php | 52 +++ .../Smtp/EsmtpTransportFactoryTest.php | 52 +++ .../Mailer/Tests/TransportFactoryTestCase.php | 105 +++++ .../Component/Mailer/Tests/TransportTest.php | 384 +++--------------- src/Symfony/Component/Mailer/Transport.php | 218 ++++------ .../Transport/AbstractTransportFactory.php | 54 +++ .../Component/Mailer/Transport/Dsn.php | 89 ++++ .../Mailer/Transport/NullTransportFactory.php | 34 ++ .../Transport/SendmailTransportFactory.php | 34 ++ .../Transport/Smtp/EsmtpTransportFactory.php | 47 +++ .../Transport/TransportFactoryInterface.php | 29 ++ 32 files changed, 1672 insertions(+), 479 deletions(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.xml create mode 100644 src/Symfony/Component/Mailer/Bridge/Amazon/Factory/SesTransportFactory.php create mode 100644 src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Factory/SesTransportFactoryTest.php create mode 100644 src/Symfony/Component/Mailer/Bridge/Google/Factory/GmailTransportFactory.php create mode 100644 src/Symfony/Component/Mailer/Bridge/Google/Tests/Factory/GmailTransportFactoryTest.php create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailchimp/Factory/MandrillTransportFactory.php create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailchimp/Tests/Factory/MandrillTransportFactoryTest.php create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailgun/Factory/MailgunTransportFactory.php create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Factory/MailgunTransportFactoryTest.php create mode 100644 src/Symfony/Component/Mailer/Bridge/Postmark/Factory/PostmarkTransportFactory.php create mode 100644 src/Symfony/Component/Mailer/Bridge/Postmark/Tests/Factory/PostmarkTransportFactoryTest.php create mode 100644 src/Symfony/Component/Mailer/Bridge/Sendgrid/Factory/SendgridTransportFactory.php create mode 100644 src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Factory/SendgridTransportFactoryTest.php create mode 100644 src/Symfony/Component/Mailer/Exception/IncompleteDsnException.php create mode 100644 src/Symfony/Component/Mailer/Exception/UnsupportedHostException.php create mode 100644 src/Symfony/Component/Mailer/Exception/UnsupportedSchemeException.php create mode 100644 src/Symfony/Component/Mailer/Tests/Transport/DsnTest.php create mode 100644 src/Symfony/Component/Mailer/Tests/Transport/NullTransportFactoryTest.php create mode 100644 src/Symfony/Component/Mailer/Tests/Transport/SendmailTransportFactoryTest.php create mode 100644 src/Symfony/Component/Mailer/Tests/Transport/Smtp/EsmtpTransportFactoryTest.php create mode 100644 src/Symfony/Component/Mailer/Tests/TransportFactoryTestCase.php create mode 100644 src/Symfony/Component/Mailer/Transport/AbstractTransportFactory.php create mode 100644 src/Symfony/Component/Mailer/Transport/Dsn.php create mode 100644 src/Symfony/Component/Mailer/Transport/NullTransportFactory.php create mode 100644 src/Symfony/Component/Mailer/Transport/SendmailTransportFactory.php create mode 100644 src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransportFactory.php create mode 100644 src/Symfony/Component/Mailer/Transport/TransportFactoryInterface.php diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 2c7f8732f5..b79bc753f3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -77,6 +77,12 @@ use Symfony\Component\Lock\PersistStoreInterface; use Symfony\Component\Lock\Store\FlockStore; use Symfony\Component\Lock\Store\StoreFactory; use Symfony\Component\Lock\StoreInterface; +use Symfony\Component\Mailer\Bridge\Amazon\Factory\SesTransportFactory; +use Symfony\Component\Mailer\Bridge\Google\Factory\GmailTransportFactory; +use Symfony\Component\Mailer\Bridge\Mailchimp\Factory\MandrillTransportFactory; +use Symfony\Component\Mailer\Bridge\Mailgun\Factory\MailgunTransportFactory; +use Symfony\Component\Mailer\Bridge\Postmark\Factory\PostmarkTransportFactory; +use Symfony\Component\Mailer\Bridge\Sendgrid\Factory\SendgridTransportFactory; use Symfony\Component\Mailer\Mailer; use Symfony\Component\Messenger\Handler\MessageHandlerInterface; use Symfony\Component\Messenger\MessageBus; @@ -1955,8 +1961,24 @@ class FrameworkExtension extends Extension } $loader->load('mailer.xml'); + $loader->load('mailer_transports.xml'); $container->getDefinition('mailer.default_transport')->setArgument(0, $config['dsn']); + $classToServices = [ + SesTransportFactory::class => 'mailer.transport_factory.amazon', + GmailTransportFactory::class => 'mailer.transport_factory.gmail', + MandrillTransportFactory::class => 'mailer.transport_factory.mailchimp', + MailgunTransportFactory::class => 'mailer.transport_factory.mailgun', + PostmarkTransportFactory::class => 'mailer.transport_factory.postmark', + SendgridTransportFactory::class => 'mailer.transport_factory.sendgrid', + ]; + + foreach ($classToServices as $class => $service) { + if (!class_exists($class)) { + $container->removeDefinition($service); + } + } + $recipients = $config['envelope']['recipients'] ?? null; $sender = $config['envelope']['sender'] ?? null; diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer.xml index cfe98f21dc..becf0d1b71 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer.xml @@ -12,12 +12,13 @@ + + + + - + - - - diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.xml new file mode 100644 index 0000000000..bddcc67f01 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Factory/SesTransportFactory.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Factory/SesTransportFactory.php new file mode 100644 index 0000000000..ca6fd49829 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Factory/SesTransportFactory.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Amazon\Factory; + +use Symfony\Component\Mailer\Bridge\Amazon; +use Symfony\Component\Mailer\Exception\UnsupportedSchemeException; +use Symfony\Component\Mailer\Transport\AbstractTransportFactory; +use Symfony\Component\Mailer\Transport\Dsn; +use Symfony\Component\Mailer\Transport\TransportInterface; + +/** + * @author Konstantin Myakshin + */ +final class SesTransportFactory extends AbstractTransportFactory +{ + public function create(Dsn $dsn): TransportInterface + { + $scheme = $dsn->getScheme(); + $user = $this->getUser($dsn); + $password = $this->getPassword($dsn); + $region = $dsn->getOption('region'); + + if ('api' === $scheme) { + return new Amazon\Http\Api\SesTransport($user, $password, $region, $this->client, $this->dispatcher, $this->logger); + } + + if ('http' === $scheme) { + return new Amazon\Http\SesTransport($user, $password, $region, $this->client, $this->dispatcher, $this->logger); + } + + if ('smtp' === $scheme) { + return new Amazon\Smtp\SesTransport($user, $password, $region, $this->dispatcher, $this->logger); + } + + throw new UnsupportedSchemeException($dsn); + } + + public function supports(Dsn $dsn): bool + { + return 'ses' === $dsn->getHost(); + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Factory/SesTransportFactoryTest.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Factory/SesTransportFactoryTest.php new file mode 100644 index 0000000000..595f725828 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Factory/SesTransportFactoryTest.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Amazon\Tests\Factory; + +use Symfony\Component\Mailer\Bridge\Amazon; +use Symfony\Component\Mailer\Bridge\Amazon\Factory\SesTransportFactory; +use Symfony\Component\Mailer\Tests\TransportFactoryTestCase; +use Symfony\Component\Mailer\Transport\Dsn; +use Symfony\Component\Mailer\Transport\TransportFactoryInterface; + +class SesTransportFactoryTest extends TransportFactoryTestCase +{ + public function getFactory(): TransportFactoryInterface + { + return new SesTransportFactory($this->getDispatcher(), $this->getClient(), $this->getLogger()); + } + + public function supportsProvider(): iterable + { + yield [ + new Dsn('api', 'ses'), + true, + ]; + + yield [ + new Dsn('http', 'ses'), + true, + ]; + + yield [ + new Dsn('smtp', 'ses'), + true, + ]; + + yield [ + new Dsn('smtp', 'example.com'), + false, + ]; + } + + public function createProvider(): iterable + { + $client = $this->getClient(); + $dispatcher = $this->getDispatcher(); + $logger = $this->getLogger(); + + yield [ + new Dsn('api', 'ses', self::USER, self::PASSWORD), + new Amazon\Http\Api\SesTransport(self::USER, self::PASSWORD, null, $client, $dispatcher, $logger), + ]; + + yield [ + new Dsn('api', 'ses', self::USER, self::PASSWORD, null, ['region' => 'eu-west-1']), + new Amazon\Http\Api\SesTransport(self::USER, self::PASSWORD, 'eu-west-1', $client, $dispatcher, $logger), + ]; + + yield [ + new Dsn('http', 'ses', self::USER, self::PASSWORD), + new Amazon\Http\SesTransport(self::USER, self::PASSWORD, null, $client, $dispatcher, $logger), + ]; + + yield [ + new Dsn('http', 'ses', self::USER, self::PASSWORD, null, ['region' => 'eu-west-1']), + new Amazon\Http\SesTransport(self::USER, self::PASSWORD, 'eu-west-1', $client, $dispatcher, $logger), + ]; + + yield [ + new Dsn('smtp', 'ses', self::USER, self::PASSWORD), + new Amazon\Smtp\SesTransport(self::USER, self::PASSWORD, null, $dispatcher, $logger), + ]; + + yield [ + new Dsn('smtp', 'ses', self::USER, self::PASSWORD, null, ['region' => 'eu-west-1']), + new Amazon\Smtp\SesTransport(self::USER, self::PASSWORD, 'eu-west-1', $dispatcher, $logger), + ]; + } + + public function unsupportedSchemeProvider(): iterable + { + yield [new Dsn('foo', 'ses', self::USER, self::PASSWORD)]; + } + + public function incompleteDsnProvider(): iterable + { + yield [new Dsn('smtp', 'ses', self::USER)]; + + yield [new Dsn('smtp', 'ses', null, self::PASSWORD)]; + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Google/Factory/GmailTransportFactory.php b/src/Symfony/Component/Mailer/Bridge/Google/Factory/GmailTransportFactory.php new file mode 100644 index 0000000000..d96a471018 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Google/Factory/GmailTransportFactory.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Google\Factory; + +use Symfony\Component\Mailer\Bridge\Google\Smtp\GmailTransport; +use Symfony\Component\Mailer\Exception\UnsupportedSchemeException; +use Symfony\Component\Mailer\Transport\AbstractTransportFactory; +use Symfony\Component\Mailer\Transport\Dsn; +use Symfony\Component\Mailer\Transport\TransportInterface; + +/** + * @author Konstantin Myakshin + */ +final class GmailTransportFactory extends AbstractTransportFactory +{ + public function create(Dsn $dsn): TransportInterface + { + if ('smtp' === $dsn->getScheme()) { + return new GmailTransport($this->getUser($dsn), $this->getPassword($dsn), $this->dispatcher, $this->logger); + } + + throw new UnsupportedSchemeException($dsn); + } + + public function supports(Dsn $dsn): bool + { + return 'gmail' === $dsn->getHost(); + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Google/Tests/Factory/GmailTransportFactoryTest.php b/src/Symfony/Component/Mailer/Bridge/Google/Tests/Factory/GmailTransportFactoryTest.php new file mode 100644 index 0000000000..a8a2f07396 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Google/Tests/Factory/GmailTransportFactoryTest.php @@ -0,0 +1,50 @@ +getDispatcher(), $this->getClient(), $this->getLogger()); + } + + public function supportsProvider(): iterable + { + yield [ + new Dsn('smtp', 'gmail'), + true, + ]; + + yield [ + new Dsn('smtp', 'example.com'), + false, + ]; + } + + public function createProvider(): iterable + { + yield [ + new Dsn('smtp', 'gmail', self::USER, self::PASSWORD), + new GmailTransport(self::USER, self::PASSWORD, $this->getDispatcher(), $this->getLogger()), + ]; + } + + public function unsupportedSchemeProvider(): iterable + { + yield [new Dsn('http', 'gmail', self::USER, self::PASSWORD)]; + } + + public function incompleteDsnProvider(): iterable + { + yield [new Dsn('smtp', 'gmail', self::USER)]; + + yield [new Dsn('smtp', 'gmail', null, self::PASSWORD)]; + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Factory/MandrillTransportFactory.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Factory/MandrillTransportFactory.php new file mode 100644 index 0000000000..265302fa8b --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Factory/MandrillTransportFactory.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Mailchimp\Factory; + +use Symfony\Component\Mailer\Bridge\Mailchimp; +use Symfony\Component\Mailer\Exception\UnsupportedSchemeException; +use Symfony\Component\Mailer\Transport\AbstractTransportFactory; +use Symfony\Component\Mailer\Transport\Dsn; +use Symfony\Component\Mailer\Transport\TransportInterface; + +/** + * @author Konstantin Myakshin + */ +final class MandrillTransportFactory extends AbstractTransportFactory +{ + public function create(Dsn $dsn): TransportInterface + { + $scheme = $dsn->getScheme(); + $user = $this->getUser($dsn); + + if ('api' === $scheme) { + return new Mailchimp\Http\Api\MandrillTransport($user, $this->client, $this->dispatcher, $this->logger); + } + + if ('http' === $scheme) { + return new Mailchimp\Http\MandrillTransport($user, $this->client, $this->dispatcher, $this->logger); + } + + if ('smtp' === $scheme) { + $password = $this->getPassword($dsn); + + return new Mailchimp\Smtp\MandrillTransport($user, $password, $this->dispatcher, $this->logger); + } + + throw new UnsupportedSchemeException($dsn); + } + + public function supports(Dsn $dsn): bool + { + return 'mandrill' === $dsn->getHost(); + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Tests/Factory/MandrillTransportFactoryTest.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Tests/Factory/MandrillTransportFactoryTest.php new file mode 100644 index 0000000000..07dbdd4937 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Tests/Factory/MandrillTransportFactoryTest.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Mailchimp\Tests\Factory; + +use Symfony\Component\Mailer\Bridge\Mailchimp; +use Symfony\Component\Mailer\Bridge\Mailchimp\Factory\MandrillTransportFactory; +use Symfony\Component\Mailer\Tests\TransportFactoryTestCase; +use Symfony\Component\Mailer\Transport\Dsn; +use Symfony\Component\Mailer\Transport\TransportFactoryInterface; + +class MandrillTransportFactoryTest extends TransportFactoryTestCase +{ + public function getFactory(): TransportFactoryInterface + { + return new MandrillTransportFactory($this->getDispatcher(), $this->getClient(), $this->getLogger()); + } + + public function supportsProvider(): iterable + { + yield [ + new Dsn('api', 'mandrill'), + true, + ]; + + yield [ + new Dsn('http', 'mandrill'), + true, + ]; + + yield [ + new Dsn('smtp', 'mandrill'), + true, + ]; + + yield [ + new Dsn('smtp', 'example.com'), + false, + ]; + } + + public function createProvider(): iterable + { + $client = $this->getClient(); + $dispatcher = $this->getDispatcher(); + $logger = $this->getLogger(); + + yield [ + new Dsn('api', 'mandrill', self::USER), + new Mailchimp\Http\Api\MandrillTransport(self::USER, $client, $dispatcher, $logger), + ]; + + yield [ + new Dsn('http', 'mandrill', self::USER), + new Mailchimp\Http\MandrillTransport(self::USER, $client, $dispatcher, $logger), + ]; + + yield [ + new Dsn('smtp', 'mandrill', self::USER, self::PASSWORD), + new Mailchimp\Smtp\MandrillTransport(self::USER, self::PASSWORD, $dispatcher, $logger), + ]; + } + + public function unsupportedSchemeProvider(): iterable + { + yield [new Dsn('foo', 'mandrill', self::USER)]; + } + + public function incompleteDsnProvider(): iterable + { + yield [new Dsn('api', 'mandrill')]; + + yield [new Dsn('smtp', 'mandrill', self::USER)]; + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/Factory/MailgunTransportFactory.php b/src/Symfony/Component/Mailer/Bridge/Mailgun/Factory/MailgunTransportFactory.php new file mode 100644 index 0000000000..3cb4369eb3 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/Factory/MailgunTransportFactory.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Mailgun\Factory; + +use Symfony\Component\Mailer\Bridge\Mailgun; +use Symfony\Component\Mailer\Exception\UnsupportedSchemeException; +use Symfony\Component\Mailer\Transport\AbstractTransportFactory; +use Symfony\Component\Mailer\Transport\Dsn; +use Symfony\Component\Mailer\Transport\TransportInterface; + +/** + * @author Konstantin Myakshin + */ +final class MailgunTransportFactory extends AbstractTransportFactory +{ + public function create(Dsn $dsn): TransportInterface + { + $scheme = $dsn->getScheme(); + $user = $this->getUser($dsn); + $password = $this->getPassword($dsn); + $region = $dsn->getOption('region'); + + if ('api' === $scheme) { + return new Mailgun\Http\Api\MailgunTransport($user, $password, $region, $this->client, $this->dispatcher, $this->logger); + } + + if ('http' === $scheme) { + return new Mailgun\Http\MailgunTransport($user, $password, $region, $this->client, $this->dispatcher, $this->logger); + } + + if ('smtp' === $scheme) { + return new Mailgun\Smtp\MailgunTransport($user, $password, $region, $this->dispatcher, $this->logger); + } + + throw new UnsupportedSchemeException($dsn); + } + + public function supports(Dsn $dsn): bool + { + return 'mailgun' === $dsn->getHost(); + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Factory/MailgunTransportFactoryTest.php b/src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Factory/MailgunTransportFactoryTest.php new file mode 100644 index 0000000000..c65b372d4c --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailgun/Tests/Factory/MailgunTransportFactoryTest.php @@ -0,0 +1,79 @@ +getDispatcher(), $this->getClient(), $this->getLogger()); + } + + public function supportsProvider(): iterable + { + yield [ + new Dsn('api', 'mailgun'), + true, + ]; + + yield [ + new Dsn('http', 'mailgun'), + true, + ]; + + yield [ + new Dsn('smtp', 'mailgun'), + true, + ]; + + yield [ + new Dsn('smtp', 'example.com'), + false, + ]; + } + + public function createProvider(): iterable + { + $client = $this->getClient(); + $dispatcher = $this->getDispatcher(); + $logger = $this->getLogger(); + + yield [ + new Dsn('api', 'mailgun', self::USER, self::PASSWORD), + new Mailgun\Http\Api\MailgunTransport(self::USER, self::PASSWORD, null, $client, $dispatcher, $logger), + ]; + + yield [ + new Dsn('api', 'mailgun', self::USER, self::PASSWORD, null, ['region' => 'eu']), + new Mailgun\Http\Api\MailgunTransport(self::USER, self::PASSWORD, 'eu', $client, $dispatcher, $logger), + ]; + + yield [ + new Dsn('http', 'mailgun', self::USER, self::PASSWORD), + new Mailgun\Http\MailgunTransport(self::USER, self::PASSWORD, null, $client, $dispatcher, $logger), + ]; + + yield [ + new Dsn('smtp', 'mailgun', self::USER, self::PASSWORD), + new Mailgun\Smtp\MailgunTransport(self::USER, self::PASSWORD, null, $dispatcher, $logger), + ]; + } + + public function unsupportedSchemeProvider(): iterable + { + yield [new Dsn('foo', 'mailgun', self::USER, self::PASSWORD)]; + } + + public function incompleteDsnProvider(): iterable + { + yield [new Dsn('api', 'mailgun', self::USER)]; + + yield [new Dsn('api', 'mailgun', null, self::PASSWORD)]; + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Postmark/Factory/PostmarkTransportFactory.php b/src/Symfony/Component/Mailer/Bridge/Postmark/Factory/PostmarkTransportFactory.php new file mode 100644 index 0000000000..0a67102120 --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Postmark/Factory/PostmarkTransportFactory.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Postmark\Factory; + +use Symfony\Component\Mailer\Bridge\Postmark; +use Symfony\Component\Mailer\Exception\UnsupportedSchemeException; +use Symfony\Component\Mailer\Transport\AbstractTransportFactory; +use Symfony\Component\Mailer\Transport\Dsn; +use Symfony\Component\Mailer\Transport\TransportInterface; + +/** + * @author Konstantin Myakshin + */ +final class PostmarkTransportFactory extends AbstractTransportFactory +{ + public function create(Dsn $dsn): TransportInterface + { + $scheme = $dsn->getScheme(); + $user = $this->getUser($dsn); + + if ('api' === $scheme) { + return new Postmark\Http\Api\PostmarkTransport($user, $this->client, $this->dispatcher, $this->logger); + } + + if ('smtp' === $scheme) { + return new Postmark\Smtp\PostmarkTransport($user, $this->dispatcher, $this->logger); + } + + throw new UnsupportedSchemeException($dsn); + } + + public function supports(Dsn $dsn): bool + { + return 'postmark' === $dsn->getHost(); + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Postmark/Tests/Factory/PostmarkTransportFactoryTest.php b/src/Symfony/Component/Mailer/Bridge/Postmark/Tests/Factory/PostmarkTransportFactoryTest.php new file mode 100644 index 0000000000..0de2e35aea --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Postmark/Tests/Factory/PostmarkTransportFactoryTest.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Postmark\Tests\Factory; + +use Symfony\Component\Mailer\Bridge\Postmark; +use Symfony\Component\Mailer\Bridge\Postmark\Factory\PostmarkTransportFactory; +use Symfony\Component\Mailer\Tests\TransportFactoryTestCase; +use Symfony\Component\Mailer\Transport\Dsn; +use Symfony\Component\Mailer\Transport\TransportFactoryInterface; + +class PostmarkTransportFactoryTest extends TransportFactoryTestCase +{ + public function getFactory(): TransportFactoryInterface + { + return new PostmarkTransportFactory($this->getDispatcher(), $this->getClient(), $this->getLogger()); + } + + public function supportsProvider(): iterable + { + yield [ + new Dsn('api', 'postmark'), + true, + ]; + + yield [ + new Dsn('smtp', 'postmark'), + true, + ]; + + yield [ + new Dsn('smtp', 'example.com'), + false, + ]; + } + + public function createProvider(): iterable + { + $dispatcher = $this->getDispatcher(); + $logger = $this->getLogger(); + + yield [ + new Dsn('api', 'postmark', self::USER), + new Postmark\Http\Api\PostmarkTransport(self::USER, $this->getClient(), $dispatcher, $logger), + ]; + + yield [ + new Dsn('smtp', 'postmark', self::USER), + new Postmark\Smtp\PostmarkTransport(self::USER, $dispatcher, $logger), + ]; + } + + public function unsupportedSchemeProvider(): iterable + { + yield [new Dsn('foo', 'postmark', self::USER)]; + } + + public function incompleteDsnProvider(): iterable + { + yield [new Dsn('api', 'postmark')]; + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Factory/SendgridTransportFactory.php b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Factory/SendgridTransportFactory.php new file mode 100644 index 0000000000..ec7ed3cfdd --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Factory/SendgridTransportFactory.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Sendgrid\Factory; + +use Symfony\Component\Mailer\Bridge\Sendgrid; +use Symfony\Component\Mailer\Exception\UnsupportedSchemeException; +use Symfony\Component\Mailer\Transport\AbstractTransportFactory; +use Symfony\Component\Mailer\Transport\Dsn; +use Symfony\Component\Mailer\Transport\TransportInterface; + +/** + * @author Konstantin Myakshin + */ +final class SendgridTransportFactory extends AbstractTransportFactory +{ + public function create(Dsn $dsn): TransportInterface + { + $key = $this->getUser($dsn); + + if ('api' === $dsn->getScheme()) { + return new Sendgrid\Http\Api\SendgridTransport($key, $this->client, $this->dispatcher, $this->logger); + } + + if ('smtp' === $dsn->getScheme()) { + return new Sendgrid\Smtp\SendgridTransport($key, $this->dispatcher, $this->logger); + } + + throw new UnsupportedSchemeException($dsn); + } + + public function supports(Dsn $dsn): bool + { + return 'sendgrid' === $dsn->getHost(); + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Factory/SendgridTransportFactoryTest.php b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Factory/SendgridTransportFactoryTest.php new file mode 100644 index 0000000000..82ac41e03b --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Sendgrid/Tests/Factory/SendgridTransportFactoryTest.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Sendgrid\Tests\Factory; + +use Symfony\Component\Mailer\Bridge\Sendgrid; +use Symfony\Component\Mailer\Bridge\Sendgrid\Factory\SendgridTransportFactory; +use Symfony\Component\Mailer\Tests\TransportFactoryTestCase; +use Symfony\Component\Mailer\Transport\Dsn; +use Symfony\Component\Mailer\Transport\TransportFactoryInterface; + +class SendgridTransportFactoryTest extends TransportFactoryTestCase +{ + public function getFactory(): TransportFactoryInterface + { + return new SendgridTransportFactory($this->getDispatcher(), $this->getClient(), $this->getLogger()); + } + + public function supportsProvider(): iterable + { + yield [ + new Dsn('api', 'sendgrid'), + true, + ]; + + yield [ + new Dsn('smtp', 'sendgrid'), + true, + ]; + + yield [ + new Dsn('smtp', 'example.com'), + false, + ]; + } + + public function createProvider(): iterable + { + $dispatcher = $this->getDispatcher(); + $logger = $this->getLogger(); + + yield [ + new Dsn('api', 'sendgrid', self::USER), + new Sendgrid\Http\Api\SendgridTransport(self::USER, $this->getClient(), $dispatcher, $logger), + ]; + + yield [ + new Dsn('smtp', 'sendgrid', self::USER), + new Sendgrid\Smtp\SendgridTransport(self::USER, $dispatcher, $logger), + ]; + } + + public function unsupportedSchemeProvider(): iterable + { + yield [new Dsn('foo', 'sendgrid', self::USER)]; + } +} diff --git a/src/Symfony/Component/Mailer/CHANGELOG.md b/src/Symfony/Component/Mailer/CHANGELOG.md index 5b2c5c528f..7e2c53504b 100644 --- a/src/Symfony/Component/Mailer/CHANGELOG.md +++ b/src/Symfony/Component/Mailer/CHANGELOG.md @@ -6,6 +6,8 @@ CHANGELOG * [BC BREAK] Transports depend on `Symfony\Contracts\EventDispatcher\EventDispatcherInterface` instead of `Symfony\Component\EventDispatcher\EventDispatcherInterface`. + * Added possibility to register custom transport for dsn by implementing + `Symfony\Component\Mailer\Transport\TransportFactoryInterface` and tagging with `mailer.transport_factory` tag in DI. 4.3.0 ----- diff --git a/src/Symfony/Component/Mailer/Exception/IncompleteDsnException.php b/src/Symfony/Component/Mailer/Exception/IncompleteDsnException.php new file mode 100644 index 0000000000..f2618b65d9 --- /dev/null +++ b/src/Symfony/Component/Mailer/Exception/IncompleteDsnException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Exception; + +/** + * @author Konstantin Myakshin + */ +class IncompleteDsnException extends InvalidArgumentException +{ +} diff --git a/src/Symfony/Component/Mailer/Exception/UnsupportedHostException.php b/src/Symfony/Component/Mailer/Exception/UnsupportedHostException.php new file mode 100644 index 0000000000..92af7b2567 --- /dev/null +++ b/src/Symfony/Component/Mailer/Exception/UnsupportedHostException.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Exception; + +use Symfony\Component\Mailer\Bridge; +use Symfony\Component\Mailer\Transport\Dsn; + +/** + * @author Konstantin Myakshin + */ +class UnsupportedHostException extends LogicException +{ + private const HOST_TO_PACKAGE_MAP = [ + 'gmail' => [ + 'class' => Bridge\Google\Factory\GmailTransportFactory::class, + 'package' => 'symfony/google-mailer', + ], + 'mailgun' => [ + 'class' => Bridge\Mailgun\Factory\MailgunTransportFactory::class, + 'package' => 'symfony/mailgun-mailer', + ], + 'postmark' => [ + 'class' => Bridge\Postmark\Factory\PostmarkTransportFactory::class, + 'package' => 'symfony/postmark-mailer', + ], + 'sendgrid' => [ + 'class' => Bridge\Sendgrid\Factory\SendgridTransportFactory::class, + 'package' => 'symfony/sendgrid-mailer', + ], + 'ses' => [ + 'class' => Bridge\Amazon\Factory\SesTransportFactory::class, + 'package' => 'symfony/amazon-mailer', + ], + 'mandrill' => [ + 'class' => Bridge\Mailchimp\Factory\MandrillTransportFactory::class, + 'package' => 'symfony/mailchimp-mailer', + ], + ]; + + public function __construct(Dsn $dsn) + { + $host = $dsn->getHost(); + $package = self::HOST_TO_PACKAGE_MAP[$host] ?? null; + if ($package && !class_exists($package['class'])) { + parent::__construct(sprintf('Unable to send emails via "%s" as the bridge is not installed. Try running "composer require %s".', $host, $package['package'])); + + return; + } + + parent::__construct(sprintf('The "%s" mailer is not supported.', $host)); + } +} diff --git a/src/Symfony/Component/Mailer/Exception/UnsupportedSchemeException.php b/src/Symfony/Component/Mailer/Exception/UnsupportedSchemeException.php new file mode 100644 index 0000000000..8457378c46 --- /dev/null +++ b/src/Symfony/Component/Mailer/Exception/UnsupportedSchemeException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Exception; + +use Symfony\Component\Mailer\Transport\Dsn; + +/** + * @author Konstantin Myakshin + */ +class UnsupportedSchemeException extends LogicException +{ + public function __construct(Dsn $dsn) + { + parent::__construct(sprintf('The "%s" scheme is not supported for mailer "%s".', $dsn->getScheme(), $dsn->getHost())); + } +} diff --git a/src/Symfony/Component/Mailer/Tests/Transport/DsnTest.php b/src/Symfony/Component/Mailer/Tests/Transport/DsnTest.php new file mode 100644 index 0000000000..04f12030da --- /dev/null +++ b/src/Symfony/Component/Mailer/Tests/Transport/DsnTest.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Tests\Transport; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Mailer\Exception\InvalidArgumentException; +use Symfony\Component\Mailer\Transport\Dsn; + +class DsnTest extends TestCase +{ + /** + * @dataProvider fromStringProvider + */ + public function testFromString(string $string, Dsn $dsn): void + { + $this->assertEquals($dsn, Dsn::fromString($string)); + } + + public function testGetOption(): void + { + $options = ['with_value' => 'some value', 'nullable' => null]; + $dsn = new Dsn('smtp', 'example.com', null, null, null, $options); + + $this->assertSame('some value', $dsn->getOption('with_value')); + $this->assertSame('default', $dsn->getOption('nullable', 'default')); + $this->assertSame('default', $dsn->getOption('not_existent_property', 'default')); + } + + /** + * @dataProvider invalidDsnProvider + */ + public function testInvalidDsn(string $dsn, string $exceptionMessage): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage($exceptionMessage); + Dsn::fromString($dsn); + } + + public function fromStringProvider(): iterable + { + yield 'simple smtp without user and pass' => [ + 'smtp://example.com', + new Dsn('smtp', 'example.com'), + ]; + + yield 'simple smtp with custom port' => [ + 'smtp://user1:pass2@example.com:99', + new Dsn('smtp', 'example.com', 'user1', 'pass2', 99), + ]; + + yield 'gmail smtp with urlencoded user and pass' => [ + 'smtp://u%24er:pa%24s@gmail', + new Dsn('smtp', 'gmail', 'u$er', 'pa$s'), + ]; + + yield 'mailgun api with custom options' => [ + 'api://u%24er:pa%24s@mailgun?region=eu', + new Dsn('api', 'mailgun', 'u$er', 'pa$s', null, ['region' => 'eu']), + ]; + } + + public function invalidDsnProvider(): iterable + { + yield [ + 'some://', + 'The "some://" mailer DSN is invalid.', + ]; + + yield [ + '//sendmail', + 'The "//sendmail" mailer DSN must contain a transport scheme.', + ]; + + yield [ + 'file:///some/path', + 'The "file:///some/path" mailer DSN must contain a mailer name.', + ]; + } +} diff --git a/src/Symfony/Component/Mailer/Tests/Transport/NullTransportFactoryTest.php b/src/Symfony/Component/Mailer/Tests/Transport/NullTransportFactoryTest.php new file mode 100644 index 0000000000..8b8ab4a8cd --- /dev/null +++ b/src/Symfony/Component/Mailer/Tests/Transport/NullTransportFactoryTest.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Tests\Transport; + +use Symfony\Component\Mailer\Tests\TransportFactoryTestCase; +use Symfony\Component\Mailer\Transport\Dsn; +use Symfony\Component\Mailer\Transport\NullTransport; +use Symfony\Component\Mailer\Transport\NullTransportFactory; +use Symfony\Component\Mailer\Transport\TransportFactoryInterface; + +class NullTransportFactoryTest extends TransportFactoryTestCase +{ + public function getFactory(): TransportFactoryInterface + { + return new NullTransportFactory($this->getDispatcher(), $this->getClient(), $this->getLogger()); + } + + public function supportsProvider(): iterable + { + yield [ + new Dsn('smtp', 'null'), + true, + ]; + + yield [ + new Dsn('smtp', 'example.com'), + false, + ]; + } + + public function createProvider(): iterable + { + yield [ + new Dsn('smtp', 'null'), + new NullTransport($this->getDispatcher(), $this->getLogger()), + ]; + } + + public function unsupportedSchemeProvider(): iterable + { + yield [new Dsn('foo', 'null')]; + } +} diff --git a/src/Symfony/Component/Mailer/Tests/Transport/SendmailTransportFactoryTest.php b/src/Symfony/Component/Mailer/Tests/Transport/SendmailTransportFactoryTest.php new file mode 100644 index 0000000000..d5ee4bec52 --- /dev/null +++ b/src/Symfony/Component/Mailer/Tests/Transport/SendmailTransportFactoryTest.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Tests\Transport; + +use Symfony\Component\Mailer\Tests\TransportFactoryTestCase; +use Symfony\Component\Mailer\Transport\Dsn; +use Symfony\Component\Mailer\Transport\SendmailTransport; +use Symfony\Component\Mailer\Transport\SendmailTransportFactory; +use Symfony\Component\Mailer\Transport\TransportFactoryInterface; + +class SendmailTransportFactoryTest extends TransportFactoryTestCase +{ + public function getFactory(): TransportFactoryInterface + { + return new SendmailTransportFactory($this->getDispatcher(), $this->getClient(), $this->getLogger()); + } + + public function supportsProvider(): iterable + { + yield [ + new Dsn('smtp', 'sendmail'), + true, + ]; + + yield [ + new Dsn('smtp', 'example.com'), + false, + ]; + } + + public function createProvider(): iterable + { + yield [ + new Dsn('smtp', 'sendmail'), + new SendmailTransport(null, $this->getDispatcher(), $this->getLogger()), + ]; + } + + public function unsupportedSchemeProvider(): iterable + { + yield [new Dsn('http', 'sendmail')]; + } +} diff --git a/src/Symfony/Component/Mailer/Tests/Transport/Smtp/EsmtpTransportFactoryTest.php b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/EsmtpTransportFactoryTest.php new file mode 100644 index 0000000000..3a76d46fe1 --- /dev/null +++ b/src/Symfony/Component/Mailer/Tests/Transport/Smtp/EsmtpTransportFactoryTest.php @@ -0,0 +1,52 @@ +getDispatcher(), $this->getClient(), $this->getLogger()); + } + + public function supportsProvider(): iterable + { + yield [ + new Dsn('smtp', 'example.com'), + true, + ]; + + yield [ + new Dsn('api', 'example.com'), + false, + ]; + } + + public function createProvider(): iterable + { + $eventDispatcher = $this->getDispatcher(); + $logger = $this->getLogger(); + + $transport = new EsmtpTransport('example.com', 25, null, null, $eventDispatcher, $logger); + + yield [ + new Dsn('smtp', 'example.com'), + $transport, + ]; + + $transport = new EsmtpTransport('example.com', 99, 'ssl', 'login', $eventDispatcher, $logger); + $transport->setUsername(self::USER); + $transport->setPassword(self::PASSWORD); + + yield [ + new Dsn('smtp', 'example.com', self::USER, self::PASSWORD, 99, ['encryption' => 'ssl', 'auth_mode' => 'login']), + $transport, + ]; + } +} diff --git a/src/Symfony/Component/Mailer/Tests/TransportFactoryTestCase.php b/src/Symfony/Component/Mailer/Tests/TransportFactoryTestCase.php new file mode 100644 index 0000000000..b17f81c1e6 --- /dev/null +++ b/src/Symfony/Component/Mailer/Tests/TransportFactoryTestCase.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Tests; + +use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; +use Symfony\Component\Mailer\Exception\IncompleteDsnException; +use Symfony\Component\Mailer\Exception\UnsupportedSchemeException; +use Symfony\Component\Mailer\Transport\Dsn; +use Symfony\Component\Mailer\Transport\TransportFactoryInterface; +use Symfony\Component\Mailer\Transport\TransportInterface; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +abstract class TransportFactoryTestCase extends TestCase +{ + protected const USER = 'u$er'; + protected const PASSWORD = 'pa$s'; + + protected $dispatcher; + protected $client; + protected $logger; + + abstract public function getFactory(): TransportFactoryInterface; + + abstract public function supportsProvider(): iterable; + + abstract public function createProvider(): iterable; + + public function unsupportedSchemeProvider(): iterable + { + return []; + } + + public function incompleteDsnProvider(): iterable + { + return []; + } + + /** + * @dataProvider supportsProvider + */ + public function testSupports(Dsn $dsn, bool $supports): void + { + $factory = $this->getFactory(); + + $this->assertSame($supports, $factory->supports($dsn)); + } + + /** + * @dataProvider createProvider + */ + public function testCreate(Dsn $dsn, TransportInterface $transport): void + { + $factory = $this->getFactory(); + + $this->assertEquals($transport, $factory->create($dsn)); + } + + /** + * @dataProvider unsupportedSchemeProvider + */ + public function testUnsupportedSchemeException(Dsn $dsn): void + { + $factory = $this->getFactory(); + + $this->expectException(UnsupportedSchemeException::class); + $factory->create($dsn); + } + + /** + * @dataProvider incompleteDsnProvider + */ + public function testIncompleteDsnException(Dsn $dsn): void + { + $factory = $this->getFactory(); + + $this->expectException(IncompleteDsnException::class); + $factory->create($dsn); + } + + protected function getDispatcher(): EventDispatcherInterface + { + return $this->dispatcher ?? $this->dispatcher = $this->createMock(EventDispatcherInterface::class); + } + + protected function getClient(): HttpClientInterface + { + return $this->client ?? $this->client = $this->createMock(HttpClientInterface::class); + } + + protected function getLogger(): LoggerInterface + { + return $this->logger ?? $this->logger = $this->createMock(LoggerInterface::class); + } +} diff --git a/src/Symfony/Component/Mailer/Tests/TransportTest.php b/src/Symfony/Component/Mailer/Tests/TransportTest.php index 8c7cd99d98..6fb3a1a08d 100644 --- a/src/Symfony/Component/Mailer/Tests/TransportTest.php +++ b/src/Symfony/Component/Mailer/Tests/TransportTest.php @@ -12,345 +12,71 @@ namespace Symfony\Component\Mailer\Tests; use PHPUnit\Framework\TestCase; -use Psr\Log\LoggerInterface; -use Symfony\Component\Mailer\Bridge\Amazon; -use Symfony\Component\Mailer\Bridge\Google; -use Symfony\Component\Mailer\Bridge\Mailchimp; -use Symfony\Component\Mailer\Bridge\Mailgun; -use Symfony\Component\Mailer\Bridge\Postmark; -use Symfony\Component\Mailer\Bridge\Sendgrid; -use Symfony\Component\Mailer\Exception\InvalidArgumentException; -use Symfony\Component\Mailer\Exception\LogicException; +use Symfony\Component\Mailer\SentMessage; +use Symfony\Component\Mailer\SmtpEnvelope; use Symfony\Component\Mailer\Transport; -use Symfony\Component\Mime\Email; -use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; -use Symfony\Contracts\HttpClient\HttpClientInterface; -use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Component\Mailer\Transport\Dsn; +use Symfony\Component\Mailer\Transport\TransportInterface; +use Symfony\Component\Mime\RawMessage; class TransportTest extends TestCase { - public function testFromDsnNull() + /** + * @dataProvider fromStringProvider + */ + public function testFromString(string $dsn, TransportInterface $transport): void { - $dispatcher = $this->createMock(EventDispatcherInterface::class); - $logger = $this->createMock(LoggerInterface::class); - $transport = Transport::fromDsn('smtp://null', $dispatcher, null, $logger); - $this->assertInstanceOf(Transport\NullTransport::class, $transport); - $p = new \ReflectionProperty(Transport\AbstractTransport::class, 'dispatcher'); - $p->setAccessible(true); - $this->assertSame($dispatcher, $p->getValue($transport)); + $transportFactory = new Transport([new DummyTransportFactory()]); + + $this->assertEquals($transport, $transportFactory->fromString($dsn)); } - public function testFromDsnSendmail() + public function fromStringProvider(): iterable { - $dispatcher = $this->createMock(EventDispatcherInterface::class); - $logger = $this->createMock(LoggerInterface::class); - $transport = Transport::fromDsn('smtp://sendmail', $dispatcher, null, $logger); - $this->assertInstanceOf(Transport\SendmailTransport::class, $transport); - $p = new \ReflectionProperty(Transport\AbstractTransport::class, 'dispatcher'); - $p->setAccessible(true); - $this->assertSame($dispatcher, $p->getValue($transport)); - } + $transportA = new DummyTransport('a'); + $transportB = new DummyTransport('b'); - public function testFromDsnSmtp() - { - $dispatcher = $this->createMock(EventDispatcherInterface::class); - $logger = $this->createMock(LoggerInterface::class); - $transport = Transport::fromDsn('smtp://localhost:44?auth_mode=plain&encryption=tls', $dispatcher, null, $logger); - $this->assertInstanceOf(Transport\Smtp\SmtpTransport::class, $transport); - $this->assertProperties($transport, $dispatcher, $logger); - $this->assertEquals('localhost', $transport->getStream()->getHost()); - $this->assertEquals('plain', $transport->getAuthMode()); - $this->assertTrue($transport->getStream()->isTLS()); - $this->assertEquals(44, $transport->getStream()->getPort()); - } + yield 'simple transport' => [ + 'dummy://a', + $transportA, + ]; - public function testFromInvalidDsn() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "some://" mailer DSN is invalid.'); - Transport::fromDsn('some://'); - } + yield 'failover transport' => [ + 'dummy://a || dummy://b', + new Transport\FailoverTransport([$transportA, $transportB]), + ]; - public function testNoScheme() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "//sendmail" mailer DSN must contain a transport scheme.'); - Transport::fromDsn('//sendmail'); - } - - public function testFromInvalidDsnNoHost() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The "file:///some/path" mailer DSN must contain a mailer name.'); - Transport::fromDsn('file:///some/path'); - } - - public function testFromInvalidTransportName() - { - $this->expectException(LogicException::class); - Transport::fromDsn('api://foobar'); - } - - public function testFromDsnGmail() - { - $dispatcher = $this->createMock(EventDispatcherInterface::class); - $logger = $this->createMock(LoggerInterface::class); - $transport = Transport::fromDsn('smtp://'.urlencode('u$er').':'.urlencode('pa$s').'@gmail', $dispatcher, null, $logger); - $this->assertInstanceOf(Google\Smtp\GmailTransport::class, $transport); - $this->assertEquals('u$er', $transport->getUsername()); - $this->assertEquals('pa$s', $transport->getPassword()); - $this->assertProperties($transport, $dispatcher, $logger); - - $this->expectException(LogicException::class); - Transport::fromDsn('http://gmail'); - } - - public function testFromDsnMailgun() - { - $dispatcher = $this->createMock(EventDispatcherInterface::class); - $logger = $this->createMock(LoggerInterface::class); - $transport = Transport::fromDsn('smtp://'.urlencode('u$er').':'.urlencode('pa$s').'@mailgun', $dispatcher, null, $logger); - $this->assertInstanceOf(Mailgun\Smtp\MailgunTransport::class, $transport); - $this->assertEquals('u$er', $transport->getUsername()); - $this->assertEquals('pa$s', $transport->getPassword()); - $this->assertProperties($transport, $dispatcher, $logger); - - $transport = Transport::fromDsn('smtp://'.urlencode('u$er').':'.urlencode('pa$s').'@mailgun', $dispatcher, null, $logger); - $this->assertEquals('smtp.mailgun.org', $transport->getStream()->getHost()); - - $transport = Transport::fromDsn('smtp://'.urlencode('u$er').':'.urlencode('pa$s').'@mailgun?region=eu', $dispatcher, null, $logger); - $this->assertEquals('smtp.eu.mailgun.org', $transport->getStream()->getHost()); - - $transport = Transport::fromDsn('smtp://'.urlencode('u$er').':'.urlencode('pa$s').'@mailgun?region=us', $dispatcher, null, $logger); - $this->assertEquals('smtp.mailgun.org', $transport->getStream()->getHost()); - - $client = $this->createMock(HttpClientInterface::class); - $transport = Transport::fromDsn('http://'.urlencode('u$er').':'.urlencode('pa$s').'@mailgun', $dispatcher, $client, $logger); - $this->assertInstanceOf(Mailgun\Http\MailgunTransport::class, $transport); - $this->assertProperties($transport, $dispatcher, $logger, [ - 'key' => 'u$er', - 'domain' => 'pa$s', - 'client' => $client, - ]); - - $response = $this->createMock(ResponseInterface::class); - $response->expects($this->any())->method('getStatusCode')->willReturn(200); - $message = (new Email())->from('me@me.com')->to('you@you.com')->subject('hello')->text('Hello you'); - - $client = $this->createMock(HttpClientInterface::class); - $client->expects($this->once())->method('request')->with('POST', 'https://api.mailgun.net/v3/pa%24s/messages.mime')->willReturn($response); - $transport = Transport::fromDsn('http://'.urlencode('u$er').':'.urlencode('pa$s').'@mailgun', $dispatcher, $client, $logger); - $transport->send($message); - - $client = $this->createMock(HttpClientInterface::class); - $client->expects($this->once())->method('request')->with('POST', 'https://api.eu.mailgun.net/v3/pa%24s/messages.mime')->willReturn($response); - $transport = Transport::fromDsn('http://'.urlencode('u$er').':'.urlencode('pa$s').'@mailgun?region=eu', $dispatcher, $client, $logger); - $transport->send($message); - - $client = $this->createMock(HttpClientInterface::class); - $client->expects($this->once())->method('request')->with('POST', 'https://api.mailgun.net/v3/pa%24s/messages.mime')->willReturn($response); - $transport = Transport::fromDsn('http://'.urlencode('u$er').':'.urlencode('pa$s').'@mailgun?region=us', $dispatcher, $client, $logger); - $transport->send($message); - - $transport = Transport::fromDsn('api://'.urlencode('u$er').':'.urlencode('pa$s').'@mailgun', $dispatcher, $client, $logger); - $this->assertInstanceOf(Mailgun\Http\Api\MailgunTransport::class, $transport); - $this->assertProperties($transport, $dispatcher, $logger, [ - 'key' => 'u$er', - 'domain' => 'pa$s', - 'client' => $client, - ]); - - $client = $this->createMock(HttpClientInterface::class); - $client->expects($this->once())->method('request')->with('POST', 'https://api.mailgun.net/v3/pa%24s/messages')->willReturn($response); - $transport = Transport::fromDsn('api://'.urlencode('u$er').':'.urlencode('pa$s').'@mailgun', $dispatcher, $client, $logger); - $transport->send($message); - - $client = $this->createMock(HttpClientInterface::class); - $client->expects($this->once())->method('request')->with('POST', 'https://api.eu.mailgun.net/v3/pa%24s/messages')->willReturn($response); - $transport = Transport::fromDsn('api://'.urlencode('u$er').':'.urlencode('pa$s').'@mailgun?region=eu', $dispatcher, $client, $logger); - $transport->send($message); - - $client = $this->createMock(HttpClientInterface::class); - $client->expects($this->once())->method('request')->with('POST', 'https://api.mailgun.net/v3/pa%24s/messages')->willReturn($response); - $transport = Transport::fromDsn('api://'.urlencode('u$er').':'.urlencode('pa$s').'@mailgun?region=us', $dispatcher, $client, $logger); - $transport->send($message); - - $message = (new Email())->from('me@me.com')->to('you@you.com')->subject('hello')->html('test'); - $client = $this->createMock(HttpClientInterface::class); - $client->expects($this->once())->method('request')->with('POST', 'https://api.mailgun.net/v3/pa%24s/messages')->willReturn($response); - $transport = Transport::fromDsn('api://'.urlencode('u$er').':'.urlencode('pa$s').'@mailgun?region=us', $dispatcher, $client, $logger); - $transport->send($message); - - $stream = fopen('data://text/plain,'.$message->getTextBody(), 'r'); - $message = (new Email())->from('me@me.com')->to('you@you.com')->subject('hello')->html($stream); - $client = $this->createMock(HttpClientInterface::class); - $client->expects($this->once())->method('request')->with('POST', 'https://api.mailgun.net/v3/pa%24s/messages')->willReturn($response); - $transport = Transport::fromDsn('api://'.urlencode('u$er').':'.urlencode('pa$s').'@mailgun?region=us', $dispatcher, $client, $logger); - $transport->send($message); - - $this->expectException(LogicException::class); - Transport::fromDsn('foo://mailgun'); - } - - public function testFromDsnPostmark() - { - $dispatcher = $this->createMock(EventDispatcherInterface::class); - $logger = $this->createMock(LoggerInterface::class); - $transport = Transport::fromDsn('smtp://'.urlencode('u$er').'@postmark', $dispatcher, null, $logger); - $this->assertInstanceOf(Postmark\Smtp\PostmarkTransport::class, $transport); - $this->assertEquals('u$er', $transport->getUsername()); - $this->assertEquals('u$er', $transport->getPassword()); - $this->assertProperties($transport, $dispatcher, $logger); - - $client = $this->createMock(HttpClientInterface::class); - $transport = Transport::fromDsn('api://'.urlencode('u$er').'@postmark', $dispatcher, $client, $logger); - $this->assertInstanceOf(Postmark\Http\Api\PostmarkTransport::class, $transport); - $this->assertProperties($transport, $dispatcher, $logger, [ - 'key' => 'u$er', - 'client' => $client, - ]); - - $this->expectException(LogicException::class); - Transport::fromDsn('http://postmark'); - } - - public function testFromDsnSendgrid() - { - $dispatcher = $this->createMock(EventDispatcherInterface::class); - $logger = $this->createMock(LoggerInterface::class); - $transport = Transport::fromDsn('smtp://'.urlencode('u$er').'@sendgrid', $dispatcher, null, $logger); - $this->assertInstanceOf(Sendgrid\Smtp\SendgridTransport::class, $transport); - $this->assertEquals('apikey', $transport->getUsername()); - $this->assertEquals('u$er', $transport->getPassword()); - $this->assertProperties($transport, $dispatcher, $logger); - - $client = $this->createMock(HttpClientInterface::class); - $transport = Transport::fromDsn('api://'.urlencode('u$er').'@sendgrid', $dispatcher, $client, $logger); - $this->assertInstanceOf(Sendgrid\Http\Api\SendgridTransport::class, $transport); - $this->assertProperties($transport, $dispatcher, $logger, [ - 'key' => 'u$er', - 'client' => $client, - ]); - - $this->expectException(LogicException::class); - Transport::fromDsn('http://sendgrid'); - } - - public function testFromDsnAmazonSes() - { - $dispatcher = $this->createMock(EventDispatcherInterface::class); - $logger = $this->createMock(LoggerInterface::class); - $transport = Transport::fromDsn('smtp://'.urlencode('u$er').':'.urlencode('pa$s').'@ses?region=sun', $dispatcher, null, $logger); - $this->assertInstanceOf(Amazon\Smtp\SesTransport::class, $transport); - $this->assertEquals('u$er', $transport->getUsername()); - $this->assertEquals('pa$s', $transport->getPassword()); - $this->assertContains('.sun.', $transport->getStream()->getHost()); - $this->assertProperties($transport, $dispatcher, $logger); - - $client = $this->createMock(HttpClientInterface::class); - $transport = Transport::fromDsn('http://'.urlencode('u$er').':'.urlencode('pa$s').'@ses?region=sun', $dispatcher, $client, $logger); - $this->assertInstanceOf(Amazon\Http\SesTransport::class, $transport); - $this->assertProperties($transport, $dispatcher, $logger, [ - 'accessKey' => 'u$er', - 'secretKey' => 'pa$s', - 'region' => 'sun', - 'client' => $client, - ]); - - $transport = Transport::fromDsn('api://'.urlencode('u$er').':'.urlencode('pa$s').'@ses?region=sun', $dispatcher, $client, $logger); - $this->assertInstanceOf(Amazon\Http\Api\SesTransport::class, $transport); - $this->assertProperties($transport, $dispatcher, $logger, [ - 'accessKey' => 'u$er', - 'secretKey' => 'pa$s', - 'region' => 'sun', - 'client' => $client, - ]); - - $this->expectException(LogicException::class); - Transport::fromDsn('foo://ses'); - } - - public function testFromDsnMailchimp() - { - $dispatcher = $this->createMock(EventDispatcherInterface::class); - $logger = $this->createMock(LoggerInterface::class); - $transport = Transport::fromDsn('smtp://'.urlencode('u$er').':'.urlencode('pa$s').'@mandrill', $dispatcher, null, $logger); - $this->assertInstanceOf(Mailchimp\Smtp\MandrillTransport::class, $transport); - $this->assertEquals('u$er', $transport->getUsername()); - $this->assertEquals('pa$s', $transport->getPassword()); - $this->assertProperties($transport, $dispatcher, $logger); - - $client = $this->createMock(HttpClientInterface::class); - $transport = Transport::fromDsn('http://'.urlencode('u$er').'@mandrill', $dispatcher, $client, $logger); - $this->assertInstanceOf(Mailchimp\Http\MandrillTransport::class, $transport); - $this->assertProperties($transport, $dispatcher, $logger, [ - 'key' => 'u$er', - 'client' => $client, - ]); - - $transport = Transport::fromDsn('api://'.urlencode('u$er').'@mandrill', $dispatcher, $client, $logger); - $this->assertInstanceOf(Mailchimp\Http\Api\MandrillTransport::class, $transport); - $this->assertProperties($transport, $dispatcher, $logger, [ - 'key' => 'u$er', - 'client' => $client, - ]); - - $this->expectException(LogicException::class); - Transport::fromDsn('foo://mandrill'); - } - - public function testFromDsnFailover() - { - $user = 'user'; - $pass = 'pass'; - $dispatcher = $this->createMock(EventDispatcherInterface::class); - $logger = $this->createMock(LoggerInterface::class); - $transport = Transport::fromDsn('smtp://example.com || smtp://'.urlencode($user).'@example.com || smtp://'.urlencode($user).':'.urlencode($pass).'@example.com', $dispatcher, null, $logger); - $this->assertInstanceOf(Transport\FailoverTransport::class, $transport); - $p = new \ReflectionProperty(Transport\RoundRobinTransport::class, 'transports'); - $p->setAccessible(true); - $transports = $p->getValue($transport); - $this->assertCount(3, $transports); - foreach ($transports as $transport) { - $this->assertProperties($transport, $dispatcher, $logger); - } - $this->assertSame('', $transports[0]->getUsername()); - $this->assertSame('', $transports[0]->getPassword()); - $this->assertSame($user, $transports[1]->getUsername()); - $this->assertSame('', $transports[1]->getPassword()); - $this->assertSame($user, $transports[2]->getUsername()); - $this->assertSame($pass, $transports[2]->getPassword()); - } - - public function testFromDsnRoundRobin() - { - $dispatcher = $this->createMock(EventDispatcherInterface::class); - $logger = $this->createMock(LoggerInterface::class); - $transport = Transport::fromDsn('smtp://null && smtp://null && smtp://null', $dispatcher, null, $logger); - $this->assertInstanceOf(Transport\RoundRobinTransport::class, $transport); - $p = new \ReflectionProperty(Transport\RoundRobinTransport::class, 'transports'); - $p->setAccessible(true); - $transports = $p->getValue($transport); - $this->assertCount(3, $transports); - foreach ($transports as $transport) { - $this->assertProperties($transport, $dispatcher, $logger); - } - } - - private function assertProperties(Transport\TransportInterface $transport, EventDispatcherInterface $dispatcher, LoggerInterface $logger, array $props = []) - { - $p = new \ReflectionProperty(Transport\AbstractTransport::class, 'dispatcher'); - $p->setAccessible(true); - $this->assertSame($dispatcher, $p->getValue($transport)); - - $p = new \ReflectionProperty(Transport\AbstractTransport::class, 'logger'); - $p->setAccessible(true); - $this->assertSame($logger, $p->getValue($transport)); - - foreach ($props as $prop => $value) { - $p = new \ReflectionProperty($transport, $prop); - $p->setAccessible(true); - $this->assertEquals($value, $p->getValue($transport)); - } + yield 'round robin transport' => [ + 'dummy://a && dummy://b', + new Transport\RoundRobinTransport([$transportA, $transportB]), + ]; + } +} + +class DummyTransport implements Transport\TransportInterface +{ + private $host; + + public function __construct(string $host) + { + $this->host = $host; + } + + public function send(RawMessage $message, SmtpEnvelope $envelope = null): ?SentMessage + { + throw new \BadMethodCallException('This method newer should be called.'); + } +} + +class DummyTransportFactory implements Transport\TransportFactoryInterface +{ + public function create(Dsn $dsn): TransportInterface + { + return new DummyTransport($dsn->getHost()); + } + + public function supports(Dsn $dsn): bool + { + return 'dummy' === $dsn->getScheme(); } } diff --git a/src/Symfony/Component/Mailer/Transport.php b/src/Symfony/Component/Mailer/Transport.php index 42f545f9fd..b167b17d8c 100644 --- a/src/Symfony/Component/Mailer/Transport.php +++ b/src/Symfony/Component/Mailer/Transport.php @@ -12,181 +12,107 @@ namespace Symfony\Component\Mailer; use Psr\Log\LoggerInterface; -use Symfony\Component\Mailer\Bridge\Amazon; -use Symfony\Component\Mailer\Bridge\Google; -use Symfony\Component\Mailer\Bridge\Mailchimp; -use Symfony\Component\Mailer\Bridge\Mailgun; -use Symfony\Component\Mailer\Bridge\Postmark; -use Symfony\Component\Mailer\Bridge\Sendgrid; -use Symfony\Component\Mailer\Exception\InvalidArgumentException; -use Symfony\Component\Mailer\Exception\LogicException; +use Symfony\Component\Mailer\Bridge\Amazon\Factory\SesTransportFactory; +use Symfony\Component\Mailer\Bridge\Google\Factory\GmailTransportFactory; +use Symfony\Component\Mailer\Bridge\Mailchimp\Factory\MandrillTransportFactory; +use Symfony\Component\Mailer\Bridge\Mailgun\Factory\MailgunTransportFactory; +use Symfony\Component\Mailer\Bridge\Postmark\Factory\PostmarkTransportFactory; +use Symfony\Component\Mailer\Bridge\Sendgrid\Factory\SendgridTransportFactory; +use Symfony\Component\Mailer\Exception\UnsupportedHostException; +use Symfony\Component\Mailer\Transport\Dsn; +use Symfony\Component\Mailer\Transport\NullTransportFactory; +use Symfony\Component\Mailer\Transport\SendmailTransportFactory; +use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransportFactory; +use Symfony\Component\Mailer\Transport\TransportFactoryInterface; use Symfony\Component\Mailer\Transport\TransportInterface; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; /** * @author Fabien Potencier + * @author Konstantin Myakshin */ class Transport { + private const FACTORY_CLASSES = [ + SesTransportFactory::class, + GmailTransportFactory::class, + MandrillTransportFactory::class, + MailgunTransportFactory::class, + PostmarkTransportFactory::class, + SendgridTransportFactory::class, + ]; + + private $factories; + public static function fromDsn(string $dsn, EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null): TransportInterface { - // failover? + $factory = new self(self::getDefaultFactories($dispatcher, $client, $logger)); + + return $factory->fromString($dsn); + } + + /** + * @param TransportFactoryInterface[] $factories + */ + public function __construct(iterable $factories) + { + $this->factories = $factories; + } + + public function fromString(string $dsn): TransportInterface + { $dsns = preg_split('/\s++\|\|\s++/', $dsn); if (\count($dsns) > 1) { - $transports = []; - foreach ($dsns as $dsn) { - $transports[] = self::createTransport($dsn, $dispatcher, $client, $logger); - } - - return new Transport\FailoverTransport($transports); + return new Transport\FailoverTransport($this->createFromDsns($dsns)); } - // round robin? $dsns = preg_split('/\s++&&\s++/', $dsn); if (\count($dsns) > 1) { - $transports = []; - foreach ($dsns as $dsn) { - $transports[] = self::createTransport($dsn, $dispatcher, $client, $logger); - } - - return new Transport\RoundRobinTransport($transports); + return new Transport\RoundRobinTransport($this->createFromDsns($dsns)); } - return self::createTransport($dsn, $dispatcher, $client, $logger); + return $this->fromDsnObject(Dsn::fromString($dsn)); } - private static function createTransport(string $dsn, EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null): TransportInterface + public function fromDsnObject(Dsn $dsn): TransportInterface { - if (false === $parsedDsn = parse_url($dsn)) { - throw new InvalidArgumentException(sprintf('The "%s" mailer DSN is invalid.', $dsn)); + foreach ($this->factories as $factory) { + if ($factory->supports($dsn)) { + return $factory->create($dsn); + } } - if (!isset($parsedDsn['scheme'])) { - throw new InvalidArgumentException(sprintf('The "%s" mailer DSN must contain a transport scheme.', $dsn)); + throw new UnsupportedHostException($dsn); + } + + /** + * @param string[] $dsns + * + * @return TransportInterface[] + */ + private function createFromDsns(array $dsns): array + { + $transports = []; + foreach ($dsns as $dsn) { + $transports[] = $this->fromDsnObject(Dsn::fromString($dsn)); } - if (!isset($parsedDsn['host'])) { - throw new InvalidArgumentException(sprintf('The "%s" mailer DSN must contain a mailer name.', $dsn)); + return $transports; + } + + private static function getDefaultFactories(EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null): iterable + { + foreach (self::FACTORY_CLASSES as $factoryClass) { + if (class_exists($factoryClass)) { + yield new $factoryClass($dispatcher, $client, $logger); + } } - $user = urldecode($parsedDsn['user'] ?? ''); - $pass = urldecode($parsedDsn['pass'] ?? ''); - parse_str($parsedDsn['query'] ?? '', $query); + yield new NullTransportFactory($dispatcher, $client, $logger); - switch ($parsedDsn['host']) { - case 'null': - if ('smtp' === $parsedDsn['scheme']) { - return new Transport\NullTransport($dispatcher, $logger); - } + yield new SendmailTransportFactory($dispatcher, $client, $logger); - throw new LogicException(sprintf('The "%s" scheme is not supported for mailer "%s".', $parsedDsn['scheme'], $parsedDsn['host'])); - case 'sendmail': - if ('smtp' === $parsedDsn['scheme']) { - return new Transport\SendmailTransport(null, $dispatcher, $logger); - } - - throw new LogicException(sprintf('The "%s" scheme is not supported for mailer "%s".', $parsedDsn['scheme'], $parsedDsn['host'])); - case 'gmail': - if (!class_exists(Google\Smtp\GmailTransport::class)) { - throw new \LogicException('Unable to send emails via Gmail as the Google bridge is not installed. Try running "composer require symfony/google-mailer".'); - } - - if ('smtp' === $parsedDsn['scheme']) { - return new Google\Smtp\GmailTransport($user, $pass, $dispatcher, $logger); - } - - throw new LogicException(sprintf('The "%s" scheme is not supported for mailer "%s".', $parsedDsn['scheme'], $parsedDsn['host'])); - case 'mailgun': - if (!class_exists(Mailgun\Smtp\MailgunTransport::class)) { - throw new \LogicException('Unable to send emails via Mailgun as the bridge is not installed. Try running "composer require symfony/mailgun-mailer".'); - } - - if ('smtp' === $parsedDsn['scheme']) { - return new Mailgun\Smtp\MailgunTransport($user, $pass, $query['region'] ?? null, $dispatcher, $logger); - } - if ('http' === $parsedDsn['scheme']) { - return new Mailgun\Http\MailgunTransport($user, $pass, $query['region'] ?? null, $client, $dispatcher, $logger); - } - if ('api' === $parsedDsn['scheme']) { - return new Mailgun\Http\Api\MailgunTransport($user, $pass, $query['region'] ?? null, $client, $dispatcher, $logger); - } - - throw new LogicException(sprintf('The "%s" scheme is not supported for mailer "%s".', $parsedDsn['scheme'], $parsedDsn['host'])); - case 'postmark': - if (!class_exists(Postmark\Smtp\PostmarkTransport::class)) { - throw new \LogicException('Unable to send emails via Postmark as the bridge is not installed. Try running "composer require symfony/postmark-mailer".'); - } - - if ('smtp' === $parsedDsn['scheme']) { - return new Postmark\Smtp\PostmarkTransport($user, $dispatcher, $logger); - } - if ('api' === $parsedDsn['scheme']) { - return new Postmark\Http\Api\PostmarkTransport($user, $client, $dispatcher, $logger); - } - - throw new LogicException(sprintf('The "%s" scheme is not supported for mailer "%s".', $parsedDsn['scheme'], $parsedDsn['host'])); - case 'sendgrid': - if (!class_exists(Sendgrid\Smtp\SendgridTransport::class)) { - throw new \LogicException('Unable to send emails via Sendgrid as the bridge is not installed. Try running "composer require symfony/sendgrid-mailer".'); - } - - if ('smtp' === $parsedDsn['scheme']) { - return new Sendgrid\Smtp\SendgridTransport($user, $dispatcher, $logger); - } - if ('api' === $parsedDsn['scheme']) { - return new Sendgrid\Http\Api\SendgridTransport($user, $client, $dispatcher, $logger); - } - - throw new LogicException(sprintf('The "%s" scheme is not supported for mailer "%s".', $parsedDsn['scheme'], $parsedDsn['host'])); - case 'ses': - if (!class_exists(Amazon\Smtp\SesTransport::class)) { - throw new \LogicException('Unable to send emails via Amazon SES as the bridge is not installed. Try running "composer require symfony/amazon-mailer".'); - } - - if ('smtp' === $parsedDsn['scheme']) { - return new Amazon\Smtp\SesTransport($user, $pass, $query['region'] ?? null, $dispatcher, $logger); - } - if ('api' === $parsedDsn['scheme']) { - return new Amazon\Http\Api\SesTransport($user, $pass, $query['region'] ?? null, $client, $dispatcher, $logger); - } - if ('http' === $parsedDsn['scheme']) { - return new Amazon\Http\SesTransport($user, $pass, $query['region'] ?? null, $client, $dispatcher, $logger); - } - - throw new LogicException(sprintf('The "%s" scheme is not supported for mailer "%s".', $parsedDsn['scheme'], $parsedDsn['host'])); - case 'mandrill': - if (!class_exists(Mailchimp\Smtp\MandrillTransport::class)) { - throw new \LogicException('Unable to send emails via Mandrill as the bridge is not installed. Try running "composer require symfony/mailchimp-mailer".'); - } - - if ('smtp' === $parsedDsn['scheme']) { - return new Mailchimp\Smtp\MandrillTransport($user, $pass, $dispatcher, $logger); - } - if ('api' === $parsedDsn['scheme']) { - return new Mailchimp\Http\Api\MandrillTransport($user, $client, $dispatcher, $logger); - } - if ('http' === $parsedDsn['scheme']) { - return new Mailchimp\Http\MandrillTransport($user, $client, $dispatcher, $logger); - } - - throw new LogicException(sprintf('The "%s" scheme is not supported for mailer "%s".', $parsedDsn['scheme'], $parsedDsn['host'])); - default: - if ('smtp' === $parsedDsn['scheme']) { - $transport = new Transport\Smtp\EsmtpTransport($parsedDsn['host'], $parsedDsn['port'] ?? 25, $query['encryption'] ?? null, $query['auth_mode'] ?? null, $dispatcher, $logger); - - if ($user) { - $transport->setUsername($user); - } - - if ($pass) { - $transport->setPassword($pass); - } - - return $transport; - } - - throw new LogicException(sprintf('The "%s" mailer is not supported.', $parsedDsn['host'])); - } + yield new EsmtpTransportFactory($dispatcher, $client, $logger); } } diff --git a/src/Symfony/Component/Mailer/Transport/AbstractTransportFactory.php b/src/Symfony/Component/Mailer/Transport/AbstractTransportFactory.php new file mode 100644 index 0000000000..959fca5746 --- /dev/null +++ b/src/Symfony/Component/Mailer/Transport/AbstractTransportFactory.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Mailer\Exception\IncompleteDsnException; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * @author Konstantin Myakshin + */ +abstract class AbstractTransportFactory implements TransportFactoryInterface +{ + protected $dispatcher; + protected $client; + protected $logger; + + public function __construct(EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null) + { + $this->dispatcher = $dispatcher; + $this->client = $client; + $this->logger = $logger; + } + + protected function getUser(Dsn $dsn): string + { + $user = $dsn->getUser(); + if (null === $user) { + throw new IncompleteDsnException('User is not set.'); + } + + return $user; + } + + protected function getPassword(Dsn $dsn): string + { + $password = $dsn->getPassword(); + if (null === $password) { + throw new IncompleteDsnException('Password is not set.'); + } + + return $password; + } +} diff --git a/src/Symfony/Component/Mailer/Transport/Dsn.php b/src/Symfony/Component/Mailer/Transport/Dsn.php new file mode 100644 index 0000000000..b5e2843ab4 --- /dev/null +++ b/src/Symfony/Component/Mailer/Transport/Dsn.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport; + +use Symfony\Component\Mailer\Exception\InvalidArgumentException; + +/** + * @author Konstantin Myakshin + */ +final class Dsn +{ + private $scheme; + private $host; + private $user; + private $password; + private $port; + private $options; + + public function __construct(string $scheme, string $host, ?string $user = null, ?string $password = null, ?int $port = null, array $options = []) + { + $this->scheme = $scheme; + $this->host = $host; + $this->user = $user; + $this->password = $password; + $this->port = $port; + $this->options = $options; + } + + public static function fromString(string $dsn): self + { + if (false === $parsedDsn = parse_url($dsn)) { + throw new InvalidArgumentException(sprintf('The "%s" mailer DSN is invalid.', $dsn)); + } + + if (!isset($parsedDsn['scheme'])) { + throw new InvalidArgumentException(sprintf('The "%s" mailer DSN must contain a transport scheme.', $dsn)); + } + + if (!isset($parsedDsn['host'])) { + throw new InvalidArgumentException(sprintf('The "%s" mailer DSN must contain a mailer name.', $dsn)); + } + + $user = urldecode($parsedDsn['user'] ?? null); + $password = urldecode($parsedDsn['pass'] ?? null); + $port = $parsedDsn['port'] ?? null; + parse_str($parsedDsn['query'] ?? '', $query); + + return new self($parsedDsn['scheme'], $parsedDsn['host'], $user, $password, $port, $query); + } + + public function getScheme(): string + { + return $this->scheme; + } + + public function getHost(): string + { + return $this->host; + } + + public function getUser(): ?string + { + return $this->user; + } + + public function getPassword(): ?string + { + return $this->password; + } + + public function getPort(int $default = null): ?int + { + return $this->port ?? $default; + } + + public function getOption(string $key, $default = null) + { + return $this->options[$key] ?? $default; + } +} diff --git a/src/Symfony/Component/Mailer/Transport/NullTransportFactory.php b/src/Symfony/Component/Mailer/Transport/NullTransportFactory.php new file mode 100644 index 0000000000..34600f7ec3 --- /dev/null +++ b/src/Symfony/Component/Mailer/Transport/NullTransportFactory.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport; + +use Symfony\Component\Mailer\Exception\UnsupportedSchemeException; + +/** + * @author Konstantin Myakshin + */ +final class NullTransportFactory extends AbstractTransportFactory +{ + public function create(Dsn $dsn): TransportInterface + { + if ('smtp' === $dsn->getScheme()) { + return new NullTransport($this->dispatcher, $this->logger); + } + + throw new UnsupportedSchemeException($dsn); + } + + public function supports(Dsn $dsn): bool + { + return 'null' === $dsn->getHost(); + } +} diff --git a/src/Symfony/Component/Mailer/Transport/SendmailTransportFactory.php b/src/Symfony/Component/Mailer/Transport/SendmailTransportFactory.php new file mode 100644 index 0000000000..99e7bbf097 --- /dev/null +++ b/src/Symfony/Component/Mailer/Transport/SendmailTransportFactory.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport; + +use Symfony\Component\Mailer\Exception\UnsupportedSchemeException; + +/** + * @author Konstantin Myakshin + */ +final class SendmailTransportFactory extends AbstractTransportFactory +{ + public function create(Dsn $dsn): TransportInterface + { + if ('smtp' === $dsn->getScheme()) { + return new SendmailTransport(null, $this->dispatcher, $this->logger); + } + + throw new UnsupportedSchemeException($dsn); + } + + public function supports(Dsn $dsn): bool + { + return 'sendmail' === $dsn->getHost(); + } +} diff --git a/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransportFactory.php b/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransportFactory.php new file mode 100644 index 0000000000..d1a5c60c5f --- /dev/null +++ b/src/Symfony/Component/Mailer/Transport/Smtp/EsmtpTransportFactory.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport\Smtp; + +use Symfony\Component\Mailer\Transport\AbstractTransportFactory; +use Symfony\Component\Mailer\Transport\Dsn; +use Symfony\Component\Mailer\Transport\TransportInterface; + +/** + * @author Konstantin Myakshin + */ +final class EsmtpTransportFactory extends AbstractTransportFactory +{ + public function create(Dsn $dsn): TransportInterface + { + $encryption = $dsn->getOption('encryption'); + $authMode = $dsn->getOption('auth_mode'); + $port = $dsn->getPort(25); + $host = $dsn->getHost(); + + $transport = new EsmtpTransport($host, $port, $encryption, $authMode, $this->dispatcher, $this->logger); + + if ($user = $dsn->getUser()) { + $transport->setUsername($user); + } + + if ($password = $dsn->getPassword()) { + $transport->setPassword($password); + } + + return $transport; + } + + public function supports(Dsn $dsn): bool + { + return 'smtp' === $dsn->getScheme(); + } +} diff --git a/src/Symfony/Component/Mailer/Transport/TransportFactoryInterface.php b/src/Symfony/Component/Mailer/Transport/TransportFactoryInterface.php new file mode 100644 index 0000000000..9785ae81a9 --- /dev/null +++ b/src/Symfony/Component/Mailer/Transport/TransportFactoryInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Transport; + +use Symfony\Component\Mailer\Exception\IncompleteDsnException; +use Symfony\Component\Mailer\Exception\UnsupportedSchemeException; + +/** + * @author Konstantin Myakshin + */ +interface TransportFactoryInterface +{ + /** + * @throws UnsupportedSchemeException + * @throws IncompleteDsnException + */ + public function create(Dsn $dsn): TransportInterface; + + public function supports(Dsn $dsn): bool; +} From ffb22ef08245be89b29d76e8509a4078dda63d72 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Wed, 17 Jul 2019 11:43:47 +0200 Subject: [PATCH 19/19] [WebProfilerBundle] Remove unneeded information in the routing panel --- .../Resources/views/Router/panel.html.twig | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Router/panel.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Router/panel.html.twig index ea8600a2d0..41636d1440 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Router/panel.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Router/panel.html.twig @@ -5,13 +5,6 @@ {{ request.route ?: '(none)' }} Matched route - - {% if request.route %} -
- {{ traces|length }} - Tested routes before match -
- {% endif %} {% if request.route %}