Skip to content
  • About
    • What is Symfony?
    • Community
    • News
    • Contributing
    • Support
  • Documentation
    • Symfony Docs
    • Symfony Book
    • Screencasts
    • Symfony Bundles
    • Symfony Cloud
    • Training
  • Services
    • Platform.sh for Symfony Best platform to deploy Symfony apps
    • SymfonyInsight Automatic quality checks for your apps
    • Symfony Certification Prove your knowledge and boost your career
    • SensioLabs Professional services to help you with Symfony
    • Blackfire Profile and monitor performance of your apps
  • Other
  • Blog
  • Download
sponsored by
  1. Home
  2. Documentation
  3. Symfony: The Fast Track
  4. Polish
  5. Wysyłanie e-maili do administratorów

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:

templates/emails/comment_notification.html.twig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{% extends '@email/default/notification/body.html.twig' %}

{% block content %}
    Author: {{ comment.author }}<br />
    Email: {{ comment.email }}<br />
    State: {{ comment.state }}<br />

    <p>
        {{ comment.text }}
    </p>
{% endblock %}

{% block action %}
    <spacer size="16"></spacer>
    <button href="{{ url('review_comment', { id: comment.id }) }}">Accept</button>
    <button href="{{ url('review_comment', { id: comment.id, reject: true }) }}">Reject</button>
{% endblock %}

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:

src/Controller/AdminController.php
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
49
50
51
52
53
namespace App\Controller;

use App\Entity\Comment;
use App\Message\CommentMessage;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Workflow\Registry;
use Twig\Environment;

class AdminController extends AbstractController
{
    private $twig;
    private $entityManager;
    private $bus;

    public function __construct(Environment $twig, EntityManagerInterface $entityManager, MessageBusInterface $bus)
    {
        $this->twig = $twig;
        $this->entityManager = $entityManager;
        $this->bus = $bus;
    }

    #[Route('/admin/comment/review/{id}', name: 'review_comment')]
    public function reviewComment(Request $request, Comment $comment, Registry $registry): Response
    {
        $accepted = !$request->query->get('reject');

        $machine = $registry->get($comment);
        if ($machine->can($comment, 'publish')) {
            $transition = $accepted ? 'publish' : 'reject';
        } elseif ($machine->can($comment, 'publish_ham')) {
            $transition = $accepted ? 'publish_ham' : 'reject_ham';
        } else {
            return new Response('Comment already reviewed or not in the right state.');
        }

        $machine->apply($comment, $transition);
        $this->entityManager->flush();

        if ($accepted) {
            $this->bus->dispatch(new CommentMessage($comment->getId()));
        }

        return new Response($this->twig->render('admin/review.html.twig', [
            'transition' => $transition,
            'comment' => $comment,
        ]));
    }
}

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ę:

templates/admin/review.html.twig
1
2
3
4
5
6
7
8
{% extends 'base.html.twig' %}

{% block body %}
    <h2>Comment reviewed, thank you!</h2>

    <p>Applied transition: <strong>{{ transition }}</strong></p>
    <p>New state: <strong>{{ comment.state }}</strong></p>
{% endblock %}

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:

docker-compose.override.yml
1
2
3
4
5
6
services:
###> symfony/mailer ###
  mailer:
    image: schickling/mailcatcher
    ports: [1025, 1080]
###< symfony/mailer ###

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:

config/packages/messenger.yaml
1
2
3
4
5
6
7
8
9
framework:
    messenger:
        routing:
            Symfony\Component\Mailer\Messenger\SendEmailMessage: async
            Symfony\Component\Notifier\Message\ChatMessage: async
            Symfony\Component\Notifier\Message\SmsMessage: async

            # Route your messages to the transports
            App\Message\CommentMessage: async

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

Idąc dalej

  • Samouczek SymfonyCasts Mailer;
  • Dokumentacja szablonów Inky;
  • Procesory zmiennych środowiskowych;
  • Dokumentacja Symfony Framework Mailer;
  • Dokumentacja Platform.sh dotycząca e-maili.
Previous page Podejmowanie decyzji przy użyciu komponentu Workflow
Next page Użycie pamięci podręcznej w celu zwiększenia wydajności
This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.
TOC
    Version

    Symfony 5.4 is backed by

    Get your Symfony expertise recognized

    Get your Symfony expertise recognized

    Peruse our complete Symfony & PHP solutions catalog for your web development needs.

    Peruse our complete Symfony & PHP solutions catalog for your web development needs.

    Version:
    Locale:
    ebook

    This book is backed by:

    see all backers

    Symfony footer

    Avatar of Simon Sargeant, a Symfony contributor

    Thanks Simon Sargeant for being a Symfony contributor

    1 commit • 8 lines changed

    View all contributors that help us make Symfony

    Become a Symfony contributor

    Be an active part of the community and contribute ideas, code and bug fixes. Both experts and newcomers are welcome.

    Learn how to contribute

    Symfony™ is a trademark of Symfony SAS. All rights reserved.

    • What is Symfony?

      • What is Symfony?
      • Symfony at a Glance
      • Symfony Components
      • Symfony Releases
      • Security Policy
      • Logo & Screenshots
      • Trademark & Licenses
      • symfony1 Legacy
    • Learn Symfony

      • Symfony Docs
      • Symfony Book
      • Reference
      • Bundles
      • Best Practices
      • Training
      • eLearning Platform
      • Certification
    • Screencasts

      • Learn Symfony
      • Learn PHP
      • Learn JavaScript
      • Learn Drupal
      • Learn RESTful APIs
    • Community

      • Symfony Community
      • SymfonyConnect
      • Events & Meetups
      • Projects using Symfony
      • Contributors
      • Symfony Jobs
      • Backers
      • Code of Conduct
      • Downloads Stats
      • Support
    • Blog

      • All Blog Posts
      • A Week of Symfony
      • Case Studies
      • Cloud
      • Community
      • Conferences
      • Diversity
      • Living on the edge
      • Releases
      • Security Advisories
      • Symfony Insight
      • Twig
      • SensioLabs Blog
    • Services

      • SensioLabs services
      • Train developers
      • Manage your project quality
      • Improve your project performance
      • Host Symfony projects

      Powered by

    Follow Symfony