diff --git a/.travis.yml b/.travis.yml index c8f35b41b2..ab6c2d09b8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -185,7 +185,7 @@ install: - | # Install the phpunit-bridge from a PR if required # - # To run a PR with a patched phpunit-bridge, first submit the path for the + # To run a PR with a patched phpunit-bridge, first submit the patch for the # phpunit-bridge as a separate PR against the next feature-branch then # uncomment and update the following line with that PR number #SYMFONY_PHPUNIT_BRIDGE_PR=32886 diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/PolyfillAssertTrait.php b/src/Symfony/Bridge/PhpUnit/Legacy/PolyfillAssertTrait.php index 5d564774a7..69a01d2927 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/PolyfillAssertTrait.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/PolyfillAssertTrait.php @@ -17,7 +17,7 @@ use PHPUnit\Framework\Constraint\StringContains; use PHPUnit\Framework\Constraint\TraversableContains; /** - * This trait is @internal + * This trait is @internal. */ trait PolyfillAssertTrait { @@ -251,4 +251,196 @@ trait PolyfillAssertTrait static::assertInternalType('float', $actual, $message); static::assertTrue(is_nan($actual), $message ? $message : "Failed asserting that $actual is nan."); } + + /** + * @param string $filename + * @param string $message + * + * @return void + */ + public static function assertIsReadable($filename, $message = '') + { + static::assertInternalType('string', $filename, $message); + static::assertTrue(is_readable($filename), $message ? $message : "Failed asserting that $filename is readable."); + } + + /** + * @param string $filename + * @param string $message + * + * @return void + */ + public static function assertNotIsReadable($filename, $message = '') + { + static::assertInternalType('string', $filename, $message); + static::assertFalse(is_readable($filename), $message ? $message : "Failed asserting that $filename is not readable."); + } + + /** + * @param string $filename + * @param string $message + * + * @return void + */ + public static function assertIsWritable($filename, $message = '') + { + static::assertInternalType('string', $filename, $message); + static::assertTrue(is_writable($filename), $message ? $message : "Failed asserting that $filename is writable."); + } + + /** + * @param string $filename + * @param string $message + * + * @return void + */ + public static function assertNotIsWritable($filename, $message = '') + { + static::assertInternalType('string', $filename, $message); + static::assertFalse(is_writable($filename), $message ? $message : "Failed asserting that $filename is not writable."); + } + + /** + * @param string $directory + * @param string $message + * + * @return void + */ + public static function assertDirectoryExists($directory, $message = '') + { + static::assertInternalType('string', $directory, $message); + static::assertTrue(is_dir($directory), $message ? $message : "Failed asserting that $directory exists."); + } + + /** + * @param string $directory + * @param string $message + * + * @return void + */ + public static function assertDirectoryNotExists($directory, $message = '') + { + static::assertInternalType('string', $directory, $message); + static::assertFalse(is_dir($directory), $message ? $message : "Failed asserting that $directory does not exist."); + } + + /** + * @param string $directory + * @param string $message + * + * @return void + */ + public static function assertDirectoryIsReadable($directory, $message = '') + { + static::assertDirectoryExists($directory, $message); + static::assertIsReadable($directory, $message); + } + + /** + * @param string $directory + * @param string $message + * + * @return void + */ + public static function assertDirectoryNotIsReadable($directory, $message = '') + { + static::assertDirectoryExists($directory, $message); + static::assertNotIsReadable($directory, $message); + } + + /** + * @param string $directory + * @param string $message + * + * @return void + */ + public static function assertDirectoryIsWritable($directory, $message = '') + { + static::assertDirectoryExists($directory, $message); + static::assertIsWritable($directory, $message); + } + + /** + * @param string $directory + * @param string $message + * + * @return void + */ + public static function assertDirectoryNotIsWritable($directory, $message = '') + { + static::assertDirectoryExists($directory, $message); + static::assertNotIsWritable($directory, $message); + } + + /** + * @param string $filename + * @param string $message + * + * @return void + */ + public static function assertFileExists($filename, $message = '') + { + static::assertInternalType('string', $filename, $message); + static::assertTrue(file_exists($filename), $message ? $message : "Failed asserting that $filename exists."); + } + + /** + * @param string $filename + * @param string $message + * + * @return void + */ + public static function assertFileNotExists($filename, $message = '') + { + static::assertInternalType('string', $filename, $message); + static::assertFalse(file_exists($filename), $message ? $message : "Failed asserting that $filename does not exist."); + } + + /** + * @param string $filename + * @param string $message + * + * @return void + */ + public static function assertFileIsReadable($filename, $message = '') + { + static::assertFileExists($filename, $message); + static::assertIsReadable($filename, $message); + } + + /** + * @param string $filename + * @param string $message + * + * @return void + */ + public static function assertFileNotIsReadable($filename, $message = '') + { + static::assertFileExists($filename, $message); + static::assertNotIsReadable($filename, $message); + } + + /** + * @param string $filename + * @param string $message + * + * @return void + */ + public static function assertFileIsWritable($filename, $message = '') + { + static::assertFileExists($filename, $message); + static::assertIsWritable($filename, $message); + } + + /** + * @param string $filename + * @param string $message + * + * @return void + */ + public static function assertFileNotIsWritable($filename, $message = '') + { + static::assertFileExists($filename, $message); + static::assertNotIsWritable($filename, $message); + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 19721fb871..196d7d6c7e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -528,6 +528,10 @@ class FrameworkExtension extends Extension $loader->load('messenger_debug.xml'); } + if (class_exists(Mailer::class)) { + $loader->load('mailer_debug.xml'); + } + $container->setParameter('profiler_listener.only_exceptions', $config['only_exceptions']); $container->setParameter('profiler_listener.only_master_requests', $config['only_master_requests']); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_debug.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_debug.xml new file mode 100644 index 0000000000..17ed496661 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_debug.xml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/src/Symfony/Component/Mailer/CHANGELOG.md b/src/Symfony/Component/Mailer/CHANGELOG.md index 6a0669faca..76b895a680 100644 --- a/src/Symfony/Component/Mailer/CHANGELOG.md +++ b/src/Symfony/Component/Mailer/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 4.4.0 ----- + * Added `MessageEvents` and `MessageLoggerListener` to allow collecting sent emails * [BC BREAK] `TransportInterface` has a new `getName()` method * [BC BREAK] Classes `AbstractApiTransport` and `AbstractHttpTransport` moved under `Transport` sub-namespace. * [BC BREAK] Transports depend on `Symfony\Contracts\EventDispatcher\EventDispatcherInterface` diff --git a/src/Symfony/Component/Mailer/Event/MessageEvent.php b/src/Symfony/Component/Mailer/Event/MessageEvent.php index 41c31d442a..84be56d999 100644 --- a/src/Symfony/Component/Mailer/Event/MessageEvent.php +++ b/src/Symfony/Component/Mailer/Event/MessageEvent.php @@ -16,7 +16,7 @@ use Symfony\Component\Mime\RawMessage; use Symfony\Contracts\EventDispatcher\Event; /** - * Allows the transformation of a Message. + * Allows the transformation of a Message and the SMTP Envelope before the email is sent. * * @author Fabien Potencier */ @@ -24,11 +24,15 @@ class MessageEvent extends Event { private $message; private $envelope; + private $transportName; + private $queued; - public function __construct(RawMessage $message, SmtpEnvelope $envelope) + public function __construct(RawMessage $message, SmtpEnvelope $envelope, string $transportName, bool $queued = false) { $this->message = $message; $this->envelope = $envelope; + $this->transportName = $transportName; + $this->queued = $queued; } public function getMessage(): RawMessage @@ -50,4 +54,14 @@ class MessageEvent extends Event { $this->envelope = $envelope; } + + public function getTransportName(): string + { + return $this->transportName; + } + + public function isQueued(): bool + { + return $this->queued; + } } diff --git a/src/Symfony/Component/Mailer/Event/MessageEvents.php b/src/Symfony/Component/Mailer/Event/MessageEvents.php new file mode 100644 index 0000000000..a921022ced --- /dev/null +++ b/src/Symfony/Component/Mailer/Event/MessageEvents.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\Event; + +/** + * @author Fabien Potencier + */ +class MessageEvents +{ + private $events = []; + private $transports = []; + + public function add(MessageEvent $event): void + { + $this->events[] = $event; + $this->transports[$event->getTransportName()] = true; + } + + public function getTransports(): array + { + return array_keys($this->transports); + } + + /** + * @return MessageEvent[] + */ + public function getEvents(string $name = null): array + { + if (null === $name) { + return $this->events; + } + + $events = []; + foreach ($this->events as $event) { + if ($name === $event->getTransportName()) { + $events[] = $event; + } + } + + return $events; + } +} diff --git a/src/Symfony/Component/Mailer/EventListener/MessageLoggerListener.php b/src/Symfony/Component/Mailer/EventListener/MessageLoggerListener.php new file mode 100644 index 0000000000..093bf2bb9e --- /dev/null +++ b/src/Symfony/Component/Mailer/EventListener/MessageLoggerListener.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Mailer\Event\MessageEvent; +use Symfony\Component\Mailer\Event\MessageEvents; +use Symfony\Contracts\Service\ResetInterface; + +/** + * Logs Messages. + * + * @author Fabien Potencier + */ +class MessageLoggerListener implements EventSubscriberInterface, ResetInterface +{ + private $events; + + public function __construct() + { + $this->events = new MessageEvents(); + } + + /** + * {@inheritdoc} + */ + public function reset() + { + $this->events = new MessageEvents(); + } + + public function onMessage(MessageEvent $event): void + { + $this->events->add($event); + } + + public function getEvents(): MessageEvents + { + return $this->events; + } + + public static function getSubscribedEvents() + { + return [ + MessageEvent::class => ['onMessage', -255], + ]; + } +} diff --git a/src/Symfony/Component/Mailer/Mailer.php b/src/Symfony/Component/Mailer/Mailer.php index 324f50cad7..db87602022 100644 --- a/src/Symfony/Component/Mailer/Mailer.php +++ b/src/Symfony/Component/Mailer/Mailer.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Mailer; +use Symfony\Component\Mailer\Event\MessageEvent; +use Symfony\Component\Mailer\Exception\TransportException; use Symfony\Component\Mailer\Messenger\SendEmailMessage; use Symfony\Component\Mailer\Transport\TransportInterface; use Symfony\Component\Messenger\MessageBusInterface; @@ -38,6 +40,19 @@ class Mailer implements MailerInterface return; } + $message = clone $message; + if (null !== $envelope) { + $envelope = clone $envelope; + } else { + try { + $envelope = new DelayedSmtpEnvelope($message); + } catch (\Exception $e) { + throw new TransportException('Cannot send message without a valid envelope.', 0, $e); + } + } + $event = new MessageEvent($message, $envelope, $this->transport->getName()); + $this->dispatcher->dispatch($event); + $this->bus->dispatch(new SendEmailMessage($message, $envelope)); } } diff --git a/src/Symfony/Component/Mailer/Transport/AbstractTransport.php b/src/Symfony/Component/Mailer/Transport/AbstractTransport.php index 3dd59c195c..90b04c19ab 100644 --- a/src/Symfony/Component/Mailer/Transport/AbstractTransport.php +++ b/src/Symfony/Component/Mailer/Transport/AbstractTransport.php @@ -69,7 +69,7 @@ abstract class AbstractTransport implements TransportInterface } } - $event = new MessageEvent($message, $envelope); + $event = new MessageEvent($message, $envelope, $this->getName(), true); $this->dispatcher->dispatch($event); $envelope = $event->getEnvelope(); if (!$envelope->getRecipients()) { diff --git a/src/Symfony/Component/Mailer/composer.json b/src/Symfony/Component/Mailer/composer.json index f9790c2f8a..896f6380c1 100644 --- a/src/Symfony/Component/Mailer/composer.json +++ b/src/Symfony/Component/Mailer/composer.json @@ -20,7 +20,8 @@ "egulias/email-validator": "^2.0", "psr/log": "~1.0", "symfony/event-dispatcher": "^4.4|^5.0", - "symfony/mime": "^4.4|^5.0" + "symfony/mime": "^4.4|^5.0", + "symfony/service-contracts": "^1.1" }, "require-dev": { "symfony/amazon-mailer": "^4.4|^5.0",