From 805e9e62c1a23f54eb90699566b90935309143c7 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 6 May 2020 09:10:29 +0200 Subject: [PATCH] [FrameworkBundle][Mailer] Add a way to configure some email headers from semantic configuration --- .../DependencyInjection/Configuration.php | 15 +++ .../FrameworkExtension.php | 23 +++- .../Resources/config/mailer.xml | 5 + .../Resources/config/schema/symfony-1.0.xsd | 7 +- .../DependencyInjection/ConfigurationTest.php | 1 + .../Fixtures/php/mailer.php | 5 + .../Fixtures/xml/mailer.xml | 3 + .../Fixtures/yml/mailer.yml | 4 + .../FrameworkExtensionTest.php | 5 + .../Bundle/FrameworkBundle/composer.json | 4 +- .../Mailer/EventListener/MessageListener.php | 66 +++++++++-- .../EventListener/MessageListenerTest.php | 105 ++++++++++++++++++ src/Symfony/Component/Mailer/composer.json | 2 +- src/Symfony/Component/Mime/Header/Headers.php | 65 +++++++---- .../Mime/Tests/Header/HeadersTest.php | 27 +++++ 15 files changed, 301 insertions(+), 36 deletions(-) create mode 100644 src/Symfony/Component/Mailer/Tests/EventListener/MessageListenerTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index eab898b882..767bcec8ea 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -1492,6 +1492,7 @@ class Configuration implements ConfigurationInterface ->thenInvalid('"dsn" and "transports" cannot be used together.') ->end() ->fixXmlConfig('transport') + ->fixXmlConfig('header') ->children() ->scalarNode('message_bus')->defaultNull()->info('The message bus to use. Defaults to the default bus if the Messenger component is installed.')->end() ->scalarNode('dsn')->defaultNull()->end() @@ -1515,6 +1516,20 @@ class Configuration implements ConfigurationInterface ->end() ->end() ->end() + ->arrayNode('headers') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->prototype('array') + ->normalizeKeys(false) + ->beforeNormalization() + ->ifTrue(function ($v) { return !\is_array($v) || array_keys($v) !== ['value']; }) + ->then(function ($v) { return ['value' => $v]; }) + ->end() + ->children() + ->variableNode('value')->end() + ->end() + ->end() + ->end() ->end() ->end() ->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 4517521c76..2f92b1117a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -89,6 +89,7 @@ use Symfony\Component\Messenger\MessageBus; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Messenger\Transport\TransportFactoryInterface; use Symfony\Component\Messenger\Transport\TransportInterface; +use Symfony\Component\Mime\Header\Headers; use Symfony\Component\Mime\MimeTypeGuesserInterface; use Symfony\Component\Mime\MimeTypes; use Symfony\Component\Notifier\Bridge\Firebase\FirebaseTransportFactory; @@ -1986,12 +1987,24 @@ class FrameworkExtension extends Extension } } - $recipients = $config['envelope']['recipients'] ?? null; - $sender = $config['envelope']['sender'] ?? null; - $envelopeListener = $container->getDefinition('mailer.envelope_listener'); - $envelopeListener->setArgument(0, $sender); - $envelopeListener->setArgument(1, $recipients); + $envelopeListener->setArgument(0, $config['envelope']['sender'] ?? null); + $envelopeListener->setArgument(1, $config['envelope']['recipients'] ?? null); + + if ($config['headers']) { + $headers = new Definition(Headers::class); + foreach ($config['headers'] as $name => $data) { + $value = $data['value']; + if (\in_array(strtolower($name), ['from', 'to', 'cc', 'bcc', 'reply-to'])) { + $value = (array) $value; + } + $headers->addMethodCall('addHeader', [$name, $value]); + } + $messageListener = $container->getDefinition('mailer.message_listener'); + $messageListener->setArgument(0, $headers); + } else { + $container->removeDefinition('mailer.message_listener'); + } } private function registerNotifierConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer.xml index 560556c7ff..c267bc675b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer.xml @@ -39,6 +39,11 @@ + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index 99ffbabb82..5e6e27f3c3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -560,14 +560,19 @@ + + + + + - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index 3ba4c3ecfe..514931ad5c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -493,6 +493,7 @@ class ConfigurationTest extends TestCase 'transports' => [], 'enabled' => !class_exists(FullStack::class) && class_exists(Mailer::class), 'message_bus' => null, + 'headers' => [], ], 'notifier' => [ 'enabled' => !class_exists(FullStack::class) && class_exists(Notifier::class), diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer.php index ef8cdd385c..5e3093b33b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer.php @@ -7,5 +7,10 @@ $container->loadFromExtension('framework', [ 'sender' => 'sender@example.org', 'recipients' => ['redirected@example.org', 'redirected1@example.org'], ], + 'headers' => [ + 'from' => 'from@example.org', + 'bcc' => ['bcc1@example.org', 'bcc2@example.org'], + 'foo' => 'bar', + ], ], ]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer.xml index ff4d75c825..be53f59bc3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer.xml @@ -13,6 +13,9 @@ redirected@example.org redirected1@example.org + from@example.org + bcc1@example.org + bar diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer.yml index 07d435d9df..f8b3c87c43 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer.yml @@ -6,3 +6,7 @@ framework: recipients: - redirected@example.org - redirected1@example.org + headers: + from: from@example.org + bcc: [bcc1@example.org, bcc2@example.org] + foo: bar diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index f175975896..70c8734c5e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -1435,6 +1435,11 @@ abstract class FrameworkExtensionTest extends TestCase $this->assertSame('sender@example.org', $l->getArgument(0)); $this->assertSame(['redirected@example.org', 'redirected1@example.org'], $l->getArgument(1)); $this->assertEquals(new Reference('messenger.default_bus', ContainerInterface::NULL_ON_INVALID_REFERENCE), $container->getDefinition('mailer.mailer')->getArgument(1)); + + $this->assertTrue($container->hasDefinition('mailer.message_listener')); + $l = $container->getDefinition('mailer.message_listener'); + $h = $l->getArgument(0); + $this->assertCount(3, $h->getMethodCalls()); } public function testMailerWithDisabledMessageBus(): void diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 20004ef328..42fcc87178 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -45,7 +45,7 @@ "symfony/expression-language": "^4.4|^5.0", "symfony/http-client": "^4.4|^5.0", "symfony/lock": "^4.4|^5.0", - "symfony/mailer": "^4.4|^5.0", + "symfony/mailer": "^5.2", "symfony/messenger": "^4.4|^5.0", "symfony/mime": "^4.4|^5.0", "symfony/process": "^4.4|^5.0", @@ -79,7 +79,7 @@ "symfony/http-client": "<4.4", "symfony/form": "<4.4", "symfony/lock": "<4.4", - "symfony/mailer": "<4.4", + "symfony/mailer": "<5.2", "symfony/messenger": "<4.4", "symfony/mime": "<4.4", "symfony/property-info": "<4.4", diff --git a/src/Symfony/Component/Mailer/EventListener/MessageListener.php b/src/Symfony/Component/Mailer/EventListener/MessageListener.php index f300f6370a..dbf2570a27 100644 --- a/src/Symfony/Component/Mailer/EventListener/MessageListener.php +++ b/src/Symfony/Component/Mailer/EventListener/MessageListener.php @@ -13,8 +13,11 @@ namespace Symfony\Component\Mailer\EventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Mailer\Event\MessageEvent; +use Symfony\Component\Mailer\Exception\InvalidArgumentException; +use Symfony\Component\Mailer\Exception\RuntimeException; use Symfony\Component\Mime\BodyRendererInterface; use Symfony\Component\Mime\Header\Headers; +use Symfony\Component\Mime\Header\MailboxListHeader; use Symfony\Component\Mime\Message; /** @@ -24,13 +27,38 @@ use Symfony\Component\Mime\Message; */ class MessageListener implements EventSubscriberInterface { + public const HEADER_SET_IF_EMPTY = 1; + public const HEADER_ADD = 2; + public const HEADER_REPLACE = 3; + public const DEFAULT_RULES = [ + 'from' => self::HEADER_SET_IF_EMPTY, + 'return-path' => self::HEADER_SET_IF_EMPTY, + 'reply-to' => self::HEADER_ADD, + 'to' => self::HEADER_SET_IF_EMPTY, + 'cc' => self::HEADER_ADD, + 'bcc' => self::HEADER_ADD, + ]; + private $headers; + private $headerRules = []; private $renderer; - public function __construct(Headers $headers = null, BodyRendererInterface $renderer = null) + public function __construct(Headers $headers = null, BodyRendererInterface $renderer = null, array $headerRules = self::DEFAULT_RULES) { $this->headers = $headers; $this->renderer = $renderer; + foreach ($headerRules as $headerName => $rule) { + $this->addHeaderRule($headerName, $rule); + } + } + + public function addHeaderRule(string $headerName, int $rule): void + { + if ($rule < 1 || $rule > 3) { + throw new InvalidArgumentException(sprintf('The "%d" rule is not supported.', $rule)); + } + + $this->headerRules[$headerName] = $rule; } public function onMessage(MessageEvent $event): void @@ -54,14 +82,38 @@ class MessageListener implements EventSubscriberInterface foreach ($this->headers->all() as $name => $header) { if (!$headers->has($name)) { $headers->add($header); - } else { - if (Headers::isUniqueHeader($name)) { - continue; - } - $headers->add($header); + + continue; + } + + switch ($this->headerRules[$name] ?? self::HEADER_SET_IF_EMPTY) { + case self::HEADER_SET_IF_EMPTY: + break; + + case self::HEADER_REPLACE: + $headers->remove($name); + $headers->add($header); + + break; + + case self::HEADER_ADD: + if (!Headers::isUniqueHeader($name)) { + $headers->add($header); + + break; + } + + $h = $headers->get($name); + if (!$h instanceof MailboxListHeader) { + throw new RuntimeException(sprintf('Unable to set header "%s".', $name)); + } + + Headers::checkHeaderClass($header); + foreach ($header->getAddresses() as $address) { + $h->addAddress($address); + } } } - $message->setHeaders($headers); } private function renderMessage(Message $message): void diff --git a/src/Symfony/Component/Mailer/Tests/EventListener/MessageListenerTest.php b/src/Symfony/Component/Mailer/Tests/EventListener/MessageListenerTest.php new file mode 100644 index 0000000000..6096f5614c --- /dev/null +++ b/src/Symfony/Component/Mailer/Tests/EventListener/MessageListenerTest.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Mailer\Envelope; +use Symfony\Component\Mailer\Event\MessageEvent; +use Symfony\Component\Mailer\EventListener\MessageListener; +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Header\Headers; +use Symfony\Component\Mime\Header\MailboxListHeader; +use Symfony\Component\Mime\Header\UnstructuredHeader; +use Symfony\Component\Mime\Message; + +class MessageListenerTest extends TestCase +{ + /** + * @dataProvider provideHeaders + */ + public function testHeaders(Headers $initialHeaders, Headers $defaultHeaders, Headers $expectedHeaders, array $rules = MessageListener::DEFAULT_RULES) + { + $message = new Message($initialHeaders); + $listener = new MessageListener($defaultHeaders, null, $rules); + $event = new MessageEvent($message, new Envelope(new Address('sender@example.com'), [new Address('recipient@example.com')]), 'smtp'); + $listener->onMessage($event); + + $this->assertEquals($expectedHeaders, $event->getMessage()->getHeaders()); + } + + public function provideHeaders(): iterable + { + $initialHeaders = new Headers(); + $defaultHeaders = (new Headers()) + ->add(new MailboxListHeader('from', [new Address('from-default@example.com')])) + ; + yield 'No defaults, all headers copied over' => [$initialHeaders, $defaultHeaders, $defaultHeaders]; + + $initialHeaders = new Headers(); + $defaultHeaders = (new Headers()) + ->add(new UnstructuredHeader('foo', 'bar')) + ->add(new UnstructuredHeader('bar', 'foo')) + ; + yield 'No defaults, default is to set if empty' => [$initialHeaders, $defaultHeaders, $defaultHeaders]; + + $initialHeaders = (new Headers()) + ->add(new UnstructuredHeader('foo', 'initial')) + ; + $defaultHeaders = (new Headers()) + ->add(new UnstructuredHeader('foo', 'bar')) + ->add(new UnstructuredHeader('bar', 'foo')) + ; + $expectedHeaders = (new Headers()) + ->add(new UnstructuredHeader('foo', 'initial')) + ->add(new UnstructuredHeader('bar', 'foo')) + ; + yield 'Some defaults, default is to set if empty' => [$initialHeaders, $defaultHeaders, $expectedHeaders]; + + $initialHeaders = (new Headers()) + ->add(new UnstructuredHeader('foo', 'initial')) + ; + $defaultHeaders = (new Headers()) + ->add(new UnstructuredHeader('foo', 'bar')) + ->add(new UnstructuredHeader('bar', 'foo')) + ; + $rules = [ + 'foo' => MessageListener::HEADER_REPLACE, + ]; + yield 'Some defaults, replace if set' => [$initialHeaders, $defaultHeaders, $defaultHeaders, $rules]; + + $initialHeaders = (new Headers()) + ->add(new UnstructuredHeader('foo', 'bar')) + ; + $defaultHeaders = (new Headers()) + ->add(new UnstructuredHeader('foo', 'foo')) + ; + $expectedHeaders = (new Headers()) + ->add(new UnstructuredHeader('foo', 'bar')) + ->add(new UnstructuredHeader('foo', 'foo')) + ; + $rules = [ + 'foo' => MessageListener::HEADER_ADD, + ]; + yield 'Some defaults, add if set (not unique header)' => [$initialHeaders, $defaultHeaders, $expectedHeaders, $rules]; + + $initialHeaders = (new Headers()) + ->add(new MailboxListHeader('bcc', [new Address('bcc-initial@example.com')])) + ; + $defaultHeaders = (new Headers()) + ->add(new MailboxListHeader('bcc', [new Address('bcc-default@example.com'), new Address('bcc-default-1@example.com')])) + ; + $expectedHeaders = (new Headers()) + ->add(new MailboxListHeader('bcc', [new Address('bcc-initial@example.com'), new Address('bcc-default@example.com'), new Address('bcc-default-1@example.com')])) + ; + yield 'bcc, add another bcc (unique header)' => [$initialHeaders, $defaultHeaders, $expectedHeaders]; + } +} diff --git a/src/Symfony/Component/Mailer/composer.json b/src/Symfony/Component/Mailer/composer.json index fa729ff483..c50506a12a 100644 --- a/src/Symfony/Component/Mailer/composer.json +++ b/src/Symfony/Component/Mailer/composer.json @@ -20,7 +20,7 @@ "egulias/email-validator": "^2.1.10", "psr/log": "~1.0", "symfony/event-dispatcher": "^4.4|^5.0", - "symfony/mime": "^4.4|^5.0", + "symfony/mime": "^5.2", "symfony/polyfill-php80": "^1.15", "symfony/service-contracts": "^1.1|^2" }, diff --git a/src/Symfony/Component/Mime/Header/Headers.php b/src/Symfony/Component/Mime/Header/Headers.php index 57c99c41fc..3f1efcbbeb 100644 --- a/src/Symfony/Component/Mime/Header/Headers.php +++ b/src/Symfony/Component/Mime/Header/Headers.php @@ -21,10 +21,23 @@ use Symfony\Component\Mime\Exception\LogicException; */ final class Headers { - private static $uniqueHeaders = [ + private const UNIQUE_HEADERS = [ 'date', 'from', 'sender', 'reply-to', 'to', 'cc', 'bcc', 'message-id', 'in-reply-to', 'references', 'subject', ]; + private const HEADER_CLASS_MAP = [ + 'date' => DateHeader::class, + 'from' => MailboxListHeader::class, + 'sender' => MailboxHeader::class, + 'reply-to' => MailboxListHeader::class, + 'to' => MailboxListHeader::class, + 'cc' => MailboxListHeader::class, + 'bcc' => MailboxListHeader::class, + 'message-id' => IdentificationHeader::class, + 'in-reply-to' => IdentificationHeader::class, + 'references' => IdentificationHeader::class, + 'return-path' => PathHeader::class, + ]; private $headers = []; private $lineLength = 76; @@ -122,6 +135,22 @@ final class Headers return $this->add(new ParameterizedHeader($name, $value, $params)); } + /** + * @return $this + */ + public function addHeader(string $name, $argument, array $more = []): self + { + $parts = explode('\\', self::HEADER_CLASS_MAP[$name] ?? UnstructuredHeader::class); + $method = 'add'.ucfirst(array_pop($parts)); + if ('addUnstructuredHeader' === $method) { + $method = 'addTextHeader'; + } elseif ('addIdentificationHeader' === $method) { + $method = 'addIdHeader'; + } + + return $this->$method($name, $argument, $more); + } + public function has(string $name): bool { return isset($this->headers[strtolower($name)]); @@ -132,28 +161,12 @@ final class Headers */ public function add(HeaderInterface $header): self { - static $map = [ - 'date' => DateHeader::class, - 'from' => MailboxListHeader::class, - 'sender' => MailboxHeader::class, - 'reply-to' => MailboxListHeader::class, - 'to' => MailboxListHeader::class, - 'cc' => MailboxListHeader::class, - 'bcc' => MailboxListHeader::class, - 'message-id' => IdentificationHeader::class, - 'in-reply-to' => IdentificationHeader::class, - 'references' => IdentificationHeader::class, - 'return-path' => PathHeader::class, - ]; + self::checkHeaderClass($header); $header->setMaxLineLength($this->lineLength); $name = strtolower($header->getName()); - if (isset($map[$name]) && !$header instanceof $map[$name]) { - throw new LogicException(sprintf('The "%s" header must be an instance of "%s" (got "%s").', $header->getName(), $map[$name], get_debug_type($header))); - } - - if (\in_array($name, self::$uniqueHeaders, true) && isset($this->headers[$name]) && \count($this->headers[$name]) > 0) { + if (\in_array($name, self::UNIQUE_HEADERS, true) && isset($this->headers[$name]) && \count($this->headers[$name]) > 0) { throw new LogicException(sprintf('Impossible to set header "%s" as it\'s already defined and must be unique.', $header->getName())); } @@ -201,7 +214,19 @@ final class Headers public static function isUniqueHeader(string $name): bool { - return \in_array($name, self::$uniqueHeaders, true); + return \in_array($name, self::UNIQUE_HEADERS, true); + } + + /** + * @throws LogicException if the header name and class are not compatible + */ + public static function checkHeaderClass(HeaderInterface $header): void + { + $name = strtolower($header->getName()); + + if (($c = self::HEADER_CLASS_MAP[$name] ?? null) && !$header instanceof $c) { + throw new LogicException(sprintf('The "%s" header must be an instance of "%s" (got "%s").', $header->getName(), $c, get_debug_type($header))); + } } public function toString(): string diff --git a/src/Symfony/Component/Mime/Tests/Header/HeadersTest.php b/src/Symfony/Component/Mime/Tests/Header/HeadersTest.php index e2eb75a697..2255dbe7a7 100644 --- a/src/Symfony/Component/Mime/Tests/Header/HeadersTest.php +++ b/src/Symfony/Component/Mime/Tests/Header/HeadersTest.php @@ -13,9 +13,11 @@ namespace Symfony\Component\Mime\Tests\Header; use PHPUnit\Framework\TestCase; use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Header\DateHeader; use Symfony\Component\Mime\Header\Headers; use Symfony\Component\Mime\Header\IdentificationHeader; use Symfony\Component\Mime\Header\MailboxListHeader; +use Symfony\Component\Mime\Header\PathHeader; use Symfony\Component\Mime\Header\UnstructuredHeader; class HeadersTest extends TestCase @@ -63,6 +65,31 @@ class HeadersTest extends TestCase $this->assertNotNull($headers->get('Return-Path')); } + public function testAddHeader() + { + $headers = new Headers(); + $headers->addHeader('from', ['from@example.com']); + $headers->addHeader('return-path', 'return@example.com'); + $headers->addHeader('foo', 'bar'); + $headers->addHeader('date', $now = new \DateTimeImmutable()); + $headers->addHeader('message-id', 'id@id'); + + $this->assertInstanceOf(MailboxListHeader::class, $headers->get('from')); + $this->assertEquals([new Address('from@example.com')], $headers->get('from')->getBody()); + + $this->assertInstanceOf(PathHeader::class, $headers->get('return-path')); + $this->assertEquals(new Address('return@example.com'), $headers->get('return-path')->getBody()); + + $this->assertInstanceOf(UnstructuredHeader::class, $headers->get('foo')); + $this->assertSame('bar', $headers->get('foo')->getBody()); + + $this->assertInstanceOf(DateHeader::class, $headers->get('date')); + $this->assertSame($now, $headers->get('date')->getBody()); + + $this->assertInstanceOf(IdentificationHeader::class, $headers->get('message-id')); + $this->assertSame(['id@id'], $headers->get('message-id')->getBody()); + } + public function testHasReturnsFalseWhenNoHeaders() { $headers = new Headers();