Transport.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Mailer;
  11. use Psr\EventDispatcher\EventDispatcherInterface;
  12. use Psr\Log\LoggerInterface;
  13. use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesTransportFactory;
  14. use Symfony\Component\Mailer\Bridge\Google\Transport\GmailTransportFactory;
  15. use Symfony\Component\Mailer\Bridge\Mailchimp\Transport\MandrillTransportFactory;
  16. use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunTransportFactory;
  17. use Symfony\Component\Mailer\Bridge\Mailjet\Transport\MailjetTransportFactory;
  18. use Symfony\Component\Mailer\Bridge\OhMySmtp\Transport\OhMySmtpTransportFactory;
  19. use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory;
  20. use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridTransportFactory;
  21. use Symfony\Component\Mailer\Bridge\Sendinblue\Transport\SendinblueTransportFactory;
  22. use Symfony\Component\Mailer\Exception\InvalidArgumentException;
  23. use Symfony\Component\Mailer\Exception\UnsupportedSchemeException;
  24. use Symfony\Component\Mailer\Transport\Dsn;
  25. use Symfony\Component\Mailer\Transport\FailoverTransport;
  26. use Symfony\Component\Mailer\Transport\NativeTransportFactory;
  27. use Symfony\Component\Mailer\Transport\NullTransportFactory;
  28. use Symfony\Component\Mailer\Transport\RoundRobinTransport;
  29. use Symfony\Component\Mailer\Transport\SendmailTransportFactory;
  30. use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransportFactory;
  31. use Symfony\Component\Mailer\Transport\TransportFactoryInterface;
  32. use Symfony\Component\Mailer\Transport\TransportInterface;
  33. use Symfony\Component\Mailer\Transport\Transports;
  34. use Symfony\Contracts\HttpClient\HttpClientInterface;
  35. /**
  36. * @author Fabien Potencier <fabien@symfony.com>
  37. * @author Konstantin Myakshin <molodchick@gmail.com>
  38. *
  39. * @final since Symfony 5.4
  40. */
  41. class Transport
  42. {
  43. private const FACTORY_CLASSES = [
  44. GmailTransportFactory::class,
  45. MailgunTransportFactory::class,
  46. MailjetTransportFactory::class,
  47. MandrillTransportFactory::class,
  48. OhMySmtpTransportFactory::class,
  49. PostmarkTransportFactory::class,
  50. SendgridTransportFactory::class,
  51. SendinblueTransportFactory::class,
  52. SesTransportFactory::class,
  53. ];
  54. private $factories;
  55. /**
  56. * @param EventDispatcherInterface|null $dispatcher
  57. * @param HttpClientInterface|null $client
  58. * @param LoggerInterface|null $logger
  59. */
  60. public static function fromDsn(string $dsn/* , EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null */): TransportInterface
  61. {
  62. $dispatcher = 2 <= \func_num_args() ? func_get_arg(1) : null;
  63. $client = 3 <= \func_num_args() ? func_get_arg(2) : null;
  64. $logger = 4 <= \func_num_args() ? func_get_arg(3) : null;
  65. $factory = new self(iterator_to_array(self::getDefaultFactories($dispatcher, $client, $logger)));
  66. return $factory->fromString($dsn);
  67. }
  68. /**
  69. * @param EventDispatcherInterface|null $dispatcher
  70. * @param HttpClientInterface|null $client
  71. * @param LoggerInterface|null $logger
  72. */
  73. public static function fromDsns(array $dsns/* , EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null */): TransportInterface
  74. {
  75. $dispatcher = 2 <= \func_num_args() ? func_get_arg(1) : null;
  76. $client = 3 <= \func_num_args() ? func_get_arg(2) : null;
  77. $logger = 4 <= \func_num_args() ? func_get_arg(3) : null;
  78. $factory = new self(iterator_to_array(self::getDefaultFactories($dispatcher, $client, $logger)));
  79. return $factory->fromStrings($dsns);
  80. }
  81. /**
  82. * @param TransportFactoryInterface[] $factories
  83. */
  84. public function __construct(iterable $factories)
  85. {
  86. $this->factories = $factories;
  87. }
  88. public function fromStrings(array $dsns): Transports
  89. {
  90. $transports = [];
  91. foreach ($dsns as $name => $dsn) {
  92. $transports[$name] = $this->fromString($dsn);
  93. }
  94. return new Transports($transports);
  95. }
  96. public function fromString(string $dsn): TransportInterface
  97. {
  98. [$transport, $offset] = $this->parseDsn($dsn);
  99. if ($offset !== \strlen($dsn)) {
  100. throw new InvalidArgumentException(sprintf('The DSN has some garbage at the end: "%s".', substr($dsn, $offset)));
  101. }
  102. return $transport;
  103. }
  104. private function parseDsn(string $dsn, int $offset = 0): array
  105. {
  106. static $keywords = [
  107. 'failover' => FailoverTransport::class,
  108. 'roundrobin' => RoundRobinTransport::class,
  109. ];
  110. while (true) {
  111. foreach ($keywords as $name => $class) {
  112. $name .= '(';
  113. if ($name === substr($dsn, $offset, \strlen($name))) {
  114. $offset += \strlen($name) - 1;
  115. preg_match('{\(([^()]|(?R))*\)}A', $dsn, $matches, 0, $offset);
  116. if (!isset($matches[0])) {
  117. continue;
  118. }
  119. ++$offset;
  120. $args = [];
  121. while (true) {
  122. [$arg, $offset] = $this->parseDsn($dsn, $offset);
  123. $args[] = $arg;
  124. if (\strlen($dsn) === $offset) {
  125. break;
  126. }
  127. ++$offset;
  128. if (')' === $dsn[$offset - 1]) {
  129. break;
  130. }
  131. }
  132. return [new $class($args), $offset];
  133. }
  134. }
  135. if (preg_match('{(\w+)\(}A', $dsn, $matches, 0, $offset)) {
  136. throw new InvalidArgumentException(sprintf('The "%s" keyword is not valid (valid ones are "%s"), ', $matches[1], implode('", "', array_keys($keywords))));
  137. }
  138. if ($pos = strcspn($dsn, ' )', $offset)) {
  139. return [$this->fromDsnObject(Dsn::fromString(substr($dsn, $offset, $pos))), $offset + $pos];
  140. }
  141. return [$this->fromDsnObject(Dsn::fromString(substr($dsn, $offset))), \strlen($dsn)];
  142. }
  143. }
  144. public function fromDsnObject(Dsn $dsn): TransportInterface
  145. {
  146. foreach ($this->factories as $factory) {
  147. if ($factory->supports($dsn)) {
  148. return $factory->create($dsn);
  149. }
  150. }
  151. throw new UnsupportedSchemeException($dsn);
  152. }
  153. /**
  154. * @param EventDispatcherInterface|null $dispatcher
  155. * @param HttpClientInterface|null $client
  156. * @param LoggerInterface|null $logger
  157. *
  158. * @return \Traversable<int, TransportFactoryInterface>
  159. */
  160. public static function getDefaultFactories(/* EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null */): iterable
  161. {
  162. $dispatcher = 1 <= \func_num_args() ? func_get_arg(0) : null;
  163. $client = 2 <= \func_num_args() ? func_get_arg(1) : null;
  164. $logger = 3 <= \func_num_args() ? func_get_arg(2) : null;
  165. foreach (self::FACTORY_CLASSES as $factoryClass) {
  166. if (class_exists($factoryClass)) {
  167. yield new $factoryClass($dispatcher, $client, $logger);
  168. }
  169. }
  170. yield new NullTransportFactory($dispatcher, $client, $logger);
  171. yield new SendmailTransportFactory($dispatcher, $client, $logger);
  172. yield new EsmtpTransportFactory($dispatcher, $client, $logger);
  173. yield new NativeTransportFactory($dispatcher, $client, $logger);
  174. }
  175. }