diff --git a/src/Symfony/Component/Mailer/CHANGELOG.md b/src/Symfony/Component/Mailer/CHANGELOG.md index 509e1b657c9ae..3abe095c0621c 100644 --- a/src/Symfony/Component/Mailer/CHANGELOG.md +++ b/src/Symfony/Component/Mailer/CHANGELOG.md @@ -4,6 +4,18 @@ CHANGELOG 4.4.0 ----- + * [BC BREAK] changed the syntax for failover and roundrobin DSNs + + Before: + + dummy://a || dummy://b (for failover) + dummy://a && dummy://b (for roundrobin) + + After: + + failover(dummy://a dummy://b) + roundrobin(dummy://a dummy://b) + * added support for multiple transports on a `Mailer` instance * [BC BREAK] removed the `auth_mode` DSN option (it is now always determined automatically) * STARTTLS cannot be enabled anymore (it is used automatically if TLS is disabled and the server supports STARTTLS) diff --git a/src/Symfony/Component/Mailer/Tests/Transport/FailoverTransportTest.php b/src/Symfony/Component/Mailer/Tests/Transport/FailoverTransportTest.php index 2fb1b422f31d5..7e66309cabb41 100644 --- a/src/Symfony/Component/Mailer/Tests/Transport/FailoverTransportTest.php +++ b/src/Symfony/Component/Mailer/Tests/Transport/FailoverTransportTest.php @@ -36,7 +36,7 @@ public function testToString() $t2 = $this->createMock(TransportInterface::class); $t2->expects($this->once())->method('__toString')->willReturn('t2://local'); $t = new FailoverTransport([$t1, $t2]); - $this->assertEquals('t1://local || t2://local', (string) $t); + $this->assertEquals('failover(t1://local t2://local)', (string) $t); } public function testSendFirstWork() diff --git a/src/Symfony/Component/Mailer/Tests/Transport/RoundRobinTransportTest.php b/src/Symfony/Component/Mailer/Tests/Transport/RoundRobinTransportTest.php index a703eb36bf15d..add578b233744 100644 --- a/src/Symfony/Component/Mailer/Tests/Transport/RoundRobinTransportTest.php +++ b/src/Symfony/Component/Mailer/Tests/Transport/RoundRobinTransportTest.php @@ -35,7 +35,7 @@ public function testToString() $t2 = $this->createMock(TransportInterface::class); $t2->expects($this->once())->method('__toString')->willReturn('t2://local'); $t = new RoundRobinTransport([$t1, $t2]); - $this->assertEquals('t1://local && t2://local', (string) $t); + $this->assertEquals('roundrobin(t1://local t2://local)', (string) $t); } public function testSendAlternate() diff --git a/src/Symfony/Component/Mailer/Tests/TransportTest.php b/src/Symfony/Component/Mailer/Tests/TransportTest.php index 47b352c7dd143..c82c83c6f9b38 100644 --- a/src/Symfony/Component/Mailer/Tests/TransportTest.php +++ b/src/Symfony/Component/Mailer/Tests/TransportTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Mailer\Tests; use PHPUnit\Framework\TestCase; +use Symfony\Component\Mailer\Exception\InvalidArgumentException; use Symfony\Component\Mailer\SentMessage; use Symfony\Component\Mailer\SmtpEnvelope; use Symfony\Component\Mailer\Transport; @@ -44,14 +45,42 @@ public function fromStringProvider(): iterable ]; yield 'failover transport' => [ - 'dummy://a || dummy://b', + 'failover(dummy://a dummy://b)', new FailoverTransport([$transportA, $transportB]), ]; yield 'round robin transport' => [ - 'dummy://a && dummy://b', + 'roundrobin(dummy://a dummy://b)', new RoundRobinTransport([$transportA, $transportB]), ]; + + yield 'mixed transport' => [ + 'roundrobin(dummy://a failover(dummy://b dummy://a) dummy://b)', + new RoundRobinTransport([$transportA, new FailoverTransport([$transportB, $transportA]), $transportB]), + ]; + } + + /** + * @dataProvider fromWrongStringProvider + */ + public function testFromWrongString(string $dsn, string $error): void + { + $transportFactory = new Transport([new DummyTransportFactory()]); + + $this->expectExceptionMessage($error); + $this->expectException(InvalidArgumentException::class); + $transportFactory->fromString($dsn); + } + + public function fromWrongStringProvider(): iterable + { + yield 'garbage at the end' => ['dummy://a some garbage here', 'The DSN has some garbage at the end: some garbage here.']; + + yield 'not a valid DSN' => ['something not a dsn', 'The "something" mailer DSN must contain a scheme.']; + + yield 'failover not closed' => ['failover(dummy://a', 'The "(dummy://a" mailer DSN must contain a scheme.']; + + yield 'not a valid keyword' => ['foobar(dummy://a)', 'The "foobar" keyword is not valid (valid ones are "failover", "roundrobin")']; } } diff --git a/src/Symfony/Component/Mailer/Transport.php b/src/Symfony/Component/Mailer/Transport.php index 3949fa78c508e..d3c4e58f6a3ff 100644 --- a/src/Symfony/Component/Mailer/Transport.php +++ b/src/Symfony/Component/Mailer/Transport.php @@ -18,6 +18,7 @@ use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunTransportFactory; use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory; use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridTransportFactory; +use Symfony\Component\Mailer\Exception\InvalidArgumentException; use Symfony\Component\Mailer\Exception\UnsupportedHostException; use Symfony\Component\Mailer\Transport\Dsn; use Symfony\Component\Mailer\Transport\FailoverTransport; @@ -82,17 +83,59 @@ public function fromStrings(array $dsns): Transports public function fromString(string $dsn): TransportInterface { - $dsns = preg_split('/\s++\|\|\s++/', $dsn); - if (\count($dsns) > 1) { - return new FailoverTransport($this->createFromDsns($dsns)); + list($transport, $offset) = $this->parseDsn($dsn); + if ($offset !== \strlen($dsn)) { + throw new InvalidArgumentException(sprintf('The DSN has some garbage at the end: %s.', substr($dsn, $offset))); } - $dsns = preg_split('/\s++&&\s++/', $dsn); - if (\count($dsns) > 1) { - return new RoundRobinTransport($this->createFromDsns($dsns)); - } + return $transport; + } + + private function parseDsn(string $dsn, int $offset = 0): array + { + static $keywords = [ + 'failover' => FailoverTransport::class, + 'roundrobin' => RoundRobinTransport::class, + ]; + + while (true) { + foreach ($keywords as $name => $class) { + $name .= '('; + if ($name === substr($dsn, $offset, \strlen($name))) { + $offset += \strlen($name) - 1; + preg_match('{\(([^()]|(?R))*\)}A', $dsn, $matches, 0, $offset); + if (!isset($matches[0])) { + continue; + } + + ++$offset; + $args = []; + while (true) { + list($arg, $offset) = $this->parseDsn($dsn, $offset); + $args[] = $arg; + if (\strlen($dsn) === $offset) { + break; + } + ++$offset; + if (')' === $dsn[$offset - 1]) { + break; + } + } + + return [new $class($args), $offset]; + } + } + + if (preg_match('{(\w+)\(}A', $dsn, $matches, 0, $offset)) { + throw new InvalidArgumentException(sprintf('The "%s" keyword is not valid (valid ones are "%s"), ', $matches[1], implode('", "', array_keys($keywords)))); + } + + if ($pos = strcspn($dsn, ' )', $offset)) { + return [$this->fromDsnObject(Dsn::fromString(substr($dsn, $offset, $pos))), $offset + $pos]; + } - return $this->fromDsnObject(Dsn::fromString($dsn)); + return [$this->fromDsnObject(Dsn::fromString(substr($dsn, $offset))), \strlen($dsn)]; + } } public function fromDsnObject(Dsn $dsn): TransportInterface @@ -106,21 +149,6 @@ public function fromDsnObject(Dsn $dsn): TransportInterface 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)); - } - - return $transports; - } - private static function getDefaultFactories(EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null): iterable { foreach (self::FACTORY_CLASSES as $factoryClass) { diff --git a/src/Symfony/Component/Mailer/Transport/FailoverTransport.php b/src/Symfony/Component/Mailer/Transport/FailoverTransport.php index 8722aa4be0a26..7d8f54c011f9c 100644 --- a/src/Symfony/Component/Mailer/Transport/FailoverTransport.php +++ b/src/Symfony/Component/Mailer/Transport/FailoverTransport.php @@ -31,6 +31,6 @@ protected function getNextTransport(): ?TransportInterface protected function getNameSymbol(): string { - return '||'; + return 'failover'; } } diff --git a/src/Symfony/Component/Mailer/Transport/RoundRobinTransport.php b/src/Symfony/Component/Mailer/Transport/RoundRobinTransport.php index f2029fb4338b0..9180817d0e28e 100644 --- a/src/Symfony/Component/Mailer/Transport/RoundRobinTransport.php +++ b/src/Symfony/Component/Mailer/Transport/RoundRobinTransport.php @@ -58,9 +58,9 @@ public function send(RawMessage $message, SmtpEnvelope $envelope = null): ?SentM public function __toString(): string { - return implode(' '.$this->getNameSymbol().' ', array_map(function (TransportInterface $transport) { + return $this->getNameSymbol().'('.implode(' ', array_map(function (TransportInterface $transport) { return (string) $transport; - }, $this->transports)); + }, $this->transports)).')'; } /** @@ -99,7 +99,7 @@ protected function isTransportDead(TransportInterface $transport): bool protected function getNameSymbol(): string { - return '&&'; + return 'roundrobin'; } private function moveCursor(int $cursor): int pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy