瀏覽代碼

Add SymfonyMailerHandler, deprecate SwiftMailerHandler (#1663)

Jordi Boggiano 3 年之前
父節點
當前提交
4c7a12b026

+ 5 - 3
composer.json

@@ -20,17 +20,19 @@
         "aws/aws-sdk-php": "^2.4.9 || ^3.0",
         "doctrine/couchdb": "~1.0@dev",
         "elasticsearch/elasticsearch": "^7",
-        "mongodb/mongodb": "^1.8",
         "graylog2/gelf-php": "^1.4.2",
+        "mongodb/mongodb": "^1.8",
         "php-amqplib/php-amqplib": "~2.4 || ^3",
         "php-console/php-console": "^3.1.3",
-        "phpspec/prophecy": "^1.6.1",
+        "phpspec/prophecy": "^1.15",
+        "phpstan/phpstan": "^0.12.91",
         "phpunit/phpunit": "^8.5",
         "predis/predis": "^1.1",
         "rollbar/rollbar": "^1.3 || ^2 || ^3",
         "ruflin/elastica": ">=0.90@dev",
         "swiftmailer/swiftmailer": "^5.3|^6.0",
-        "phpstan/phpstan": "^0.12.91"
+        "symfony/mailer": "^5.4 || ^6",
+        "symfony/mime": "^5.4 || ^6"
     },
     "suggest": {
         "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",

+ 3 - 0
src/Monolog/Handler/SwiftMailerHandler.php

@@ -24,6 +24,7 @@ use Swift;
  * @author Gyula Sallai
  *
  * @phpstan-import-type Record from \Monolog\Logger
+ * @deprecated Since Monolog 2.6. Use SymfonyMailerHandler instead.
  */
 class SwiftMailerHandler extends MailHandler
 {
@@ -42,6 +43,8 @@ class SwiftMailerHandler extends MailHandler
     {
         parent::__construct($level, $bubble);
 
+        @trigger_error('The SwiftMailerHandler is deprecated since Monolog 2.6. Use SymfonyMailerHandler instead.', E_USER_DEPRECATED);
+
         $this->mailer = $mailer;
         $this->messageTemplate = $message;
     }

+ 111 - 0
src/Monolog/Handler/SymfonyMailerHandler.php

@@ -0,0 +1,111 @@
+<?php declare(strict_types=1);
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Logger;
+use Monolog\Utils;
+use Monolog\Formatter\FormatterInterface;
+use Monolog\Formatter\LineFormatter;
+use Symfony\Component\Mailer\MailerInterface;
+use Symfony\Component\Mailer\Transport\TransportInterface;
+use Symfony\Component\Mime\Email;
+
+/**
+ * SymfonyMailerHandler uses Symfony's Mailer component to send the emails
+ *
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * @phpstan-import-type Record from \Monolog\Logger
+ */
+class SymfonyMailerHandler extends MailHandler
+{
+    /** @var MailerInterface|TransportInterface */
+    protected $mailer;
+    /** @var Email|callable(string, Record[]): Email */
+    private $emailTemplate;
+
+    /**
+     * @psalm-param Email|callable(string, Record[]): Email $email
+     *
+     * @param MailerInterface|TransportInterface $mailer The mailer to use
+     * @param callable|Email                     $email  An email template, the subject/body will be replaced
+     */
+    public function __construct($mailer, $email, $level = Logger::ERROR, bool $bubble = true)
+    {
+        parent::__construct($level, $bubble);
+
+        $this->mailer = $mailer;
+        $this->emailTemplate = $email;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected function send(string $content, array $records): void
+    {
+        $this->mailer->send($this->buildMessage($content, $records));
+    }
+
+    /**
+     * Gets the formatter for the Swift_Message subject.
+     *
+     * @param string|null $format The format of the subject
+     */
+    protected function getSubjectFormatter(?string $format): FormatterInterface
+    {
+        return new LineFormatter($format);
+    }
+
+    /**
+     * Creates instance of Email to be sent
+     *
+     * @param  string        $content formatted email body to be sent
+     * @param  array         $records Log records that formed the content
+     *
+     * @phpstan-param Record[] $records
+     */
+    protected function buildMessage(string $content, array $records): Email
+    {
+        $message = null;
+        if ($this->emailTemplate instanceof Email) {
+            $message = clone $this->emailTemplate;
+        } elseif (is_callable($this->emailTemplate)) {
+            $message = ($this->emailTemplate)($content, $records);
+        }
+
+        if (!$message instanceof Email) {
+            $record = reset($records);
+            throw new \InvalidArgumentException('Could not resolve message as instance of Email or a callable returning it' . ($record ? Utils::getRecordMessageForException($record) : ''));
+        }
+
+        if ($records) {
+            $subjectFormatter = $this->getSubjectFormatter($message->getSubject());
+            $message->subject($subjectFormatter->format($this->getHighestRecord($records)));
+        }
+
+        if ($this->isHtmlBody($content)) {
+            if (null !== ($charset = $message->getHtmlCharset())) {
+                $message->html($content, $charset);
+            } else {
+                $message->html($content);
+            }
+        } else {
+            if (null !== ($charset = $message->getTextCharset())) {
+                $message->text($content, $charset);
+            } else {
+                $message->text($content);
+            }
+        }
+
+        return $message->date(new \DateTimeImmutable());
+    }
+}

+ 100 - 0
tests/Monolog/Handler/SymfonyMailerHandlerTest.php

@@ -0,0 +1,100 @@
+<?php declare(strict_types=1);
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Logger;
+use Monolog\Test\TestCase;
+use PHPUnit\Framework\MockObject\MockObject;
+use Symfony\Component\Mailer\MailerInterface;
+use Symfony\Component\Mime\Email;
+
+class SymfonyMailerHandlerTest extends TestCase
+{
+    /** @var MailerInterface&MockObject */
+    private $mailer;
+
+    public function setUp(): void
+    {
+        $this->mailer = $this
+            ->getMockBuilder(MailerInterface::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+    }
+
+    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 SymfonyMailerHandler($this->mailer, $callback);
+
+        $records = [
+            $this->getRecord(Logger::DEBUG),
+            $this->getRecord(Logger::INFO),
+        ];
+        $handler->handleBatch($records);
+    }
+
+    public function testMessageCanBeCustomizedGivenLoggedData()
+    {
+        // Wire Mailer to expect a specific Email with a customized Subject
+        $expectedMessage = new Email();
+        $this->mailer->expects($this->once())
+            ->method('send')
+            ->with($this->callback(function ($value) use ($expectedMessage) {
+                return $value instanceof Email
+                    && $value->getSubject() === 'Emergency'
+                    && $value === $expectedMessage;
+            }));
+
+        // Callback dynamically changes subject based on number of logged records
+        $callback = function ($content, array $records) use ($expectedMessage) {
+            $subject = count($records) > 0 ? 'Emergency' : 'Normal';
+            return $expectedMessage->subject($subject);
+        };
+        $handler = new SymfonyMailerHandler($this->mailer, $callback);
+
+        // Logging 1 record makes this an Emergency
+        $records = [
+            $this->getRecord(Logger::EMERGENCY),
+        ];
+        $handler->handleBatch($records);
+    }
+
+    public function testMessageSubjectFormatting()
+    {
+        // Wire Mailer to expect a specific Email with a customized Subject
+        $messageTemplate = new Email();
+        $messageTemplate->subject('Alert: %level_name% %message%');
+        $receivedMessage = null;
+
+        $this->mailer->expects($this->once())
+            ->method('send')
+            ->with($this->callback(function ($value) use (&$receivedMessage) {
+                $receivedMessage = $value;
+
+                return true;
+            }));
+
+        $handler = new SymfonyMailerHandler($this->mailer, $messageTemplate);
+
+        $records = [
+            $this->getRecord(Logger::EMERGENCY),
+        ];
+        $handler->handleBatch($records);
+
+        $this->assertEquals('Alert: EMERGENCY test', $receivedMessage->getSubject());
+    }
+}