Skip to content

[Mailer] [DX] Introduce send email command #39173

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory;
use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridTransportFactory;
use Symfony\Component\Mailer\Bridge\Sendinblue\Transport\SendinblueTransportFactory;
use Symfony\Component\Mailer\Command\MailerSendEmailCommand;
use Symfony\Component\Mailer\Mailer;
use Symfony\Component\Messenger\Bridge\AmazonSqs\Transport\AmazonSqsTransportFactory;
use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpTransportFactory;
Expand Down Expand Up @@ -388,6 +389,10 @@ public function load(array $configs, ContainerBuilder $container)
$this->registerMailerConfiguration($config['mailer'], $container, $loader);
}

if (false === $this->mailerConfigEnabled || false === class_exists(MailerSendEmailCommand::class)) {
$container->removeDefinition('console.command.mailer_send_email');
}

if ($this->notifierConfigEnabled = $this->isConfigEnabled($container, $config['notifier'])) {
$this->registerNotifierConfiguration($config['notifier'], $container, $loader);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
use Symfony\Bundle\FrameworkBundle\Command\YamlLintCommand;
use Symfony\Bundle\FrameworkBundle\EventListener\SuggestMissingPackageSubscriber;
use Symfony\Component\Console\EventListener\ErrorListener;
use Symfony\Component\Mailer\Command\MailerSendEmailCommand;
use Symfony\Component\Messenger\Command\ConsumeMessagesCommand;
use Symfony\Component\Messenger\Command\DebugCommand;
use Symfony\Component\Messenger\Command\FailedMessagesRemoveCommand;
Expand Down Expand Up @@ -133,6 +134,12 @@
])
->tag('console.command', ['command' => 'debug:event-dispatcher'])

->set('console.command.mailer_send_email', MailerSendEmailCommand::class)
->args([
service('mailer.mailer'),
])
->tag('console.command', ['command' => 'mailer:send-email'])

