Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 4d477ec

Browse filesBrowse files
committed
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 ------- 805e9e6 [FrameworkBundle][Mailer] Add a way to configure some email headers from semantic configuration
2 parents c459c80 + 805e9e6 commit 4d477ec
Copy full SHA for 4d477ec

File tree

15 files changed

+301
-36
lines changed
Filter options

15 files changed

+301
-36
lines changed

‎src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
+15Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1492,6 +1492,7 @@ private function addMailerSection(ArrayNodeDefinition $rootNode)
14921492
->thenInvalid('"dsn" and "transports" cannot be used together.')
14931493
->end()
14941494
->fixXmlConfig('transport')
1495+
->fixXmlConfig('header')
14951496
->children()
14961497
->scalarNode('message_bus')->defaultNull()->info('The message bus to use. Defaults to the default bus if the Messenger component is installed.')->end()
14971498
->scalarNode('dsn')->defaultNull()->end()
@@ -1515,6 +1516,20 @@ private function addMailerSection(ArrayNodeDefinition $rootNode)
15151516
->end()
15161517
->end()
15171518
->end()
1519+
->arrayNode('headers')
1520+
->normalizeKeys(false)
1521+
->useAttributeAsKey('name')
1522+
->prototype('array')
1523+
->normalizeKeys(false)
1524+
->beforeNormalization()
1525+
->ifTrue(function ($v) { return !\is_array($v) || array_keys($v) !== ['value']; })
1526+
->then(function ($v) { return ['value' => $v]; })
1527+
->end()
1528+
->children()
1529+
->variableNode('value')->end()
1530+
->end()
1531+
->end()
1532+
->end()
15181533
->end()
15191534
->end()
15201535
->end()

‎src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+18-5Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
use Symfony\Component\Messenger\MessageBusInterface;
9090
use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
9191
use Symfony\Component\Messenger\Transport\TransportInterface;
92+
use Symfony\Component\Mime\Header\Headers;
9293
use Symfony\Component\Mime\MimeTypeGuesserInterface;
9394
use Symfony\Component\Mime\MimeTypes;
9495
use Symfony\Component\Notifier\Bridge\Firebase\FirebaseTransportFactory;
@@ -1986,12 +1987,24 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co
19861987
}
19871988
}
19881989

1989-
$recipients = $config['envelope']['recipients'] ?? null;
1990-
$sender = $config['envelope']['sender'] ?? null;
1991-
19921990
$envelopeListener = $container->getDefinition('mailer.envelope_listener');
1993-
$envelopeListener->setArgument(0, $sender);
1994-
$envelopeListener->setArgument(1, $recipients);
1991+
$envelopeListener->setArgument(0, $config['envelope']['sender'] ?? null);
1992+
$envelopeListener->setArgument(1, $config['envelope']['recipients'] ?? null);
1993+
1994+
if ($config['headers']) {
1995+
$headers = new Definition(Headers::class);
1996+
foreach ($config['headers'] as $name => $data) {
1997+
$value = $data['value'];
1998+
if (\in_array(strtolower($name), ['from', 'to', 'cc', 'bcc', 'reply-to'])) {
1999+
$value = (array) $value;
2000+
}
2001+
$headers->addMethodCall('addHeader', [$name, $value]);
2002+
}
2003+
$messageListener = $container->getDefinition('mailer.message_listener');
2004+
$messageListener->setArgument(0, $headers);
2005+
} else {
2006+
$container->removeDefinition('mailer.message_listener');
2007+
}
19952008
}
19962009

19972010
private function registerNotifierConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)

‎src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer.xml

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer.xml
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@
3939
<tag name="kernel.event_subscriber"/>
4040
</service>
4141

42+
<service id="mailer.message_listener" class="Symfony\Component\Mailer\EventListener\MessageListener">
43+
<argument /> <!-- headers -->
44+
<tag name="kernel.event_subscriber"/>
45+
</service>
46+
4247
<service id="mailer.logger_message_listener" class="Symfony\Component\Mailer\EventListener\MessageLoggerListener">
4348
<tag name="kernel.event_subscriber"/>
4449
</service>

