Skip to content

Commit 2fc6010

Browse files
FEATURE: Implement symfony mailer wrapper for Flow
1 parent d8e30e2 commit 2fc6010

File tree

8 files changed

+404
-2
lines changed

8 files changed

+404
-2
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/vendor
2+
composer.lock
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Neos\SymfonyMailer\Command;
5+
6+
/*
7+
* This file is part of the Neos.SymfonyMailer package.
8+
*
9+
* (c) Contributors of the Neos Project - www.neos.io
10+
*
11+
* This package is Open Source Software. For the full copyright and license
12+
* information, please view the LICENSE file which was distributed with this
13+
* source code.
14+
*/
15+
16+
use Neos\Flow\Annotations as Flow;
17+
use Neos\Flow\Cli\CommandController;
18+
use Symfony\Component\Yaml\Yaml;
19+
use Neos\SymfonyMailer\Service\MailerService;
20+
21+
/**
22+
* A command controller for migrating from SwiftMailer to Symfony Mailer
23+
*/
24+
class MigrateCommandController extends CommandController
25+
{
26+
27+
#[Flow\Inject]
28+
protected MailerService $mailerService;
29+
30+
31+
#[Flow\InjectConfiguration(path: "transport", package: "Neos.SwiftMailer")]
32+
protected array $swiftMailerConfiguration;
33+
34+
/**
35+
* Command to migrate the SwiftMailer configuration to Symfony Mailer DSN.
36+
* Therefore, we check if we have a SwiftMailer configuration and if so, we create a DSN from it.
37+
*/
38+
public function generateDSNFromSwiftMailerCommand(): void
39+
{
40+
if (!isset($this->swiftMailerConfiguration['type'])) {
41+
$this->outputLine('<error>No SwiftMailer configuration found. Nothing to migrate.</error>');
42+
$this->quit(1);
43+
}
44+
45+
// Output the SwiftMailer configuration
46+
$yaml = Yaml::dump($this->swiftMailerConfiguration, 99);
47+
$this->outputLine('<b>Found Configuration for "Neos.SwiftMailer":</b>');
48+
$this->outputLine();
49+
$this->outputLine($yaml . chr(10));
50+
$this->outputLine();
51+
52+
$transportType = $this->swiftMailerConfiguration['type'];
53+
$transportOptions = $this->swiftMailerConfiguration['options'] ?? [];
54+
55+
if ($transportType === 'Swift_SmtpTransport') {
56+
$dsn = $this->mailerService->createDsnFromSMTPSwiftMailerConfiguration($transportOptions);
57+
} else {
58+
$this->outputLine('<error>Unsupported SymfonyMailer transport type. Nothing to migrate.</error>');
59+
$this->quit(1);
60+
}
61+
62+
$this->outputLine('<success>DSN created: ' . $dsn . '</success>');
63+
}
64+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Neos\SymfonyMailer\Command;
5+
6+
/*
7+
* This file is part of the Neos.SymfonyMailer package.
8+
*
9+
* (c) Contributors of the Neos Project - www.neos.io
10+
*
11+
* This package is Open Source Software. For the full copyright and license
12+
* information, please view the LICENSE file which was distributed with this
13+
* source code.
14+
*/
15+
16+
use Neos\Flow\Annotations as Flow;
17+
use Symfony\Component\Yaml\Yaml;
18+
use Neos\Flow\Cli\CommandController;
19+
use Neos\Flow\Cli\Exception\StopCommandException;
20+
use Neos\SymfonyMailer\Exception\InvalidMailerConfigurationException;
21+
use Neos\SymfonyMailer\Service\MailerService;
22+
use Symfony\Component\Mime\Email;
23+
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
24+
25+
/**
26+
* A command controller for sending test emails
27+
*/
28+
class TestCommandController extends CommandController
29+
{
30+
31+
#[Flow\Inject]
32+
protected MailerService $mailerService;
33+
34+
#[Flow\InjectConfiguration(package: "Neos.SymfonyMailer")]
35+
protected array $mailerConfiguration;
36+
37+
/**
38+
* A command for creating and sending simple emails.
39+
*
40+
* @param string $from The from address of the message
41+
* @param string $to The to address of the message
42+
* @param string $subject The subject of the message
43+
* @param string $body The body of the message
44+
* @param string $contentType The body content type of the message (Default: test/plain)
45+
* @throws TransportExceptionInterface
46+
* @throws StopCommandException
47+
*/
48+
public function sendCommand(string $from, string $to, string $subject, string $body = '', string $contentType = 'text/plain'): void
49+
{
50+
$email = new Email();
51+
$email
52+
->from($from)
53+
->to($to)
54+
->subject($subject);
55+
56+
if ($contentType === MailerService::FORMAT_HTML) {
57+
$email->html($body);
58+
} else {
59+
$email->text($body);
60+
}
61+
62+
try {
63+
// Output the SwiftMailer configuration
64+
$yaml = Yaml::dump($this->mailerConfiguration, 99);
65+
$this->outputLine('<b>Send mail with following configuration "Neos.SymfonyMailer":</b>');
66+
$this->outputLine();
67+
$this->outputLine($yaml . chr(10));
68+
69+
$mailer = $this->mailerService->getMailer();
70+
$mailer->send($email);
71+
} catch (InvalidMailerConfigurationException|\Exception $e) {
72+
$this->outputLine('<error>' . $e->getMessage() . '</error>');
73+
$this->quit(1);
74+
}
75+
76+
$this->outputLine('<success>E-Mail has successfully been sent.</success>');
77+
}
78+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Neos\SymfonyMailer\Exception;
5+
6+
/*
7+
* This file is part of the Neos.SymfonyMailer package.
8+
*
9+
* (c) Contributors of the Neos Project - www.neos.io
10+
*
11+
* This package is Open Source Software. For the full copyright and license
12+
* information, please view the LICENSE file which was distributed with this
13+
* source code.
14+
*/
15+
16+
use \Exception;
17+
18+
/**
19+
* InvalidMailerConfigurationException exception
20+
*/
21+
class InvalidMailerConfigurationException extends Exception
22+
{
23+
}

Classes/Service/MailerService.php

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Neos\SymfonyMailer\Service;
5+
6+
/*
7+
* This file is part of the Neos.SymfonyMailer package.
8+
*
9+
* (c) Contributors of the Neos Project - www.neos.io
10+
*
11+
* This package is Open Source Software. For the full copyright and license
12+
* information, please view the LICENSE file which was distributed with this
13+
* source code.
14+
*/
15+
16+
use Neos\Flow\Annotations as Flow;
17+
use Neos\SymfonyMailer\Exception\InvalidMailerConfigurationException;
18+
use Symfony\Component\Mailer\Transport;
19+
use Symfony\Component\Mailer\Mailer;
20+
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;
21+
use Symfony\Component\Mailer\Transport\TransportInterface;
22+
23+
#[Flow\Scope("singleton")]
24+
class MailerService
25+
{
26+
#[Flow\InjectConfiguration(path: "mailer", package: "Neos.SymfonyMailer")]
27+
protected array $mailerConfiguration;
28+
29+
public const FORMAT_PLAINTEXT = 'text/plain';
30+
public const FORMAT_HTML = 'html';
31+
32+
/**
33+
* Returns a mailer instance with the given transport or the configured default transport.
34+
*
35+
* @param TransportInterface|null $transport
36+
* @return Mailer
37+
* @throws InvalidMailerConfigurationException
38+
*/
39+
public function getMailer(TransportInterface $transport = null): Mailer
40+
{
41+
if ($transport !== null) {
42+
return new Mailer($transport);
43+
}
44+
45+
// throw exception when dsn is not set
46+
if (!isset($this->mailerConfiguration['dsn'])) {
47+
throw new InvalidMailerConfigurationException('No DSN configured for Neos.SymfonyMailer', 1739540476);
48+
}
49+
50+
return new Mailer(Transport::fromDsn($this->mailerConfiguration['dsn']));
51+
}
52+
53+
/**
54+
* Function that creates a DSN from the Swift-mailer configuration array to be able to migrate the
55+
* configuration from Swift-mailer to Symfony Mailer.
56+
*
57+
* Old configuration:
58+
* Neos:
59+
* SwiftMailer:
60+
* transport:
61+
* type: 'Swift_SmtpTransport'
62+
* options:
63+
* host: 'smtp.example.com'
64+
* port: '465'
65+
* encryption: 'ssl'
66+
* username: 'myaccount@example.com'
67+
* password: 'shoobidoo'
68+
*
69+
* New configuration:
70+
* Neos:
71+
* SymfonyMailer:
72+
* mailer:
73+
* dsn: 'smtp://username:password@host:port'
74+
*
75+
* @param array $configuration
76+
* @return string
77+
*/
78+
public function createDsnFromSMTPSwiftMailerConfiguration(array $configuration): string
79+
{
80+
$transport = new EsmtpTransport(
81+
host: $configuration['host'] ?? 'localhost',
82+
port: (int)($configuration['port'] ?? 0)
83+
);
84+
85+
if (isset($configuration['localDomain'])) {
86+
$transport->setLocalDomain($configuration['localDomain']);
87+
}
88+
89+
if (isset($configuration['username'])) {
90+
$transport->setUsername($configuration['username']);
91+
}
92+
93+
if (isset($configuration['password'])) {
94+
$transport->setPassword($configuration['password']);
95+
}
96+
97+
$dsn = new Transport\Dsn(
98+
scheme: 'smtp',
99+
host: $configuration['host'] ?? 'localhost',
100+
user: $transport->getUsername(),
101+
password: $transport->getPassword(),
102+
port: $transport->getStream()->getPort()
103+
);
104+
105+
return $this->getStringFromDSN($dsn);
106+
}
107+
108+
protected function getStringFromDSN(Transport\Dsn $dsn): string
109+
{
110+
$dsnString = $dsn->getScheme() . '://';
111+
112+
if ($dsn->getUser() !== null) {
113+
$dsnString .= $dsn->getUser();
114+
if ($dsn->getPassword() !== null) {
115+
$dsnString .= ':' . $dsn->getPassword();
116+
}
117+
$dsnString .= '@';
118+
}
119+
120+
$dsnString .= $dsn->getHost();
121+
122+
if ($dsn->getPort() !== null) {
123+
$dsnString .= ':' . $dsn->getPort();
124+
}
125+
126+
return $dsnString;
127+
}
128+
}

Configuration/Settings.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Neos:
2+
SymfonyMailer:
3+
mailer:
4+
dsn: 'native://default'

README.md

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,87 @@
1-
# symfonymailer
2-
A Flow package for easy use of the Symfony Mailer
1+
Neos SymfonyMailer
2+
================
3+
4+
The Mailer package facilitates email sending via the Symfony/Mailer package and simplifies its usage with
5+
the standard method of Neos.Flow configuration.
6+
7+
The package can also facilitate a smoother migration from the old Swiftmailer package to the new Symfony/Mailer package.
8+
9+
Getting Started
10+
---------------
11+
12+
```
13+
$ composer require neos/symfonymailer
14+
```
15+
16+
Configuration
17+
-------------
18+
19+
The package provides a default configuration for the Symfony/Mailer package. You can override the default configuration
20+
by adding the following configuration to your `Settings.yaml`:
21+
22+
```yaml
23+
Neos:
24+
SymfonyMailer:
25+
mailer:
26+
dsn: 'smtp://localhost'
27+
```
28+
29+
The `dsn` parameter is the only required parameter. It should be a valid DSN string for the Symfony/Mailer package.
30+
If you migrate from Swiftmailer, you can use the following command to generate the DSN:
31+
32+
```bash
33+
./flow symfonymailer:migrate:generatedsnfromswiftmailer
34+
```
35+
36+
Usage
37+
-----
38+
39+
You can test the mailer configuration by sending a test email:
40+
41+
```bash
42+
./flow symfonymailer:test:send --from="team@neos.io" --to="jon@doe.com" --subject="Test email" --body="This is a test email"
43+
```
44+
45+
The package’s design principle is to minimize modifications to the original mailer package. Consequently, only the classes of Symfony/Mailer are utilized for emails, attachments, and so on. The package solely provides a service to initialize the mailer with the configuration.
46+
Additionally, the package provides a command to send test emails and migrate the SwiftMailer configuration to an appropriate DSN for Symfony/Mailer.
47+
48+
If you use the `LoggingTransport` or `MboxTransport` from SwiftMailer, there is no replacement for the Symfony/Mailer package.
49+
50+
**Basic Example**
51+
```php
52+
#[Flow\Inject]
53+
protected MailerService $mailerService;
54+
55+
public function sendEmail(string $from, string $to, string $subject, string $body): void
56+
{
57+
$email = new Email();
58+
$email
59+
->from($from)
60+
->to($to)
61+
->subject($subject)
62+
->text($body);
63+
64+
$mailer = $this->mailerService->getMailer();
65+
$mailer->send($email);
66+
}
67+
```
68+
69+
Migrate from Swiftmailer
70+
------------------------
71+
72+
As previously mentioned, the package includes a command to generate a DSN from the Swiftmailer configuration.
73+
To utilize this command, you must have the Swiftmailer configuration located in your `Settings.yaml` file.
74+
75+
You can use the following command to generate the DSN.
76+
```bash
77+
./flow symfonymailer:migrate:generatedsnfromswiftmailer
78+
```
79+
80+
The PHP code within your package requires some modifications.
81+
To facilitate the migration process, there are several rector rules available that facilitate the transition from Swiftmailer to Symfony/Mailer.
82+
83+
* [SwiftMessageToEmailRector](https://getrector.com/rule-detail/swift-message-to-email-rector)
84+
* [SwiftCreateMessageToNewEmailRector](https://getrector.com/rule-detail/swift-create-message-to-new-email-rector)
85+
* [SwiftSetBodyToHtmlPlainMethodCallRector](https://getrector.com/rule-detail/swift-set-body-to-html-plain-method-call-rector)
86+
87+
Utilizing these tools, it is feasible to migrate your codebase to Symfony/Mailer.

0 commit comments

Comments
 (0)