->set('console.command.messenger_consume_messages', ConsumeMessagesCommand::class)
->args([
abstract_arg('Routable message bus'),
Expand Down
2 changes: 2 additions & 0 deletions src/Symfony/Component/Mailer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ CHANGELOG
-----

* added the `mailer` monolog channel and set it on all transport definitions
* added `console mailer:send-email` command to check if your mailer configuration or supplier is (still) operational
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* added `console mailer:send-email` command to check if your mailer configuration or supplier is (still) operational
* added `mailer:send-email` command to check if your mailer configuration or supplier is (still) operational



5.2.0
-----
Expand Down
179 changes: 179 additions & 0 deletions src/Symfony/Component/Mailer/Command/MailerSendEmailCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
<?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\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;

/**
* Helps making sure your mailer provider is operational.
*
* @author Guillaume MOREL <me@gmorel.io>
*/
final class MailerSendEmailCommand extends Command
{
/** {@inheritdoc} */
protected static $defaultName = 'mailer:send-email';

/** @var MailerInterface */
private $mailer;

/** @var SymfonyStyle */
private $io;

/**
* {@inheritdoc}
*/
public function __construct(MailerInterface $mailer)
{
parent::__construct();

$this->mailer = $mailer;
}

/**
* {@inheritdoc}
*/
protected function configure()
{
$this->setDescription('Send simple email message')
->addArgument('from', InputArgument::REQUIRED, 'The from address of the message')
->addArgument('to', InputArgument::REQUIRED, 'The to address of the message')
->addOption('subject', null, InputOption::VALUE_REQUIRED, 'The subject of the message', 'Testing Mailer Component')
->addOption('body', null, InputOption::VALUE_REQUIRED, 'The body of the message', 'This is a test email.')
->addOption('body-source', null, InputOption::VALUE_REQUIRED, 'The source where body come from [stdin|file]', 'stdin')
->setHelp(
<<<EOF
The <info>%command.name%</info> command creates and sends a simple email message.
Usage:
- <info>php %command.full_name% from=a@symfony.com to=b@symfony.com</info>
- <info>php %command.full_name% from=a@symfony.com to=b@symfony.com --subject=Test --body=body</info>

You can get body of message from a file:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
You can get body of message from a file:
Use a file for the body:

<info>php %command.full_name% from=a@symfony.com to=b@symfony.com --subject=Test --body-source=file --body=/path/to/file</info>
EOF
);
}

/**
* {@inheritdoc}
*/
protected function initialize(InputInterface $input, OutputInterface $output)
{
$this->io = new SymfonyStyle($input, $output);
}

/**
* {@inheritdoc}
*
* @throws TransportExceptionInterface
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
switch ($input->getOption('body-source')) {
case 'file':
$content = $this->loadFileContent(
$input->getOption('body')
);
$input->setOption('body', $content);
break;
case 'stdin':
break;
default:
throw new \InvalidArgumentException('Body-input option should be "stdin" or "file".');
}

if ('file' === $input->getOption('body-source')) {
$email = $this->createEmailFromFile($input);
} else {
$email = $this->createEmailFromString($input);
}

$this->mailer->send($email);

$this->io->success(
sprintf(
'Email was successfully sent to "%s".',
(string) $input->getArgument('to')
)
);

return Command::SUCCESS;
}

private function createEmailFromString(InputInterface $input): Email
{
$subject = $input->getOption('subject');
$body = $input->getOption('body');

return $this->createEmailWithoutBody($input)
->text($body)
->html(
<<<HTML
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>$subject</title>
</head>
<body>
<p>$body</p>
</body>
</html>
HTML
);
}

private function createEmailFromFile(InputInterface $input): Email
{
$body = $input->getOption('body');

return $this->createEmailWithoutBody($input)
->text($body)
->html($body);
}

private function createEmailWithoutBody(InputInterface $input): Email
{
return (new Email())
->from($input->getArgument('from'))
->to($input->getArgument('to'))
->priority(Email::PRIORITY_HIGH)
->subject($input->getOption('subject'));
}

/**
* @throws \InvalidArgumentException
* @throws \LogicException
*/
private function loadFileContent(string $fileUri): string
{
if (false === file_exists($fileUri)) {
throw new \InvalidArgumentException("Could not find file \"$fileUri\".");
}

$content = file_get_contents($fileUri);
if (false === $content) {
throw new \LogicException("Could not get contents from file \"$fileUri\".");
}

return $content;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
<?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\Command;

use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\Mailer\Command\MailerSendEmailCommand;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;

class MailerSendEmailCommandTest extends TestCase
{
public function testSendMail()
{
$expectedMail = $this->createExpectedMailFromString(
'a@symfony.com',
'b@symfony.com',
'Test',
'body'
);

$mailer = $this->createMock(MailerInterface::class);
$mailer->expects($this->once())->method('send')->with($expectedMail);

$command = new MailerSendEmailCommand($mailer);

$application = new Application();
$application->add($command);
$tester = new CommandTester($application->get('mailer:send-email'));
$tester->execute([
'from' => 'a@symfony.com',
'to' => 'b@symfony.com',
'--subject' => 'Test',
'--body' => 'body',
]);

$this->assertSame(0, $tester->getStatusCode());
$this->assertStringContainsString('[OK] Email was successfully sent to "b@symfony.com"', $tester->getDisplay());
}

public function testSendMailNoSubject()
{
$expectedMail = $this->createExpectedMailFromString(
'a@symfony.com',
'b@symfony.com',
'Testing Mailer Component',
'This is a test email.'
);

$mailer = $this->createMock(MailerInterface::class);
$mailer->expects($this->once())->method('send')->with($expectedMail);

$command = new MailerSendEmailCommand($mailer);

$application = new Application();
$application->add($command);
$tester = new CommandTester($application->get('mailer:send-email'));
$tester->execute([
'from' => 'a@symfony.com',
'to' => 'b@symfony.com',
]);

$this->assertSame(0, $tester->getStatusCode());
$this->assertStringContainsString('[OK] Email was successfully sent to "b@symfony.com"', $tester->getDisplay());
}

public function testSendMailBodyFromFile()
{
$temporaryPath = $this->createTemporaryPath();
file_put_contents($temporaryPath, 'Body from file');

$expectedMail = $this->createExpectedMailFromFile(
'a@symfony.com',
'b@symfony.com',
'Testing Mailer Component',
'Body from file'
);

$mailer = $this->createMock(MailerInterface::class);
$mailer->expects($this->once())->method('send')->with($expectedMail);

$command = new MailerSendEmailCommand($mailer);

$application = new Application();
$application->add($command);
$tester = new CommandTester($application->get('mailer:send-email'));
$tester->execute([
'from' => 'a@symfony.com',
'to' => 'b@symfony.com',
'--body' => $temporaryPath,
'--body-source' => 'file',
]);

$this->assertSame(0, $tester->getStatusCode());
$this->assertStringContainsString('[OK] Email was successfully sent to "b@symfony.com"', $tester->getDisplay());
}

private function createExpectedMailFromString(string $from, string $to, string $subject, string $body): Email
{
return (new Email())
->from($from)
->to($to)
->priority(Email::PRIORITY_HIGH)
->subject($subject)
->text($body)
->html(
<<<HTML
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>$subject</title>
</head>
<body>
<p>$body</p>
</body>
</html>
HTML
);
}

private function createExpectedMailFromFile(string $from, string $to, string $subject, string $body): Email
{
return (new Email())
->from($from)
->to($to)
->priority(Email::PRIORITY_HIGH)
->subject($subject)
->text($body)
->html($body);
}

private function createTemporaryPath(): string
{
return stream_get_meta_data(tmpfile())['uri'];
}
}
3 changes: 2 additions & 1 deletion src/Symfony/Component/Mailer/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
"symfony/mailchimp-mailer": "^4.4|^5.0",
"symfony/messenger": "^4.4|^5.0",
"symfony/postmark-mailer": "^4.4|^5.0",
"symfony/sendgrid-mailer": "^4.4|^5.0"
"symfony/sendgrid-mailer": "^4.4|^5.0",
"symfony/console": "^4.4|^5.0"
},
"conflict": {
"symfony/http-kernel": "<4.4"
Expand Down