‎src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd
+6-1Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -560,14 +560,19 @@
560560
<xsd:complexType name="mailer">
561561
<xsd:sequence>
562562
<xsd:element name="envelope" type="mailer_envelope" minOccurs="0" maxOccurs="1" />
563+
<xsd:element name="header" type="header" minOccurs="0" maxOccurs="unbounded" />
563564
</xsd:sequence>
564565
<xsd:attribute name="dsn" type="xsd:string" />
565566
<xsd:attribute name="message-bus" type="xsd:string" />
566567
</xsd:complexType>
567568

569+
<xsd:complexType name="header" mixed="true">
570+
<xsd:attribute name="name" type="xsd:string" use="required" />
571+
</xsd:complexType>
572+
568573
<xsd:complexType name="mailer_envelope">
569574
<xsd:sequence>
570-
<xsd:element name="sender" type="xsd:string" />
575+
<xsd:element name="sender" type="xsd:string" minOccurs="0" maxOccurs="1" />
571576
<xsd:element name="recipients" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
572577
</xsd:sequence>
573578
</xsd:complexType>

‎src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,7 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor
493493
'transports' => [],
494494
'enabled' => !class_exists(FullStack::class) && class_exists(Mailer::class),
495495
'message_bus' => null,
496+
'headers' => [],
496497
],
497498
'notifier' => [
498499
'enabled' => !class_exists(FullStack::class) && class_exists(Notifier::class),

‎src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mailer.php
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,10 @@
77
'sender' => 'sender@example.org',
88
'recipients' => ['redirected@example.org', 'redirected1@example.org'],
99
],
10+
'headers' => [
11+
'from' => 'from@example.org',
12+
'bcc' => ['bcc1@example.org', 'bcc2@example.org'],
13+
'foo' => 'bar',
14+
],
1015
],
1116
]);

‎src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer.xml

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mailer.xml
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
<framework:recipients>redirected@example.org</framework:recipients>
1414
<framework:recipients>redirected1@example.org</framework:recipients>
1515
</framework:envelope>
16+
<framework:header name="from">from@example.org</framework:header>
17+
<framework:header name="bcc">bcc1@example.org</framework:header>
18+
<framework:header name="foo">bar</framework:header>
1619
</framework:mailer>
1720
</framework:config>
1821
</container>

‎src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer.yml

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mailer.yml
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@ framework:
66
recipients:
77
- redirected@example.org
88
- redirected1@example.org
9+
headers:
10+
from: from@example.org
11+
bcc: [bcc1@example.org, bcc2@example.org]
12+
foo: bar

‎src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,6 +1435,11 @@ public function testMailer(): void
14351435
$this->assertSame('sender@example.org', $l->getArgument(0));
14361436
$this->assertSame(['redirected@example.org', 'redirected1@example.org'], $l->getArgument(1));
14371437
$this->assertEquals(new Reference('messenger.default_bus', ContainerInterface::NULL_ON_INVALID_REFERENCE), $container->getDefinition('mailer.mailer')->getArgument(1));
1438+
1439+
$this->assertTrue($container->hasDefinition('mailer.message_listener'));
1440+
$l = $container->getDefinition('mailer.message_listener');
1441+
$h = $l->getArgument(0);
1442+
$this->assertCount(3, $h->getMethodCalls());
14381443
}
14391444

14401445
public function testMailerWithDisabledMessageBus(): void

‎src/Symfony/Bundle/FrameworkBundle/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/composer.json
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"symfony/expression-language": "^4.4|^5.0",
4646
"symfony/http-client": "^4.4|^5.0",
4747
"symfony/lock": "^4.4|^5.0",
48-
"symfony/mailer": "^4.4|^5.0",
48+
"symfony/mailer": "^5.2",
4949
"symfony/messenger": "^4.4|^5.0",
5050
"symfony/mime": "^4.4|^5.0",
5151
"symfony/process": "^4.4|^5.0",
@@ -79,7 +79,7 @@
7979
"symfony/http-client": "<4.4",
8080
"symfony/form": "<4.4",
8181
"symfony/lock": "<4.4",
82-
"symfony/mailer": "<4.4",
82+
"symfony/mailer": "<5.2",
8383
"symfony/messenger": "<4.4",
8484
"symfony/mime": "<4.4",
8585
"symfony/property-info": "<4.4",

‎src/Symfony/Component/Mailer/EventListener/MessageListener.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Mailer/EventListener/MessageListener.php
+59-7Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@
1313

