Відправка електронної пошти адміністраторам
Щоб забезпечити високу якість відгуків, адміністратор має модерувати всі коментарі. Коли коментар знаходиться у стані ham
або potential_spam
, адміністратору слід відправити електронний лист з двома посиланнями: одне щоб прийняти коментар, а інше щоб відхилити його.
Встановлення адреси електронної пошти адміністратора
Для зберігання адреси електронної пошти використовуйте параметр контейнера. З метою демонстрації ми також дозволимо встановити її за допомогою змінної середовища (малоймовірно, що це знадобиться у "реальному житті"). Щоб полегшити впровадження у сервісах, яким потрібна електронна адреса адміністратора, визначте параметр контейнера у секції bind
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
--- a/config/services.yaml
+++ b/config/services.yaml
@@ -4,6 +4,7 @@
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:
+ default_admin_email: admin@example.com
services:
# default configuration for services in *this* file
@@ -13,6 +14,7 @@ services:
bind:
string $photoDir: "%kernel.project_dir%/public/uploads/photos"
string $akismetKey: "%env(AKISMET_KEY)%"
+ string $adminEmail: "%env(string:default:default_admin_email:ADMIN_EMAIL)%"
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
Змінна середовища може бути "оброблена" перед її використанням. Тут ми використовуємо процесор default
, щоб повернутися до значення параметра default_admin_email
, якщо змінної середовища ADMIN_EMAIL
не існує.
Відправка повідомлення електронної пошти
Щоб надіслати електронний лист, ви можете вибирати між кількома абстракціями класу Email
; від Message
, найнижчого рівня, до NotificationEmail
, найвищого. Ви, ймовірно, найчастіше будете використовувати клас Email
, але NotificationEmail
— це ідеальний вибір для внутрішньої електронної пошти.
Замінімо логіку автоматичної перевірки в обробнику повідомлень:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
--- a/src/MessageHandler/CommentMessageHandler.php
+++ b/src/MessageHandler/CommentMessageHandler.php
@@ -7,6 +7,8 @@ use App\Repository\CommentRepository;
use App\SpamChecker;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
+use Symfony\Bridge\Twig\Mime\NotificationEmail;
+use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Workflow\WorkflowInterface;
@@ -18,15 +20,19 @@ class CommentMessageHandler implements MessageHandlerInterface
private $commentRepository;
private $bus;
private $workflow;
+ private $mailer;
+ private $adminEmail;
private $logger;
- public function __construct(EntityManagerInterface $entityManager, SpamChecker $spamChecker, CommentRepository $commentRepository, MessageBusInterface $bus, WorkflowInterface $commentStateMachine, LoggerInterface $logger = null)
+ public function __construct(EntityManagerInterface $entityManager, SpamChecker $spamChecker, CommentRepository $commentRepository, MessageBusInterface $bus, WorkflowInterface $commentStateMachine, MailerInterface $mailer, string $adminEmail, LoggerInterface $logger = null)
{
$this->entityManager = $entityManager;
$this->spamChecker = $spamChecker;
$this->commentRepository = $commentRepository;
$this->bus = $bus;
$this->workflow = $commentStateMachine;
+ $this->mailer = $mailer;
+ $this->adminEmail = $adminEmail;
$this->logger = $logger;
}
@@ -51,8 +57,13 @@ class CommentMessageHandler implements MessageHandlerInterface
$this->bus->dispatch($message);
} elseif ($this->workflow->can($comment, 'publish') || $this->workflow->can($comment, 'publish_ham')) {
- $this->workflow->apply($comment, $this->workflow->can($comment, 'publish') ? 'publish' : 'publish_ham');
- $this->entityManager->flush();
+ $this->mailer->send((new NotificationEmail())
+ ->subject('New comment posted')
+ ->htmlTemplate('emails/comment_notification.html.twig')
+ ->from($this->adminEmail)
+ ->to($this->adminEmail)
+ ->context(['comment' => $comment])
+ );
} elseif ($this->logger) {
$this->logger->debug('Dropping comment message', ['comment' => $comment->getId(), 'state' => $comment->getState()]);
}
MailerInterface
— це основна точка входу, вона дозволяє відправляти електронні листи за допомогою методу send()
Щоб відправити електронний лист, нам потрібен відправник (заголовок From
/Sender
). Замість того щоб встановлювати його явно в екземплярі Email, визначте його глобально:
1 2 3 4 5 6 7 8
--- a/config/packages/mailer.yaml
+++ b/config/packages/mailer.yaml
@@ -1,3 +1,5 @@
framework:
mailer:
dsn: '%env(MAILER_DSN)%'
+ envelope:
+ sender: "%env(string:default:default_admin_email:ADMIN_EMAIL)%"
Розширення шаблону повідомлення електронної пошти
Шаблон повідомлення електронної пошти успадковується від шаблону повідомлення електронної пошти за замовчуванням, який поставляється разом із Symfony:
Шаблон перевизначає кілька блоків, щоб налаштувати електронний лист і додати деякі посилання, які дозволяють адміністратору прийняти або відхилити коментар. Будь-який аргумент маршруту, який не є валідним параметром маршруту, додається як елемент рядка запиту (URL-адреса відхилення виглядає як /admin/comment/review/42?reject=true
).
Шаблон NotificationEmail
за замовчуванням використовує Inky замість HTML для розмітки електронних листів. Це допомагає створювати адаптивні електронні листи, сумісні з усіма популярними поштовими клієнтами.
Для максимальної сумісності з програмами-читачами електронної пошти, базовий макет повідомлень вбудовує всі таблиці стилів (за допомогою пакету CSS inliner) за замовчуванням.
Ці дві функції є частиною додаткових розширень Twig, які необхідно встановити:
1
$ symfony composer req "twig/cssinliner-extra:^3" "twig/inky-extra:^3"
Генерування абсолютних URL-адрес у команді Symfony
В електронних листах генеруйте URL-адреси за допомогою url()
замість path()
, оскільки вам потрібні абсолютні шляхи (зі схемою і хостом).
Електронний лист відправляється з обробника повідомлень у контексті консолі. Генерувати абсолютні URL-адреси у веб-контексті простіше, оскільки ми знаємо схему та домен поточної сторінки. Це не стосується контексту консолі.
Визначте доменне ім’я та схему для явного використання:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
--- a/config/services.yaml
+++ b/config/services.yaml
@@ -5,6 +5,11 @@
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:
default_admin_email: admin@example.com
+ default_domain: '127.0.0.1'
+ default_scheme: 'http'
+
+ router.request_context.host: '%env(default:default_domain:SYMFONY_DEFAULT_ROUTE_HOST)%'
+ router.request_context.scheme: '%env(default:default_scheme:SYMFONY_DEFAULT_ROUTE_SCHEME)%'
services:
# default configuration for services in *this* file
Змінні середовища SYMFONY_DEFAULT_ROUTE_HOST
і SYMFONY_DEFAULT_ROUTE_PORT
автоматично встановлюються локально під час використання symfony
CLI й визначаються на основі конфігурації у Platform.sh.
Підключення маршруту до контролера
Маршрут review_comment
ще не існує, створімо адміністративний контролер для його обробки:
URL-адреса перевірки коментаря починається з /admin/
, щоб захистити його за допомогою брандмауера, визначеного на попередньому кроці. Адміністратору необхідно пройти аутентифікацію, щоб отримати доступу до цього ресурсу.
Замість створення екземпляра Response
, ми використовували render()
, метод швидкого доступу, що надається базовим класом контролера AbstractController
.
Коли перевірка завершена, адміністратор бачить повідомлення з подякою за його копітку роботу, за допомогою короткого шаблону:
Використання Mail Catcher
Замість того щоб використовувати "реальний" SMTP-сервер чи стороннього постачальника для відправки електронних листів, використовуймо засіб перехоплення пошти. Перехоплювач надає SMTP-сервер, який не доставляє електронні листи, але натомість робить їх доступними через веб-інтерфейс: На щастя Symfony вже автоматично налаштував для нас засіб перехоплення пошти:
Доступ до веб-служби електронної пошти
Ви можете відкрити веб-службу електронної пошти з термінала:
1
$ symfony open:local:webmail
Або з панелі інструментів веб-налагодження:
Відправте коментар, ви маєте отримати електронний лист в інтерфейсі веб-служби електронної пошти:
Натисніть на заголовок електронного листа в інтерфейсі та прийміть або відхиліть коментар на ваш розсуд:
Перевірте журнали за допомогою server:log
, якщо це не працює належним чином.
Керування довго виконуваними сценаріями
Наявність довго виконуваних сценаріїв супроводжується поведінкою, про яку ви маєте знати. На відміну від моделі PHP, що використовується для HTTP, де кожен запит починається з чистого стану, споживач повідомлення працює безперервно у фоновому режимі. Кожна обробка повідомлення успадковує поточний стан, включаючи кеш пам'яті. Щоб уникнути будь-яких проблем із Doctrine, її менеджери сутностей автоматично очищаються після обробки повідомлення. Ви маєте перевірити, чи потрібно вашим власним сервісам робити те ж саме чи ні.
Відправка електронних листів асинхронно
Відправка електронного листа, відправленого в обробник повідомлень, може зайняти деякий час. Може навіть статися виняток. У разі виникнення винятку, під час обробки повідомлення, воно буде відправлено повторно. Але замість того щоб намагатися повторно опрацювати повідомлення коментаря, було б краще просто повторити спробу відправки електронного листа.
Ми вже знаємо, як це зробити: відправити повідомлення електронної пошти до шини.
Екземпляр MailerInterface
виконує копітку роботу: коли шина визначена, він направляє повідомлення електронної пошти до неї, а не відправляє їх. Жодні зміни у вашому коді не потрібні.
Шина вже відправляє електронний лист асинхронно відповідно до конфігурації Messenger за замовчуванням:
Навіть якщо ми використовуємо той самий транспорт для повідомлень коментарів і повідомлень електронної пошти, це не обов'язково має бути так. Наприклад, ви можете використовувати інший транспорт для керування різними пріоритетами повідомлень. Використання різних типів транспорту також дає вам можливість мати різні робочі комп'ютери, які обробляють різні види повідомлень. Це дуже гнучко і залежить тільки від вас.
Тестування електронних листів
Існує багато способів тестування електронних листів.
Ви можете написати модульні тести, якщо напишете клас для кожного електронного листа (наприклад, наслідуючи Email
або TemplatedEmail
).
Однак найбільш поширені тести, які ви будете писати, — це функціональні тести, які перевіряють, чи призводять певні дії до відправки електронного листа, і, ймовірно, тести на вміст електронних листів, якщо вони динамічні.
Symfony постачається із твердженнями що полегшують таке тестування, ось приклад тесту, який демонструє деякі можливості:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
public function testMailerAssertions()
{
$client = static::createClient();
$client->request('GET', '/');
$this->assertEmailCount(1);
$event = $this->getMailerEvent(0);
$this->assertEmailIsQueued($event);
$email = $this->getMailerMessage(0);
$this->assertEmailHeaderSame($email, 'To', 'fabien@example.com');
$this->assertEmailTextBodyContains($email, 'Bar');
$this->assertEmailAttachmentCount($email, 1);
}
Ці твердження працюють, коли електронні листи відправляються синхронно або асинхронно.
Відправка електронних листів у Platform.sh
Для Platform.sh немає конкретної конфігурації. Всі облікові записи поставляються з обліковим записом SendGrid, який автоматично використовується для відправки електронних листів.
Note
Про всяк випадок, електронні листи за замовчуванням відправляються лише у гілці master
. Увімкніть SMTP явно в гілках, які не є master
, якщо ви знаєте, що робите:
1
$ symfony cloud:env:info enable_smtp on