Wysyłanie e-maili do administratorów
W celu zapewnienia wysokiej jakości informacji zwrotnej, administracja musi moderować wszystkie komentarze. Kiedy komentarz jest w stanie ham
lub potential_spam
, wiadomość e-mail powinna zostać wysłana do administratorów z dwoma linkami: jednym do zaakceptowania komentarza, a drugim do jego odrzucenia.
Ustawianie e-maila dla konta administracyjnego
Aby zapisać adres e-mail konta administracyjnego, użyj parametru kontenera. Dla celów demonstracyjnych, pozwalamy również na ustawienie go za pomocą zmiennej środowiskowej (nie powinna być potrzebna w "prawdziwym życiu"). Aby ułatwić wstrzykiwanie usług, które wymagają adresu e-mail konta administracyjnego, zdefiniuj również ustawienie kontenera 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
Zmienna środowiskowa może zostać "przetworzona" przed użyciem. W tym przypadku, używamy procesora default
, aby zwrócić domyślnie wartość parametru default_admin_email
, jeśli zmienna środowiskowa ADMIN_EMAIL
nie istnieje.
Wysyłanie powiadomień e-mail
Aby wysłać wiadomość e-mail, możesz wybrać pomiędzy kilkoma abstrakcjami klasy Email
; od klasy najniższego poziomu Message
do najwyższego poziomu NotificationEmail
. Prawdopodobnie najczęściej skorzystasz z klasy Email
, ale NotificationEmail
jest idealnym wyborem dla wewnętrznych wiadomości e-mail.
Przy obsłudze wiadomości zastąpmy reguły automatycznej walidacji:
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
jest kluczowym interfejsem pozwalającym na wysyłanie e-maili metodą send()
.
Aby wysłać wiadomość e-mail, potrzebujemy nadawcy (nagłówka From
/Sender
). Zamiast ustawiać go bezpośrednio w instancji klasy Email
, zdefiniuj ją globalnie:
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)%"
Rozszerzanie szablonu powiadomienia e-mail (ang. notification email template)
Szablon powiadomienia e-mail dziedziczy po domyślnym szablonie powiadomienia e-mail, który jest dostarczany wraz z Symfony:
Szablon nadpisuje kilka bloków, aby dostosować treść wiadomości e-mail i dodać kilka odnośników, które pozwalają administracji zaakceptować lub odrzucić komentarz. Każdy argument trasy (ang. route argument), który nie jest poprawnym parametrem trasy (ang. route parameter) jest dodawany jako element łańcucha zapytań (adres URL odrzucenia wygląda tak /admin/comment/review/42?reject=true
).
Domyślny szablonNotificationEmail
używa Inky zamiast HTML do projektowania wiadomości e-mail. Jest on pomocny w tworzeniu responsywnych wiadomości e-mail, które są kompatybilne z wszystkimi popularnymi klientami poczty elektronicznej.
W celu zapewnienia maksymalnej kompatybilności z czytnikami e-mail, podstawowy layout powiadomień domyślnie zawiera wszystkie arkusze stylów (poprzez pakiet CSS inliner).
Te dwie funkcje są częścią opcjonalnych rozszerzeń biblioteki Twig, które należy zainstalować:
1
$ symfony composer req "twig/cssinliner-extra:^3" "twig/inky-extra:^3"
Generowanie bezwzględnych adresów URL wewnątrz polecenia (ang. command)
W wiadomościach e-mail generuj adresy URL wykorzystując funkcję url()
zamiast path()
, jako że potrzebujesz bezwzględnych adresów (z protokołem i hostem).
E-mail jest wysyłany za pomocą obsługi wiadomości (ang. message handler) z poziomu konsoli. Generowanie bezwzględnych adresów URL z poziomu przeglądarki jest łatwiejsze, ponieważ znamy protokół i domenę strony, co nie ma miejsca w przypadku wywołania z poziomu konsoli.
Zdefiniuj nazwę domeny i protokół do użycia:
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
Zmienne środowiskowe SYMFONY_DEFAULT_ROUTE_HOST
i SYMFONY_DEFAULT_ROUTE_PORT
są lokalnie automatycznie ustawiane podczas korzystania z symfony
CLI i ustalane na podstawie konfiguracji na Platform.sh.
Wiązanie trasy (ang. route) z kontrolerem
Trasa review_comment
jeszcze nie istnieje, stwórzmy kontroler administracyjny, który ją obsłuży:
Adres URL recenzji komentarza rozpoczyna się od /admin/
w celu zabezpieczenia go zaporą sieciową zdefiniowaną w poprzednim kroku. Aby uzyskać dostęp do tego zasobu, administracja musi być uwierzytelniona.
Zamiast tworzyć instancję Response
, użyliśmy metody-skrótu render()
dostarczanej przez klasę bazową kontrolera AbstractController
.
Po zakończeniu weryfikacji, krótki szablon podziękuje administracji za jej ciężką pracę:
Wykorzystanie Mail Catcher
Zamiast używać "prawdziwego" serwera SMTP lub zewnętrznego dostawcy do wysyłania wiadomości e-mail, użyjmy narzędzia Mail Catcher zapewnianego przez serwer SMTP, który wiadomości e-mail odbiera, ale nie dostarcza, tylko udostępnia poprzez interfejs WWW. Na szczęście Symfony automatycznie skonfigurowało dla nas taki łapacz poczty:
Dostęp do panelu poczty (ang. webmail)
Możesz otworzyć panel poczty z terminala:
1
$ symfony open:local:webmail
albo z poziomu paska narzędzi do debugowania:
Prześlij komentarz. W interfejsie panelu poczty (ang. webmail) powinna pojawić się nowa wiadomość e-mail:
Kliknij na tytuł wiadomości e-mail w interfejsie i zaakceptuj lub odrzuć komentarz, jeśli uznasz to za stosowne:
Sprawdź logi poleceniem server:log
, jeśli coś nie działa zgodnie z oczekiwaniami.
Zarządzanie długo działającymi skryptami (ang. long-running scripts)
Posiadanie długo działających skryptów wiąże się z zachowaniami, których powinno się być świadomym. W przeciwieństwie do modelu PHP używanego dla HTTP, gdzie każde żądanie zaczyna się od czystego stanu, przetwarzanie wiadomości działa w tle w sposób ciągły. Każda obsługa komunikatu dziedziczy bieżący stan, w tym pamięć podręczną. Aby uniknąć jakichkolwiek problemów z Doctrine, wszystkie menadżery encji (ang. entity manager) są automatycznie czyszczone po przetworzeniu wiadomości. Sprawdź, czy twoje własne usługi wymagają tego samego, czy też nie.
Asynchroniczne wysyłanie wiadomości e-mail
Wysłanie e-mail poprzez obsługę wiadomości (ang. message handler) może zająć trochę czasu. Może nawet rzucić wyjątek. W przypadku rzucenia wyjątku podczas obsługi wiadomości, zostanie podjęta próba przetworzenia jej ponownie, ale zamiast próbować ponownie ją przetworzyć (ang. consume), lepiej byłoby po prostu spróbować ponownie wysłać wiadomość e-mail.
Wiemy już, jak to zrobić: wyślij wiadomość e-mail na szynę (ang. bus).
Instancja MailerInterface
wykonuje za nas ciężką pracę: gdy szyna jest zdefiniowana, przesyła (ang. dispatches) na nią wiadomości e-mail zamiast je wysyłać. Nie są wymagane żadne zmiany w naszym kodzie.
Szyna wysyła już e-mail asynchronicznie, zgodnie z domyślną konfiguracją Messengera:
Możemy używać tej samej metody transportu do przesyłania komentarzy i wiadomości e-mail, ale wcale nie musi tak być. Możesz zdecydować się na użycie innego transportu do zarządzania np. wiadomościami o różnych priorytetach. Korzystanie z różnych środków transportu pozwala również na wykorzystanie różnych maszyn robotników (ang. workers) obsługujących różne rodzaje komunikatów. Rozwiązanie to jest elastyczne i zależy tylko od Ciebie.
Testowanie wiadomości e-mail
Istnieje wiele sposobów testowania wiadomości e-mail.
Możesz napisać testy jednostkowe, jeśli napiszesz osobną klasę dla każdego typu wiadomości e-mail (np. poprzez rozszerzenie Email
lub TemplatedEmail
).
Jednak najczęstszymi testami, które napiszesz, są testy funkcjonalne, sprawdzające wyzwalanie wysyłania poczty przez niektóre działania, oraz prawdopodobnie testy dotyczące treści tych wiadomości e-mail, jeśli są dynamiczne.
Symfony dostarcza asercje (ang. assertions), które ułatwiają takie testy. Przykładowy test, który ilustruje różne możliwości:
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);
}
Asercje (ang. assertions) te działają zarówno dla e-maili wysyłanych synchronicznie jak i asynchronicznie.
Wysyłanie wiadomości e-mail poprzez Platform.sh
Nie ma konfiguracji przeznaczonej dla Platform.sh. Wszystkie konta posiadają domyślnie konto Sendgrid, które jest automatycznie używane do wysyłania wiadomości e-mail.
Note
Dla naszego bezpieczeństwa e-maile nie są domyślnie wysyłane na gałęziach innych niż master
. Włącz SMTP, jeśli wiesz, co robisz:
1
$ symfony cloud:env:info enable_smtp on