feature #36736 [FrameworkBundle][Mailer] Add a way to configure some email headers from semantic configuration (fabpot)
This PR was merged into the 5.2-dev branch.
Discussion
----------
[FrameworkBundle][Mailer] Add a way to configure some email headers from semantic configuration
| Q | A
| ------------- | ---
| Branch? | master
| Bug fix? | no
| New feature? | yes <!-- please update src/**/CHANGELOG.md files -->
| Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tickets | n/a <!-- prefix each issue number with "Fix #", if any -->
| License | MIT
| Doc PR | not yet
The configuration allows to set global `sender` and `recipients`, but for the *envelope*.
If you want to set some global headers, it was not possible (a default `from` header for instance, of a `bcc`).
That's implemented in this PR.
Commits
-------
805e9e62c1
[FrameworkBundle][Mailer] Add a way to configure some email headers from semantic configuration
This commit is contained in:
commit
4d477ec3f0
@ -1492,6 +1492,7 @@ class Configuration implements ConfigurationInterface
|
|||||||
->thenInvalid('"dsn" and "transports" cannot be used together.')
|
->thenInvalid('"dsn" and "transports" cannot be used together.')
|
||||||
->end()
|
->end()
|
||||||
->fixXmlConfig('transport')
|
->fixXmlConfig('transport')
|
||||||
|
->fixXmlConfig('header')
|
||||||
->children()
|
->children()
|
||||||
->scalarNode('message_bus')->defaultNull()->info('The message bus to use. Defaults to the default bus if the Messenger component is installed.')->end()
|
->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()
|
->scalarNode('dsn')->defaultNull()->end()
|
||||||
@ -1515,6 +1516,20 @@ class Configuration implements ConfigurationInterface
|
|||||||
->end()
|
->end()
|
||||||
->end()
|
->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()
|
->end()
|
||||||
->end()
|
->end()
|
||||||
|
@ -89,6 +89,7 @@ use Symfony\Component\Messenger\MessageBus;
|
|||||||
use Symfony\Component\Messenger\MessageBusInterface;
|
use Symfony\Component\Messenger\MessageBusInterface;
|
||||||
use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
|
use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
|
||||||
use Symfony\Component\Messenger\Transport\TransportInterface;
|
use Symfony\Component\Messenger\Transport\TransportInterface;
|
||||||
|
use Symfony\Component\Mime\Header\Headers;
|
||||||
use Symfony\Component\Mime\MimeTypeGuesserInterface;
|
use Symfony\Component\Mime\MimeTypeGuesserInterface;
|
||||||
use Symfony\Component\Mime\MimeTypes;
|
use Symfony\Component\Mime\MimeTypes;
|
||||||
use Symfony\Component\Notifier\Bridge\Firebase\FirebaseTransportFactory;
|
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 = $container->getDefinition('mailer.envelope_listener');
|
||||||
$envelopeListener->setArgument(0, $sender);
|
$envelopeListener->setArgument(0, $config['envelope']['sender'] ?? null);
|
||||||
$envelopeListener->setArgument(1, $recipients);
|
$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)
|
private function registerNotifierConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)
|
||||||
|
@ -39,6 +39,11 @@
|
|||||||
<tag name="kernel.event_subscriber"/>
|
<tag name="kernel.event_subscriber"/>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service id="mailer.message_listener" class="Symfony\Component\Mailer\EventListener\MessageListener">
|
||||||
|
<argument /> <!-- headers -->
|
||||||
|
<tag name="kernel.event_subscriber"/>
|
||||||
|
</service>
|
||||||
|
|
||||||
<service id="mailer.logger_message_listener" class="Symfony\Component\Mailer\EventListener\MessageLoggerListener">
|
<service id="mailer.logger_message_listener" class="Symfony\Component\Mailer\EventListener\MessageLoggerListener">
|
||||||
<tag name="kernel.event_subscriber"/>
|
<tag name="kernel.event_subscriber"/>
|
||||||
</service>
|
</service>
|
||||||
|
@ -560,14 +560,19 @@
|
|||||||
<xsd:complexType name="mailer">
|
<xsd:complexType name="mailer">
|
||||||
<xsd:sequence>
|
<xsd:sequence>
|
||||||
<xsd:element name="envelope" type="mailer_envelope" minOccurs="0" maxOccurs="1" />
|
<xsd:element name="envelope" type="mailer_envelope" minOccurs="0" maxOccurs="1" />
|
||||||
|
<xsd:element name="header" type="header" minOccurs="0" maxOccurs="unbounded" />
|
||||||
</xsd:sequence>
|
</xsd:sequence>
|
||||||
<xsd:attribute name="dsn" type="xsd:string" />
|
<xsd:attribute name="dsn" type="xsd:string" />
|
||||||
<xsd:attribute name="message-bus" type="xsd:string" />
|
<xsd:attribute name="message-bus" type="xsd:string" />
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
|
|
||||||
|
<xsd:complexType name="header" mixed="true">
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
|
||||||
<xsd:complexType name="mailer_envelope">
|
<xsd:complexType name="mailer_envelope">
|
||||||
<xsd:sequence>
|
<xsd:sequence>
|
||||||
<xsd:element name="sender" type="xsd:string" />
|
<xsd:element name="sender" type="xsd:string" minOccurs="0" maxOccurs="1" />
|
||||||
<xsd:element name="recipients" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
|
<xsd:element name="recipients" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
|
||||||
</xsd:sequence>
|
</xsd:sequence>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
|
@ -493,6 +493,7 @@ class ConfigurationTest extends TestCase
|
|||||||
'transports' => [],
|
'transports' => [],
|
||||||
'enabled' => !class_exists(FullStack::class) && class_exists(Mailer::class),
|
'enabled' => !class_exists(FullStack::class) && class_exists(Mailer::class),
|
||||||
'message_bus' => null,
|
'message_bus' => null,
|
||||||
|
'headers' => [],
|
||||||
],
|
],
|
||||||
'notifier' => [
|
'notifier' => [
|
||||||
'enabled' => !class_exists(FullStack::class) && class_exists(Notifier::class),
|
'enabled' => !class_exists(FullStack::class) && class_exists(Notifier::class),
|
||||||
|
@ -7,5 +7,10 @@ $container->loadFromExtension('framework', [
|
|||||||
'sender' => 'sender@example.org',
|
'sender' => 'sender@example.org',
|
||||||
'recipients' => ['redirected@example.org', 'redirected1@example.org'],
|
'recipients' => ['redirected@example.org', 'redirected1@example.org'],
|
||||||
],
|
],
|
||||||
|
'headers' => [
|
||||||
|
'from' => 'from@example.org',
|
||||||
|
'bcc' => ['bcc1@example.org', 'bcc2@example.org'],
|
||||||
|
'foo' => 'bar',
|
||||||
|
],
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
@ -13,6 +13,9 @@
|
|||||||
<framework:recipients>redirected@example.org</framework:recipients>
|
<framework:recipients>redirected@example.org</framework:recipients>
|
||||||
<framework:recipients>redirected1@example.org</framework:recipients>
|
<framework:recipients>redirected1@example.org</framework:recipients>
|
||||||
</framework:envelope>
|
</framework:envelope>
|
||||||
|
<framework:header name="from">from@example.org</framework:header>
|
||||||
|
<framework:header name="bcc">bcc1@example.org</framework:header>
|
||||||
|
<framework:header name="foo">bar</framework:header>
|
||||||
</framework:mailer>
|
</framework:mailer>
|
||||||
</framework:config>
|
</framework:config>
|
||||||
</container>
|
</container>
|
||||||
|
@ -6,3 +6,7 @@ framework:
|
|||||||
recipients:
|
recipients:
|
||||||
- redirected@example.org
|
- redirected@example.org
|
||||||
- redirected1@example.org
|
- redirected1@example.org
|
||||||
|
headers:
|
||||||
|
from: from@example.org
|
||||||
|
bcc: [bcc1@example.org, bcc2@example.org]
|
||||||
|
foo: bar
|
||||||
|
@ -1435,6 +1435,11 @@ abstract class FrameworkExtensionTest extends TestCase
|
|||||||
$this->assertSame('sender@example.org', $l->getArgument(0));
|
$this->assertSame('sender@example.org', $l->getArgument(0));
|
||||||
$this->assertSame(['redirected@example.org', 'redirected1@example.org'], $l->getArgument(1));
|
$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->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
|
public function testMailerWithDisabledMessageBus(): void
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
"symfony/expression-language": "^4.4|^5.0",
|
"symfony/expression-language": "^4.4|^5.0",
|
||||||
"symfony/http-client": "^4.4|^5.0",
|
"symfony/http-client": "^4.4|^5.0",
|
||||||
"symfony/lock": "^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/messenger": "^4.4|^5.0",
|
||||||
"symfony/mime": "^4.4|^5.0",
|
"symfony/mime": "^4.4|^5.0",
|
||||||
"symfony/process": "^4.4|^5.0",
|
"symfony/process": "^4.4|^5.0",
|
||||||
@ -79,7 +79,7 @@
|
|||||||
"symfony/http-client": "<4.4",
|
"symfony/http-client": "<4.4",
|
||||||
"symfony/form": "<4.4",
|
"symfony/form": "<4.4",
|
||||||
"symfony/lock": "<4.4",
|
"symfony/lock": "<4.4",
|
||||||
"symfony/mailer": "<4.4",
|
"symfony/mailer": "<5.2",
|
||||||
"symfony/messenger": "<4.4",
|
"symfony/messenger": "<4.4",
|
||||||
"symfony/mime": "<4.4",
|
"symfony/mime": "<4.4",
|
||||||
"symfony/property-info": "<4.4",
|
"symfony/property-info": "<4.4",
|
||||||
|
@ -13,8 +13,11 @@ namespace Symfony\Component\Mailer\EventListener;
|
|||||||
|
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
use Symfony\Component\Mailer\Event\MessageEvent;
|
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\BodyRendererInterface;
|
||||||
use Symfony\Component\Mime\Header\Headers;
|
use Symfony\Component\Mime\Header\Headers;
|
||||||
|
use Symfony\Component\Mime\Header\MailboxListHeader;
|
||||||
use Symfony\Component\Mime\Message;
|
use Symfony\Component\Mime\Message;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,13 +27,38 @@ use Symfony\Component\Mime\Message;
|
|||||||
*/
|
*/
|
||||||
class MessageListener implements EventSubscriberInterface
|
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 $headers;
|
||||||
|
private $headerRules = [];
|
||||||
private $renderer;
|
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->headers = $headers;
|
||||||
$this->renderer = $renderer;
|
$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
|
public function onMessage(MessageEvent $event): void
|
||||||
@ -54,14 +82,38 @@ class MessageListener implements EventSubscriberInterface
|
|||||||
foreach ($this->headers->all() as $name => $header) {
|
foreach ($this->headers->all() as $name => $header) {
|
||||||
if (!$headers->has($name)) {
|
if (!$headers->has($name)) {
|
||||||
$headers->add($header);
|
$headers->add($header);
|
||||||
} else {
|
|
||||||
if (Headers::isUniqueHeader($name)) {
|
continue;
|
||||||
continue;
|
}
|
||||||
}
|
|
||||||
$headers->add($header);
|
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
|
private function renderMessage(Message $message): void
|
||||||
|
@ -0,0 +1,105 @@
|
|||||||
|
<?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\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];
|
||||||
|
}
|
||||||
|
}
|
@ -20,7 +20,7 @@
|
|||||||
"egulias/email-validator": "^2.1.10",
|
"egulias/email-validator": "^2.1.10",
|
||||||
"psr/log": "~1.0",
|
"psr/log": "~1.0",
|
||||||
"symfony/event-dispatcher": "^4.4|^5.0",
|
"symfony/event-dispatcher": "^4.4|^5.0",
|
||||||
"symfony/mime": "^4.4|^5.0",
|
"symfony/mime": "^5.2",
|
||||||
"symfony/polyfill-php80": "^1.15",
|
"symfony/polyfill-php80": "^1.15",
|
||||||
"symfony/service-contracts": "^1.1|^2"
|
"symfony/service-contracts": "^1.1|^2"
|
||||||
},
|
},
|
||||||
|
@ -21,10 +21,23 @@ use Symfony\Component\Mime\Exception\LogicException;
|
|||||||
*/
|
*/
|
||||||
final class Headers
|
final class Headers
|
||||||
{
|
{
|
||||||
private static $uniqueHeaders = [
|
private const UNIQUE_HEADERS = [
|
||||||
'date', 'from', 'sender', 'reply-to', 'to', 'cc', 'bcc',
|
'date', 'from', 'sender', 'reply-to', 'to', 'cc', 'bcc',
|
||||||
'message-id', 'in-reply-to', 'references', 'subject',
|
'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 $headers = [];
|
||||||
private $lineLength = 76;
|
private $lineLength = 76;
|
||||||
@ -122,6 +135,22 @@ final class Headers
|
|||||||
return $this->add(new ParameterizedHeader($name, $value, $params));
|
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
|
public function has(string $name): bool
|
||||||
{
|
{
|
||||||
return isset($this->headers[strtolower($name)]);
|
return isset($this->headers[strtolower($name)]);
|
||||||
@ -132,28 +161,12 @@ final class Headers
|
|||||||
*/
|
*/
|
||||||
public function add(HeaderInterface $header): self
|
public function add(HeaderInterface $header): self
|
||||||
{
|
{
|
||||||
static $map = [
|
self::checkHeaderClass($header);
|
||||||
'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,
|
|
||||||
];
|
|
||||||
|
|
||||||
$header->setMaxLineLength($this->lineLength);
|
$header->setMaxLineLength($this->lineLength);
|
||||||
$name = strtolower($header->getName());
|
$name = strtolower($header->getName());
|
||||||
|
|
||||||
if (isset($map[$name]) && !$header instanceof $map[$name]) {
|
if (\in_array($name, self::UNIQUE_HEADERS, true) && isset($this->headers[$name]) && \count($this->headers[$name]) > 0) {
|
||||||
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) {
|
|
||||||
throw new LogicException(sprintf('Impossible to set header "%s" as it\'s already defined and must be unique.', $header->getName()));
|
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
|
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
|
public function toString(): string
|
||||||
|
@ -13,9 +13,11 @@ namespace Symfony\Component\Mime\Tests\Header;
|
|||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\Mime\Address;
|
use Symfony\Component\Mime\Address;
|
||||||
|
use Symfony\Component\Mime\Header\DateHeader;
|
||||||
use Symfony\Component\Mime\Header\Headers;
|
use Symfony\Component\Mime\Header\Headers;
|
||||||
use Symfony\Component\Mime\Header\IdentificationHeader;
|
use Symfony\Component\Mime\Header\IdentificationHeader;
|
||||||
use Symfony\Component\Mime\Header\MailboxListHeader;
|
use Symfony\Component\Mime\Header\MailboxListHeader;
|
||||||
|
use Symfony\Component\Mime\Header\PathHeader;
|
||||||
use Symfony\Component\Mime\Header\UnstructuredHeader;
|
use Symfony\Component\Mime\Header\UnstructuredHeader;
|
||||||
|
|
||||||
class HeadersTest extends TestCase
|
class HeadersTest extends TestCase
|
||||||
@ -63,6 +65,31 @@ class HeadersTest extends TestCase
|
|||||||
$this->assertNotNull($headers->get('Return-Path'));
|
$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()
|
public function testHasReturnsFalseWhenNoHeaders()
|
||||||
{
|
{
|
||||||
$headers = new Headers();
|
$headers = new Headers();
|
||||||
|
Reference in New Issue
Block a user