1414
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
1515
use Symfony\Component\Mailer\Event\MessageEvent;
16+
use Symfony\Component\Mailer\Exception\InvalidArgumentException;
17+
use Symfony\Component\Mailer\Exception\RuntimeException;
1618
use Symfony\Component\Mime\BodyRendererInterface;
1719
use Symfony\Component\Mime\Header\Headers;
20+
use Symfony\Component\Mime\Header\MailboxListHeader;
1821
use Symfony\Component\Mime\Message;
1922

2023
/**
@@ -24,13 +27,38 @@
2427
*/
2528
class MessageListener implements EventSubscriberInterface
2629
{
30+
public const HEADER_SET_IF_EMPTY = 1;
31+
public const HEADER_ADD = 2;
32+
public const HEADER_REPLACE = 3;
33+
public const DEFAULT_RULES = [
34+
'from' => self::HEADER_SET_IF_EMPTY,
35+
'return-path' => self::HEADER_SET_IF_EMPTY,
36+
'reply-to' => self::HEADER_ADD,
37+
'to' => self::HEADER_SET_IF_EMPTY,
38+
'cc' => self::HEADER_ADD,
39+
'bcc' => self::HEADER_ADD,
40+
];
41+
2742
private $headers;
43+
private $headerRules = [];
2844
private $renderer;
2945

30-
public function __construct(Headers $headers = null, BodyRendererInterface $renderer = null)
46+
public function __construct(Headers $headers = null, BodyRendererInterface $renderer = null, array $headerRules = self::DEFAULT_RULES)
3147
{
3248
$this->headers = $headers;
3349
$this->renderer = $renderer;
50+
foreach ($headerRules as $headerName => $rule) {
51+
$this->addHeaderRule($headerName, $rule);
52+
}
53+
}
54+
55+
public function addHeaderRule(string $headerName, int $rule): void
56+
{
57+
if ($rule < 1 || $rule > 3) {
58+
throw new InvalidArgumentException(sprintf('The "%d" rule is not supported.', $rule));
59+
}
60+
61+
$this->headerRules[$headerName] = $rule;
3462
}
3563

3664
public function onMessage(MessageEvent $event): void
@@ -54,14 +82,38 @@ private function setHeaders(Message $message): void
5482
foreach ($this->headers->all() as $name => $header) {
5583
if (!$headers->has($name)) {
5684
$headers->add($header);
57-
} else {
58-
if (Headers::isUniqueHeader($name)) {
59-
continue;
60-
}
61-
$headers->add($header);
85+
86+
continue;
87+
}
88+
89+
switch ($this->headerRules[$name] ?? self::HEADER_SET_IF_EMPTY) {
90+
case self::HEADER_SET_IF_EMPTY:
91+
break;
92+
93+
case self::HEADER_REPLACE:
94+
$headers->remove($name);
95+
$headers->add($header);
96+
97+
break;
98+
99+
case self::HEADER_ADD:
100+
if (!Headers::isUniqueHeader($name)) {
101+
$headers->add($header);
102+
103+
break;
104+
}
105+
106+
$h = $headers->get($name);
107+
if (!$h instanceof MailboxListHeader) {
108+
throw new RuntimeException(sprintf('Unable to set header "%s".', $name));
109+
}
110+
111+
Headers::checkHeaderClass($header);
112+
foreach ($header->getAddresses() as $address) {
113+
$h->addAddress($address);
114+
}
62115
}
63116
}
64-
$message->setHeaders($headers);
65117
}
66118

