[Mailer] Change the syntax for DSNs using failover or roundrobin
This commit is contained in:
parent
b7371ea5c6
commit
39dd213960
@ -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)
|
||||
|
@ -36,7 +36,7 @@ class FailoverTransportTest extends TestCase
|
||||
$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()
|
||||
|
@ -35,7 +35,7 @@ class RoundRobinTransportTest extends TestCase
|
||||
$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()
|
||||
|
@ -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 @@ class TransportTest extends TestCase
|
||||
];
|
||||
|
||||
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")'];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ use Symfony\Component\Mailer\Bridge\Mailchimp\Transport\MandrillTransportFactory
|
||||
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 @@ class Transport
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return $this->fromDsnObject(Dsn::fromString($dsn));
|
||||
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(substr($dsn, $offset))), \strlen($dsn)];
|
||||
}
|
||||
}
|
||||
|
||||
public function fromDsnObject(Dsn $dsn): TransportInterface
|
||||
@ -106,21 +149,6 @@ class Transport
|
||||
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) {
|
||||
|
@ -31,6 +31,6 @@ class FailoverTransport extends RoundRobinTransport
|
||||
|
||||
protected function getNameSymbol(): string
|
||||
{
|
||||
return '||';
|
||||
return 'failover';
|
||||
}
|
||||
}
|
||||
|
@ -58,9 +58,9 @@ class RoundRobinTransport implements TransportInterface
|
||||
|
||||
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 @@ class RoundRobinTransport implements TransportInterface
|
||||
|
||||
protected function getNameSymbol(): string
|
||||
{
|
||||
return '&&';
|
||||
return 'roundrobin';
|
||||
}
|
||||
|
||||
private function moveCursor(int $cursor): int
|
||||
|
Reference in New Issue
Block a user