Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature #33456 [MonologBridge] Add Mailer handler (BoShurik)
This PR was merged into the 5.1-dev branch. Discussion ---------- [MonologBridge] Add Mailer handler | Q | A | ------------- | --- | Branch? | 4.4 | Bug fix? | no | New feature? | yes <!-- please update src/**/CHANGELOG.md files --> | BC breaks? | no <!-- see https://symfony.com/bc --> | Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files --> | Tests pass? | yes <!-- please add some, will be required by reviewers --> | Fixed tickets | #33209 <!-- #-prefixed issue number(s), if any --> | License | MIT | Doc PR | - <!-- required for new features --> <!-- Replace this notice by a short README for your feature/bugfix. This will help people understand your PR and can be used as a start for the documentation. Additionally (see https://symfony.com/roadmap): - Bug fixes must be submitted against the lowest maintained branch where they apply (lowest branches are regularly merged to upper ones so they get the fixes too). - Features and deprecations must be submitted against branch 4.4. - Legacy code removals go to the master branch. --> Commits ------- 5b7393b Add monolog mailer handler
- Loading branch information
Showing
4 changed files
with
273 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,10 @@ | ||
CHANGELOG | ||
========= | ||
|
||
5.1.0 | ||
----- | ||
* Added `MailerHandler` | ||
|
||
5.0.0 | ||
----- | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
<?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\Bridge\Monolog\Handler; | ||
|
||
use Monolog\Formatter\FormatterInterface; | ||
use Monolog\Formatter\HtmlFormatter; | ||
use Monolog\Formatter\LineFormatter; | ||
use Monolog\Handler\AbstractProcessingHandler; | ||
use Monolog\Logger; | ||
use Symfony\Component\Mailer\MailerInterface; | ||
use Symfony\Component\Mime\Email; | ||
|
||
/** | ||
* @author Alexander Borisov <boshurik@gmail.com> | ||
*/ | ||
class MailerHandler extends AbstractProcessingHandler | ||
{ | ||
private $mailer; | ||
|
||
private $messageTemplate; | ||
|
||
/** | ||
* @param callable|Email $messageTemplate | ||
*/ | ||
public function __construct(MailerInterface $mailer, $messageTemplate, int $level = Logger::DEBUG, bool $bubble = true) | ||
{ | ||
parent::__construct($level, $bubble); | ||
|
||
$this->mailer = $mailer; | ||
$this->messageTemplate = $messageTemplate; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function handleBatch(array $records): void | ||
{ | ||
$messages = []; | ||
|
||
foreach ($records as $record) { | ||
if ($record['level'] < $this->level) { | ||
continue; | ||
} | ||
$messages[] = $this->processRecord($record); | ||
} | ||
|
||
if (!empty($messages)) { | ||
$this->send((string) $this->getFormatter()->formatBatch($messages), $messages); | ||
} | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
protected function write(array $record): void | ||
{ | ||
$this->send((string) $record['formatted'], [$record]); | ||
} | ||
|
||
/** | ||
* Send a mail with the given content. | ||
* | ||
* @param string $content formatted email body to be sent | ||
* @param array $records the array of log records that formed this content | ||
*/ | ||
protected function send(string $content, array $records) | ||
{ | ||
$this->mailer->send($this->buildMessage($content, $records)); | ||
} | ||
|
||
/** | ||
* Gets the formatter for the Message subject. | ||
* | ||
* @param string $format The format of the subject | ||
*/ | ||
protected function getSubjectFormatter(string $format): FormatterInterface | ||
{ | ||
return new LineFormatter($format); | ||
} | ||
|
||
/** | ||
* Creates instance of Message to be sent. | ||
* | ||
* @param string $content formatted email body to be sent | ||
* @param array $records Log records that formed the content | ||
*/ | ||
protected function buildMessage(string $content, array $records): Email | ||
{ | ||
$message = null; | ||
if ($this->messageTemplate instanceof Email) { | ||
$message = clone $this->messageTemplate; | ||
} elseif (\is_callable($this->messageTemplate)) { | ||
$message = \call_user_func($this->messageTemplate, $content, $records); | ||
if (!$message instanceof Email) { | ||
throw new \InvalidArgumentException(sprintf('Could not resolve message from a callable. Instance of "%s" is expected', Email::class)); | ||
} | ||
} else { | ||
throw new \InvalidArgumentException('Could not resolve message as instance of Email or a callable returning it'); | ||
} | ||
|
||
if ($records) { | ||
$subjectFormatter = $this->getSubjectFormatter($message->getSubject()); | ||
$message->subject($subjectFormatter->format($this->getHighestRecord($records))); | ||
} | ||
|
||
if ($this->getFormatter() instanceof HtmlFormatter) { | ||
if ($message->getHtmlCharset()) { | ||
$message->html($content, $message->getHtmlCharset()); | ||
} else { | ||
$message->html($content); | ||
} | ||
} else { | ||
if ($message->getTextCharset()) { | ||
$message->text($content, $message->getTextCharset()); | ||
} else { | ||
$message->text($content); | ||
} | ||
} | ||
|
||
return $message; | ||
} | ||
|
||
protected function getHighestRecord(array $records): array | ||
{ | ||
$highestRecord = null; | ||
foreach ($records as $record) { | ||
if (null === $highestRecord || $highestRecord['level'] < $record['level']) { | ||
$highestRecord = $record; | ||
} | ||
} | ||
|
||
return $highestRecord; | ||
} | ||
} |
123 changes: 123 additions & 0 deletions
123
src/Symfony/Bridge/Monolog/Tests/Handler/MailerHandlerTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
<?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\Bridge\Monolog\Tests\Handler; | ||
|
||
use Monolog\Formatter\HtmlFormatter; | ||
use Monolog\Formatter\LineFormatter; | ||
use PHPUnit\Framework\MockObject\MockObject; | ||
use PHPUnit\Framework\TestCase; | ||
use Symfony\Bridge\Monolog\Handler\MailerHandler; | ||
use Symfony\Bridge\Monolog\Logger; | ||
use Symfony\Component\Mailer\MailerInterface; | ||
use Symfony\Component\Mime\Email; | ||
|
||
class MailerHandlerTest extends TestCase | ||
{ | ||
/** @var MockObject|MailerInterface */ | ||
private $mailer = null; | ||
|
||
protected function setUp(): void | ||
{ | ||
$this->mailer = $this->createMock(MailerInterface::class); | ||
} | ||
|
||
public function testHandle() | ||
{ | ||
$handler = new MailerHandler($this->mailer, (new Email())->subject('Alert: %level_name% %message%')); | ||
$handler->setFormatter(new LineFormatter()); | ||
$this->mailer | ||
->expects($this->once()) | ||
->method('send') | ||
->with($this->callback(function (Email $email) { | ||
return 'Alert: WARNING message' === $email->getSubject() && null === $email->getHtmlBody(); | ||
})) | ||
; | ||
$handler->handle($this->getRecord(Logger::WARNING, 'message')); | ||
} | ||
|
||
public function testHandleBatch() | ||
{ | ||
$handler = new MailerHandler($this->mailer, (new Email())->subject('Alert: %level_name% %message%')); | ||
$handler->setFormatter(new LineFormatter()); | ||
$this->mailer | ||
->expects($this->once()) | ||
->method('send') | ||
->with($this->callback(function (Email $email) { | ||
return 'Alert: ERROR error' === $email->getSubject() && null === $email->getHtmlBody(); | ||
})) | ||
; | ||
$handler->handleBatch($this->getMultipleRecords()); | ||
} | ||
|
||
public function testMessageCreationIsLazyWhenUsingCallback() | ||
{ | ||
$this->mailer | ||
->expects($this->never()) | ||
->method('send') | ||
; | ||
|
||
$callback = function () { | ||
throw new \RuntimeException('Email creation callback should not have been called in this test'); | ||
}; | ||
$handler = new MailerHandler($this->mailer, $callback, Logger::ALERT); | ||
|
||
$records = [ | ||
$this->getRecord(Logger::DEBUG), | ||
$this->getRecord(Logger::INFO), | ||
]; | ||
$handler->handleBatch($records); | ||
} | ||
|
||
public function testHtmlContent() | ||
{ | ||
$handler = new MailerHandler($this->mailer, (new Email())->subject('Alert: %level_name% %message%')); | ||
$handler->setFormatter(new HtmlFormatter()); | ||
$this->mailer | ||
->expects($this->once()) | ||
->method('send') | ||
->with($this->callback(function (Email $email) { | ||
return 'Alert: WARNING message' === $email->getSubject() && null === $email->getTextBody(); | ||
})) | ||
; | ||
$handler->handle($this->getRecord(Logger::WARNING, 'message')); | ||
} | ||
|
||
/** | ||
* @return array Record | ||
*/ | ||
protected function getRecord($level = Logger::WARNING, $message = 'test', $context = []) | ||
{ | ||
return [ | ||
'message' => $message, | ||
'context' => $context, | ||
'level' => $level, | ||
'level_name' => Logger::getLevelName($level), | ||
'channel' => 'test', | ||
'datetime' => \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true))), | ||
'extra' => [], | ||
]; | ||
} | ||
|
||
/** | ||
* @return array | ||
*/ | ||
protected function getMultipleRecords() | ||
{ | ||
return [ | ||
$this->getRecord(Logger::DEBUG, 'debug message 1'), | ||
$this->getRecord(Logger::DEBUG, 'debug message 2'), | ||
$this->getRecord(Logger::INFO, 'information'), | ||
$this->getRecord(Logger::WARNING, 'warning'), | ||
$this->getRecord(Logger::ERROR, 'error'), | ||
]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters