This commit is contained in:
2025-05-12 14:25:25 +02:00
parent ab2db755ef
commit 9e378ca2b7
2719 changed files with 46505 additions and 60181 deletions

View File

@@ -28,8 +28,6 @@ final class MailerTestCommand extends Command
{
public function __construct(private TransportInterface $transport)
{
$this->transport = $transport;
parent::__construct();
}

View File

@@ -29,7 +29,7 @@ final class MessageDataCollector extends DataCollector
$this->events = $logger->getEvents();
}
public function collect(Request $request, Response $response, \Throwable $exception = null): void
public function collect(Request $request, Response $response, ?\Throwable $exception = null): void
{
$this->data['events'] = $this->events;
}

View File

@@ -42,7 +42,7 @@ class MessageEvents
/**
* @return MessageEvent[]
*/
public function getEvents(string $name = null): array
public function getEvents(?string $name = null): array
{
if (null === $name) {
return $this->events;
@@ -61,7 +61,7 @@ class MessageEvents
/**
* @return RawMessage[]
*/
public function getMessages(string $name = null): array
public function getMessages(?string $name = null): array
{
$events = $this->getEvents($name);
$messages = [];

View File

@@ -33,7 +33,7 @@ class EnvelopeListener implements EventSubscriberInterface
/**
* @param array<Address|string> $recipients
*/
public function __construct(Address|string $sender = null, array $recipients = null)
public function __construct(Address|string|null $sender = null, ?array $recipients = null)
{
if (null !== $sender) {
$this->sender = Address::create($sender);

View File

@@ -43,7 +43,7 @@ class MessageListener implements EventSubscriberInterface
private array $headerRules = [];
private ?BodyRendererInterface $renderer;
public function __construct(Headers $headers = null, BodyRendererInterface $renderer = null, array $headerRules = self::DEFAULT_RULES)
public function __construct(?Headers $headers = null, ?BodyRendererInterface $renderer = null, array $headerRules = self::DEFAULT_RULES)
{
$this->headers = $headers;
$this->renderer = $renderer;

View File

@@ -20,7 +20,7 @@ class HttpTransportException extends TransportException
{
private ResponseInterface $response;
public function __construct(string $message, ResponseInterface $response, int $code = 0, \Throwable $previous = null)
public function __construct(string $message, ResponseInterface $response, int $code = 0, ?\Throwable $previous = null)
{
parent::__construct($message, $code, $previous);

View File

@@ -0,0 +1,16 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Mailer\Exception;
class UnexpectedResponseException extends TransportException
{
}

View File

@@ -78,7 +78,7 @@ class UnsupportedSchemeException extends LogicException
],
];
public function __construct(Dsn $dsn, string $name = null, array $supported = [])
public function __construct(Dsn $dsn, ?string $name = null, array $supported = [])
{
$provider = $dsn->getScheme();
if (false !== $pos = strpos($provider, '+')) {

View File

@@ -29,14 +29,14 @@ final class Mailer implements MailerInterface
private ?MessageBusInterface $bus;
private ?EventDispatcherInterface $dispatcher;
public function __construct(TransportInterface $transport, MessageBusInterface $bus = null, EventDispatcherInterface $dispatcher = null)
public function __construct(TransportInterface $transport, ?MessageBusInterface $bus = null, ?EventDispatcherInterface $dispatcher = null)
{
$this->transport = $transport;
$this->bus = $bus;
$this->dispatcher = $dispatcher;
}
public function send(RawMessage $message, Envelope $envelope = null): void
public function send(RawMessage $message, ?Envelope $envelope = null): void
{
if (null === $this->bus) {
$this->transport->send($message, $envelope);

View File

@@ -15,7 +15,7 @@ use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mime\RawMessage;
/**
* Interface for mailers able to send emails synchronous and/or asynchronous.
* Interface for mailers able to send emails synchronously and/or asynchronously.
*
* Implementations must support synchronous and asynchronous sending.
*
@@ -26,5 +26,5 @@ interface MailerInterface
/**
* @throws TransportExceptionInterface
*/
public function send(RawMessage $message, Envelope $envelope = null): void;
public function send(RawMessage $message, ?Envelope $envelope = null): void;
}

View File

@@ -22,7 +22,7 @@ class SendEmailMessage
private RawMessage $message;
private ?Envelope $envelope;
public function __construct(RawMessage $message, Envelope $envelope = null)
public function __construct(RawMessage $message, ?Envelope $envelope = null)
{
$this->message = $message;
$this->envelope = $envelope;

View File

@@ -20,7 +20,7 @@ final class EmailCount extends Constraint
private ?string $transport;
private bool $queued;
public function __construct(int $expectedValue, string $transport = null, bool $queued = false)
public function __construct(int $expectedValue, ?string $transport = null, bool $queued = false)
{
$this->expectedValue = $expectedValue;
$this->transport = $transport;

View File

@@ -77,7 +77,7 @@ abstract class TransportFactoryTestCase extends TestCase
/**
* @dataProvider unsupportedSchemeProvider
*/
public function testUnsupportedSchemeException(Dsn $dsn, string $message = null)
public function testUnsupportedSchemeException(Dsn $dsn, ?string $message = null)
{
$factory = $this->getFactory();

View File

@@ -21,6 +21,7 @@ use Symfony\Component\Mailer\Bridge\Mailchimp\Transport\MandrillTransportFactory
use Symfony\Component\Mailer\Bridge\MailerSend\Transport\MailerSendTransportFactory;
use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunTransportFactory;
use Symfony\Component\Mailer\Bridge\Mailjet\Transport\MailjetTransportFactory;
use Symfony\Component\Mailer\Bridge\MailPace\Transport\MailPaceTransportFactory;
use Symfony\Component\Mailer\Bridge\OhMySmtp\Transport\OhMySmtpTransportFactory;
use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory;
use Symfony\Component\Mailer\Bridge\Scaleway\Transport\ScalewayTransportFactory;
@@ -53,6 +54,7 @@ final class Transport
MailerSendTransportFactory::class,
MailgunTransportFactory::class,
MailjetTransportFactory::class,
MailPaceTransportFactory::class,
MandrillTransportFactory::class,
OhMySmtpTransportFactory::class,
PostmarkTransportFactory::class,
@@ -64,14 +66,14 @@ final class Transport
private iterable $factories;
public static function fromDsn(#[\SensitiveParameter] string $dsn, EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null): TransportInterface
public static function fromDsn(#[\SensitiveParameter] string $dsn, ?EventDispatcherInterface $dispatcher = null, ?HttpClientInterface $client = null, ?LoggerInterface $logger = null): TransportInterface
{
$factory = new self(iterator_to_array(self::getDefaultFactories($dispatcher, $client, $logger)));
return $factory->fromString($dsn);
}
public static function fromDsns(#[\SensitiveParameter] array $dsns, EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null): TransportInterface
public static function fromDsns(#[\SensitiveParameter] array $dsns, ?EventDispatcherInterface $dispatcher = null, ?HttpClientInterface $client = null, ?LoggerInterface $logger = null): TransportInterface
{
$factory = new self(iterator_to_array(self::getDefaultFactories($dispatcher, $client, $logger)));
@@ -167,7 +169,7 @@ final class Transport
/**
* @return \Traversable<int, TransportFactoryInterface>
*/
public static function getDefaultFactories(EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null): \Traversable
public static function getDefaultFactories(?EventDispatcherInterface $dispatcher = null, ?HttpClientInterface $client = null, ?LoggerInterface $logger = null): \Traversable
{
foreach (self::FACTORY_CLASSES as $factoryClass) {
if (class_exists($factoryClass)) {

View File

@@ -28,7 +28,7 @@ abstract class AbstractHttpTransport extends AbstractTransport
protected $port;
protected $client;
public function __construct(HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null)
public function __construct(?HttpClientInterface $client = null, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null)
{
$this->client = $client;
if (null === $client) {

View File

@@ -35,7 +35,7 @@ abstract class AbstractTransport implements TransportInterface
private float $rate = 0;
private float $lastSent = 0;
public function __construct(EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null)
public function __construct(?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null)
{
$this->dispatcher = $dispatcher;
$this->logger = $logger ?? new NullLogger();
@@ -58,7 +58,7 @@ abstract class AbstractTransport implements TransportInterface
return $this;
}
public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage
public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage
{
$message = clone $message;
$envelope = null !== $envelope ? clone $envelope : Envelope::create($message);

View File

@@ -25,7 +25,7 @@ abstract class AbstractTransportFactory implements TransportFactoryInterface
protected $client;
protected $logger;
public function __construct(EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null)
public function __construct(?EventDispatcherInterface $dispatcher = null, ?HttpClientInterface $client = null, ?LoggerInterface $logger = null)
{
$this->dispatcher = $dispatcher;
$this->client = $client;

View File

@@ -25,7 +25,7 @@ final class Dsn
private ?int $port;
private array $options;
public function __construct(string $scheme, string $host, string $user = null, #[\SensitiveParameter] string $password = null, int $port = null, array $options = [])
public function __construct(string $scheme, string $host, ?string $user = null, #[\SensitiveParameter] ?string $password = null, ?int $port = null, array $options = [])
{
$this->scheme = $scheme;
$this->host = $host;
@@ -37,24 +37,24 @@ final class Dsn
public static function fromString(#[\SensitiveParameter] string $dsn): self
{
if (false === $parsedDsn = parse_url($dsn)) {
if (false === $params = parse_url($dsn)) {
throw new InvalidArgumentException('The mailer DSN is invalid.');
}
if (!isset($parsedDsn['scheme'])) {
if (!isset($params['scheme'])) {
throw new InvalidArgumentException('The mailer DSN must contain a scheme.');
}
if (!isset($parsedDsn['host'])) {
if (!isset($params['host'])) {
throw new InvalidArgumentException('The mailer DSN must contain a host (use "default" by default).');
}
$user = '' !== ($parsedDsn['user'] ?? '') ? urldecode($parsedDsn['user']) : null;
$password = '' !== ($parsedDsn['pass'] ?? '') ? urldecode($parsedDsn['pass']) : null;
$port = $parsedDsn['port'] ?? null;
parse_str($parsedDsn['query'] ?? '', $query);
$user = '' !== ($params['user'] ?? '') ? rawurldecode($params['user']) : null;
$password = '' !== ($params['pass'] ?? '') ? rawurldecode($params['pass']) : null;
$port = $params['port'] ?? null;
parse_str($params['query'] ?? '', $query);
return new self($parsedDsn['scheme'], $parsedDsn['host'], $user, $password, $port, $query);
return new self($params['scheme'], $params['host'], $user, $password, $port, $query);
}
public function getScheme(): string
@@ -77,7 +77,7 @@ final class Dsn
return $this->password;
}
public function getPort(int $default = null): ?int
public function getPort(?int $default = null): ?int
{
return $this->port ?? $default;
}

View File

@@ -46,7 +46,7 @@ class RoundRobinTransport implements TransportInterface
$this->retryPeriod = $retryPeriod;
}
public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage
public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage
{
$exception = null;

View File

@@ -49,7 +49,7 @@ class SendmailTransport extends AbstractTransport
*
* -f<sender> flag will be appended automatically if one is not present.
*/
public function __construct(string $command = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null)
public function __construct(?string $command = null, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null)
{
parent::__construct($dispatcher, $logger);
@@ -64,11 +64,12 @@ class SendmailTransport extends AbstractTransport
$this->stream = new ProcessStream();
if (str_contains($this->command, ' -bs')) {
$this->stream->setCommand($this->command);
$this->stream->setInteractive(true);
$this->transport = new SmtpTransport($this->stream, $dispatcher, $logger);
}
}
public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage
public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage
{
if ($this->transport) {
return $this->transport->send($message, $envelope);
@@ -113,7 +114,7 @@ class SendmailTransport extends AbstractTransport
$this->stream->setCommand($command);
$this->stream->initialize();
foreach ($chunks as $chunk) {
$this->stream->write($chunk);
$this->stream->write($chunk, false);
}
$this->stream->flush();
$this->stream->terminate();

View File

@@ -15,6 +15,7 @@ use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Mailer\Exception\TransportException;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mailer\Exception\UnexpectedResponseException;
use Symfony\Component\Mailer\Transport\Smtp\Auth\AuthenticatorInterface;
use Symfony\Component\Mailer\Transport\Smtp\Stream\AbstractStream;
use Symfony\Component\Mailer\Transport\Smtp\Stream\SocketStream;
@@ -32,7 +33,7 @@ class EsmtpTransport extends SmtpTransport
private string $password = '';
private array $capabilities;
public function __construct(string $host = 'localhost', int $port = 0, bool $tls = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null, AbstractStream $stream = null, array $authenticators = null)
public function __construct(string $host = 'localhost', int $port = 0, ?bool $tls = null, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null, ?AbstractStream $stream = null, ?array $authenticators = null)
{
parent::__construct($stream, $dispatcher, $logger);
@@ -199,7 +200,7 @@ class EsmtpTransport extends SmtpTransport
$authenticator->authenticate($this);
return;
} catch (TransportExceptionInterface $e) {
} catch (UnexpectedResponseException $e) {
$code = $e->getCode();
try {

View File

@@ -17,6 +17,7 @@ use Symfony\Component\Mailer\Envelope;
use Symfony\Component\Mailer\Exception\LogicException;
use Symfony\Component\Mailer\Exception\TransportException;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mailer\Exception\UnexpectedResponseException;
use Symfony\Component\Mailer\SentMessage;
use Symfony\Component\Mailer\Transport\AbstractTransport;
use Symfony\Component\Mailer\Transport\Smtp\Stream\AbstractStream;
@@ -38,10 +39,9 @@ class SmtpTransport extends AbstractTransport
private int $pingThreshold = 100;
private float $lastMessageTime = 0;
private AbstractStream $stream;
private string $mtaResult = '';
private string $domain = '[127.0.0.1]';
public function __construct(AbstractStream $stream = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null)
public function __construct(?AbstractStream $stream = null, ?EventDispatcherInterface $dispatcher = null, ?LoggerInterface $logger = null)
{
parent::__construct($dispatcher, $logger);
@@ -131,7 +131,7 @@ class SmtpTransport extends AbstractTransport
return $this->domain;
}
public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage
public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage
{
try {
$message = parent::send($message, $envelope);
@@ -147,10 +147,6 @@ class SmtpTransport extends AbstractTransport
throw $e;
}
if ($this->mtaResult && $messageId = $this->parseMessageId($this->mtaResult)) {
$message->setMessageId($messageId);
}
$this->checkRestartThreshold();
return $message;
@@ -209,11 +205,11 @@ class SmtpTransport extends AbstractTransport
$this->ping();
}
if (!$this->started) {
$this->start();
}
try {
if (!$this->started) {
$this->start();
}
$envelope = $message->getEnvelope();
$this->doMailFromCommand($envelope->getSender()->getEncodedAddress());
foreach ($envelope->getRecipients() as $recipient) {
@@ -234,9 +230,13 @@ class SmtpTransport extends AbstractTransport
$this->getLogger()->debug(sprintf('Email transport "%s" stopped', __CLASS__));
throw $e;
}
$this->mtaResult = $this->executeCommand("\r\n.\r\n", [250]);
$mtaResult = $this->executeCommand("\r\n.\r\n", [250]);
$message->appendDebug($this->stream->getDebug());
$this->lastMessageTime = microtime(true);
if ($mtaResult && $messageId = $this->parseMessageId($mtaResult)) {
$message->setMessageId($messageId);
}
} catch (TransportExceptionInterface $e) {
$e->appendDebug($this->stream->getDebug());
$this->lastMessageTime = 0;
@@ -335,7 +335,7 @@ class SmtpTransport extends AbstractTransport
$codeStr = $code ? sprintf('code "%s"', $code) : 'empty code';
$responseStr = $response ? sprintf(', with message "%s"', trim($response)) : '';
throw new TransportException(sprintf('Expected response code "%s" but got ', implode('/', $codes)).$codeStr.$responseStr.'.', $code ?: 0);
throw new UnexpectedResponseException(sprintf('Expected response code "%s" but got ', implode('/', $codes)).$codeStr.$responseStr.'.', $code ?: 0);
}
}

View File

@@ -30,6 +30,7 @@ abstract class AbstractStream
protected $in;
/** @var resource|null */
protected $out;
protected $err;
private string $debug = '';
@@ -68,7 +69,7 @@ abstract class AbstractStream
public function terminate(): void
{
$this->stream = $this->out = $this->in = null;
$this->stream = $this->err = $this->out = $this->in = null;
}
public function readLine(): string
@@ -77,15 +78,17 @@ abstract class AbstractStream
return '';
}
$line = fgets($this->out);
$line = @fgets($this->out);
if ('' === $line || false === $line) {
$metas = stream_get_meta_data($this->out);
if ($metas['timed_out']) {
if (stream_get_meta_data($this->out)['timed_out']) {
throw new TransportException(sprintf('Connection to "%s" timed out.', $this->getReadConnectionDescription()));
}
if ($metas['eof']) {
if (feof($this->out)) { // don't use "eof" metadata, it's not accurate on Windows
throw new TransportException(sprintf('Connection to "%s" has been closed unexpectedly.', $this->getReadConnectionDescription()));
}
if (false === $line) {
throw new TransportException(sprintf('Unable to read from connection to "%s": ', $this->getReadConnectionDescription().error_get_last()['message'] ?? ''));
}
}
$this->debug .= sprintf('< %s', $line);

View File

@@ -24,12 +24,18 @@ use Symfony\Component\Mailer\Exception\TransportException;
final class ProcessStream extends AbstractStream
{
private string $command;
private bool $interactive = false;
public function setCommand(string $command): void
{
$this->command = $command;
}
public function setInteractive(bool $interactive): void
{
$this->interactive = $interactive;
}
public function initialize(): void
{
$descriptorSpec = [
@@ -45,17 +51,27 @@ final class ProcessStream extends AbstractStream
}
$this->in = &$pipes[0];
$this->out = &$pipes[1];
$this->err = &$pipes[2];
}
public function terminate(): void
{
if (null !== $this->stream) {
fclose($this->in);
$out = stream_get_contents($this->out);
fclose($this->out);
proc_close($this->stream);
$err = stream_get_contents($this->err);
fclose($this->err);
if (0 !== $exitCode = proc_close($this->stream)) {
$errorMessage = 'Process failed with exit code '.$exitCode.': '.$out.$err;
}
}
parent::terminate();
if (!$this->interactive && isset($errorMessage)) {
throw new TransportException($errorMessage);
}
}
protected function getReadConnectionDescription(): string

View File

@@ -160,7 +160,7 @@ final class SocketStream extends AbstractStream
}
stream_set_blocking($this->stream, true);
stream_set_timeout($this->stream, $timeout);
stream_set_timeout($this->stream, (int) $timeout, (int) (($timeout - (int) $timeout) * 1000000));
$this->in = &$this->stream;
$this->out = &$this->stream;
}

View File

@@ -29,5 +29,5 @@ interface TransportInterface extends \Stringable
/**
* @throws TransportExceptionInterface
*/
public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage;
public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage;
}

View File

@@ -44,7 +44,7 @@ final class Transports implements TransportInterface
}
}
public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage
public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage
{
/** @var Message $message */
if (RawMessage::class === $message::class || !$message->getHeaders()->has('X-Transport')) {