67119
private function renderMessage(Message $message): void
+105Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Mailer\Tests;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Mailer\Envelope;
16+
use Symfony\Component\Mailer\Event\MessageEvent;
17+
use Symfony\Component\Mailer\EventListener\MessageListener;
18+
use Symfony\Component\Mime\Address;
19+
use Symfony\Component\Mime\Header\Headers;
20+
use Symfony\Component\Mime\Header\MailboxListHeader;
21+
use Symfony\Component\Mime\Header\UnstructuredHeader;
22+
use Symfony\Component\Mime\Message;
23+
24+
class MessageListenerTest extends TestCase
25+
{
26+
/**
27+
* @dataProvider provideHeaders
28+
*/
29+
public function testHeaders(Headers $initialHeaders, Headers $defaultHeaders, Headers $expectedHeaders, array $rules = MessageListener::DEFAULT_RULES)
30+
{
31+
$message = new Message($initialHeaders);
32+
$listener = new MessageListener($defaultHeaders, null, $rules);
33+
$event = new MessageEvent($message, new Envelope(new Address('sender@example.com'), [new Address('recipient@example.com')]), 'smtp');
34+
$listener->onMessage($event);
35+
36+
$this->assertEquals($expectedHeaders, $event->getMessage()->getHeaders());
37+
}
38+
39+
public function provideHeaders(): iterable
40+
{
41+
$initialHeaders = new Headers();
42+
$defaultHeaders = (new Headers())
43+
->add(new MailboxListHeader('from', [new Address('from-default@example.com')]))
44+
;
45+
yield 'No defaults, all headers copied over' => [$initialHeaders, $defaultHeaders, $defaultHeaders];
46+
47+
$initialHeaders = new Headers();
48+
$defaultHeaders = (new Headers())
49+
->add(new UnstructuredHeader('foo', 'bar'))
50+
->add(new UnstructuredHeader('bar', 'foo'))
51+
;
52+
yield 'No defaults, default is to set if empty' => [$initialHeaders, $defaultHeaders, $defaultHeaders];
53+
54+
$initialHeaders = (new Headers())
55+
->add(new UnstructuredHeader('foo', 'initial'))
56+
;
57+
$defaultHeaders = (new Headers())
58+
->add(new UnstructuredHeader('foo', 'bar'))
59+
->add(new UnstructuredHeader('bar', 'foo'))
60+
;
61+
$expectedHeaders = (new Headers())
62+
->add(new UnstructuredHeader('foo', 'initial'))
63+
->add(new UnstructuredHeader('bar', 'foo'))
64+
;
65+
yield 'Some defaults, default is to set if empty' => [$initialHeaders, $defaultHeaders, $expectedHeaders];
66+
67+
$initialHeaders = (new Headers())
68+
->add(new UnstructuredHeader('foo', 'initial'))
69+
;
70+
$defaultHeaders = (new Headers())
71+
->add(new UnstructuredHeader('foo', 'bar'))
72+
->add(new UnstructuredHeader('bar', 'foo'))
73+
;
74+
$rules = [
75+
'foo' => MessageListener::HEADER_REPLACE,
76+
];
77+
yield 'Some defaults, replace if set' => [$initialHeaders, $defaultHeaders, $defaultHeaders, $rules];
78+
79+
$initialHeaders = (new Headers())
80+
->add(new UnstructuredHeader('foo', 'bar'))
81+
;
82+
$defaultHeaders = (new Headers())
83+
->add(new UnstructuredHeader('foo', 'foo'))
84+
;
85+
$expectedHeaders = (new Headers())
86+
->add(new UnstructuredHeader('foo', 'bar'))
87+
->add(new UnstructuredHeader('foo', 'foo'))
88+
;
89+
$rules = [
90+
'foo' => MessageListener::HEADER_ADD,
91+
];
92+
yield 'Some defaults, add if set (not unique header)' => [$initialHeaders, $defaultHeaders, $expectedHeaders, $rules];
93+
94+
$initialHeaders = (new Headers())
95+
->add(new MailboxListHeader('bcc', [new Address('bcc-initial@example.com')]))
96+
;
97+
$defaultHeaders = (new Headers())
98+
->add(new MailboxListHeader('bcc', [new Address('bcc-default@example.com'), new Address('bcc-default-1@example.com')]))
99+
;
100+
$expectedHeaders = (new Headers())
101+
->add(new MailboxListHeader('bcc', [new Address('bcc-initial@example.com'), new Address('bcc-default@example.com'), new Address('bcc-default-1@example.com')]))
102+
;
103+
yield 'bcc, add another bcc (unique header)' => [$initialHeaders, $defaultHeaders, $expectedHeaders];
104+
}
105+
}

‎src/Symfony/Component/Mailer/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Component/Mailer/composer.json
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"egulias/email-validator": "^2.1.10",
2121
"psr/log": "~1.0",
2222
"symfony/event-dispatcher": "^4.4|^5.0",
23-
"symfony/mime": "^4.4|^5.0",
23+
"symfony/mime": "^5.2",
2424
"symfony/polyfill-php80": "^1.15",
2525
"symfony/service-contracts": "^1.1|^2"
2626
},

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.