Share
Embed
Transcript
124 slides
-
Agenda 1. Symfony 5 2. Messenger / Mercure 3. Mailer
/ Notifier 4. HttpClient 5. Autres nouveautés 3 -
13 Symfony 5 reprend les bases de Symfony 4 et
y intègre de nouveaux composants -
14 L’objectif ? Construire des abstractions haut-niveau au dessus de
nos abstractions bas-niveau -
19 Java, node, … Un process réutilisé Process Java, node,
... Requête 1 PHP Un process par reqûete Évolution du temps Requête 2 Requête 3 Requête 4 ... Process PHP Requête 1 Requête 2 Requête 3 Requête 4 ... Process PHP Process PHP Process PHP -
20 Java, node, … Un process réutilisé PHP Un process
par reqûete État partagé = Plus performant Sans état = Plus facile à maintenir et à développer -
21 Java, node, … Un process réutilisé PHP Un process
par reqûete État partagé = Plus performant Sans état = Plus facile à maintenir et à développer Avantage technique Avantage humain -
25 PHP Un process par reqûete ... + un traitement
en arrière-plan quand c’est utile Process PHP Requête 1 Requête 2 Requête 3 Process PHP Process PHP Process Consumer PHP -
29 Process PHP Requête 1 Consumer PHP RabbitMQ, Redis, PDO,
... Consumer PHP Process PHP Requête 1 Process PHP Requête 1 ... -
30 Process PHP Requête 1 Consumer PHP RabbitMQ, Redis, PDO,
... Consumer PHP Process PHP Requête 1 Process PHP Requête 1 ... Messenger utilise un intermédiaire de traitement des messages -
31 Process PHP Requête 1 Consumer PHP RabbitMQ, Redis, PDO,
... Consumer PHP Process PHP Requête 1 Process PHP Requête 1 ... Messenger vous aide à créer des Messages depuis vos process HTTP -
32 Process PHP Requête 1 Consumer PHP RabbitMQ, Redis, PDO,
... Consumer PHP Process PHP Requête 1 Process PHP Requête 1 ... Et à traiter ces Messages grâce à des Handlers utilisés par les Consumers -
33 framework: messenger: failure_transport: failed transports: async: dsn: '%env(MESSENGER_TRANSPORT_DSN)%' retry_strategy:
max_retries: 3 delay: 1000 multiplier: 2 failed: 'doctrine://default?queue_name=failed' routing: 'Symfony\Component\Mailer\Messenger\SendEmailMessage': async 'App\Messenger\Mailchimp\MailchimpSynchronizeMessage': async -
34 framework: messenger: failure_transport: failed transports: async: dsn: '%env(MESSENGER_TRANSPORT_DSN)%' retry_strategy:
max_retries: 3 delay: 1000 multiplier: 2 failed: 'doctrine://default?queue_name=failed' routing: 'Symfony\Component\Mailer\Messenger\SendEmailMessage': async 'App\Messenger\Mailchimp\MailchimpSynchronizeMessage': async Utiliser de l’asynchrone pour ces messages (Messenger peut aussi fonctionner en synchrone) -
35 framework: messenger: failure_transport: failed transports: async: dsn: '%env(MESSENGER_TRANSPORT_DSN)%' retry_strategy:
max_retries: 3 delay: 1000 multiplier: 2 failed: 'doctrine://default?queue_name=failed' routing: 'Symfony\Component\Mailer\Messenger\SendEmailMessage': async 'App\Messenger\Mailchimp\MailchimpSynchronizeMessage': async Réessayer 3 fois (1 seconde, 2 secondes et 4 secondes plus tard) -
36 framework: messenger: failure_transport: failed transports: async: dsn: '%env(MESSENGER_TRANSPORT_DSN)%' retry_strategy:
max_retries: 3 delay: 1000 multiplier: 2 failed: 'doctrine://default?queue_name=failed' routing: 'Symfony\Component\Mailer\Messenger\SendEmailMessage': async 'App\Messenger\Mailchimp\MailchimpSynchronizeMessage': async Si un message ne fonctionne finalement pas, le stocker dans une table -
37 public function index(MessageBusInterface $bus) { // ... $this->bus->dispatch(new MailchimpAddEmailMessage($email));
} Votre contrôleur N’importe quel objet créé par vous (et contenant donc toutes les informations que vous voulez) -
38 use Symfony\Component\Messenger\Handler\MessageHandlerInterface; class MailchimpSynchronizeHandler implements MessageHandlerInterface { public function
__invoke(MailchimpAddEmailMessage $message) { // ... } } Votre handler Le handler à utiliser est déterminé grâce au typehint du message Le handler est enregistré grâce à l’auto-configuration -
42 Un binaire en Go Transmet les messages de votre
consumer à vos clients -
43 Process PHP Client Consumer PHP Envoi de l’e-mail RabbitMQ,
Redis, PDO, ... Réponse simple Message d’e-mail Message d’e-mail Message Mercure Process Mercure Message Mercure Requête initiale Connexion W S à M ercure Message Mercure -
47 $url = 'https://symfony.com/versions.json'; $response = fopen($url, 'rb'); $versions =
json_decode( stream_get_contents($response), true ); -
51 $url = 'https://symfony.com/versions.json'; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $versions = json_decode(curl_exec($ch), true); curl_close($ch); -
60 PSR18 $url = 'https://symfony.com/versions.json'; $request = $client->createRequest('GET', $url); $response
= $client->sendRequest($request); $versions = json_decode( $response->getBody()->getContents(), true ); -
61 interface ClientInterface { /** * Sends a PSR-7 request
and returns a PSR-7 response. * * @param RequestInterface $request * * @return ResponseInterface * * @throws \Psr\Http\Client\ClientExceptionInterface If an error happens * while processing the request. */ public function sendRequest(RequestInterface $request): ResponseInterface; } -
62 Asynchrone ? HTTP 2/3 ? Push ? Streams ?
Options et erreurs de transport ? -
68 interface HttpClientInterface { public const OPTIONS_DEFAULTS = [...]; public
function request( string $method, string $url, array $options = [] ): ResponseInterface; public function stream( $responses, float $timeout = null ): ResponseStreamInterface; } -
69 interface ResponseInterface { public function getStatusCode(): int; public function
getHeaders($throw = true): array; public function getContent($throw = true): string; public function toArray($throw = true): array; public function cancel(): void; public function getInfo(): array; } -
70 use Symfony\Component\HttpClient\HttpClient; $client = HttpClient::create(); $url = 'https://symfony.com/versions.json'; $response
= $client->request('GET', $url); $versions = $response->toArray(); -
71 /** * @throws TransportExceptionInterface * When a network error
occurs * * @throws RedirectionExceptionInterface * On a 3xx when $throw is true and the * "max_redirects" option has been reached * * @throws ClientExceptionInterface * On a 4xx when $throw is true * * @throws ServerExceptionInterface * On a 5xx when $throw is true */ Comportement en cas d’erreur du transport défini dans l’interface -
72 Options du transport défini dans l’interface : créer des
décorateurs réellement découplés est possible -
74 use Symfony\Component\HttpClient\HttpClient; $client = HttpClient::create(); $responses = []; for
($i = 1; $i <= 100; $i++) { $responses[] = $client->request('GET', 'https://example.com/?p='.$i); } foreach ($responses as $response) { // block until completion of $response // but monitor all other responses meanwhile echo $response->getContent(); } ResponseInterface lazy -
76 $url = 'http://.../ubuntu-18.04.1-desktop-amd64.iso'; $response = $client->request('GET', $url); // Responses
are lazy! $h = fopen('./ubuntu.iso', 'wb'); foreach ($client->stream($response) as $chunk) { fwrite($h, $chunk->getContent()); } -
78 4 décorateurs natifs • ScopingHttpClient Configuration d’options par défaut
par domaine • MockHttpClient Client qui n’effectue pas les requêtes • CachingHttpClient Ajoute la gestion du cache HTTP • HttplugClient / Psr18Client Pour la compatibilité avec les packages virtuels -
80 class MailchimpClient { private $httpClient; public function __construct(HttpClientInterface $httpClient)
{ $this->httpClient = $httpClient; } // ... } -
81 framework: http_client: max_host_connections: 4 default_options: # ... scoped_clients: github_client:
base_uri: https://api.github.com headers: Authorization: token abc123 # creates the HttpClientInterface $githubClient autowiring alias # and the HttpClientInterface $scopingHttpClient one -
83 Symfony 5 : Fabriquer de nouvelles abstractions haut-niveau au
dessus de celles bas-niveau -
85 Grâce aux abstractions bas-niveau, on peut faire beaucoup plus
de choses en moins de code -
87 class MailerController extends AbstractController { public function sendEmail(MailerInterface $mailer)
{ $email = (new Email()) ->from('[email protected]') ->to('[email protected]') ->subject('Time for Symfony Mailer!') ->text('Sending emails is fun again!') ->html('<p>See Twig integration for better HTML integration!</p>'); $mailer->send($email); // ... } } -
90 // SMTP MAILER_DSN=smtp://user:[email protected] // Sendgrid // composer require symfony/sendgrid-mailer
MAILER_DSN=smtp://$SENDGRID_KEY@sendgrid // Gmail // composer require symfony/google-mailer MAILER_DSN=smtp://$USERNAME:$PASSWORD@gmail // … -
91 // SMTP MAILER_DSN=smtp://user:[email protected] // Sendgrid // composer require symfony/sendgrid-mailer
MAILER_DSN=smtp://$SENDGRID_KEY@sendgrid // Gmail // composer require symfony/google-mailer MAILER_DSN=smtp://$USERNAME:$PASSWORD@gmail // … // High Availability: si le premier a une erreur, utiliser le deuxième MAILER_DSN='api://id@postmark || smtp://key@sendgrid' // Load Balancing: répartir les mails sur chaque provider MAILER_DSN='api://id@postmark && smtp://key@sendgrid' -
92 // SMTP MAILER_DSN=smtp://user:[email protected] // Sendgrid // composer require symfony/sendgrid-mailer
MAILER_DSN=smtp://$SENDGRID_KEY@sendgrid // Gmail // composer require symfony/google-mailer MAILER_DSN=smtp://$USERNAME:$PASSWORD@gmail // … // High Availability: si le premier a une erreur, utiliser le deuxième MAILER_DSN='api://id@postmark || smtp://key@sendgrid' // Load Balancing: répartir les mails sur chaque provider MAILER_DSN='api://id@postmark && smtp://key@sendgrid' HttpClient -
94 use Symfony\Bridge\Twig\Mime\TemplatedEmail; use Symfony\Component\Mime\Address; $email = (new TemplatedEmail()) ->from('[email protected]')
->to(new Address('[email protected]')) ->subject('Thanks for signing up!') // path of the Twig template to render ->htmlTemplate('emails/signup.html.twig') // pass variables (name => value) to the template ->context([ 'expiration_date' => new \DateTime('+7 days'), 'username' => 'foo', ]) ; -
95 Embarquer les images # config/packages/twig.yaml twig: paths: # point
this wherever your images live '%kernel.project_dir%/assets/images': images {# '@images/' refers to the Twig namespace #} <img src="{{ email.image('@images/logo.png') }}" alt="Logo"> -
96 Inliner le CSS composer require twig/extra-bundle twig/cssinliner-extra # config/packages/twig.yaml
twig: paths: '%kernel.project_dir%/assets/css: css {% apply inline_css(source('@css/email.css')) %} <h1>Welcome {{ username }}!</h1> {# ... #} {% endapply %} -
97 Inky : responsive et cross-platform composer require twig/extra-bundle twig/inky-extra
{% apply inky_to_html %} <container> <row class="header"> <columns> <spacer size="16"></spacer> <h1 class="text-center">Welcome {{ email.toName }}!</h1> </columns> {# ... #} </row> </container> {% endapply %} -
104 Reprend les principes de Mailer et les adapte à
d’autres méthodes d’envoi -
105 class SmsController extends AbstractController { public function sendSms(TexterInterface $texter)
{ $sms = new SmsMessage('+33606060606', 'New subscription started!'); $texter->send($sms); // ... } } -
106 class SmsController extends AbstractController { public function sendSms(TexterInterface $texter)
{ $sms = new SmsMessage('+33606060606', 'New subscription started!'); $texter->send($sms); // ... } } DSN : Twilio, nextmo, ... -
107 class ChatController extends AbstractController { public function sendMessage(ChatterInterface $chatter)
{ $message = new ChatMessage('New subscription started!'); $chatter->send($message); // ... } } -
108 class ChatController extends AbstractController { public function sendMessage(ChatterInterface $chatter)
{ $message = new ChatMessage('New subscription started!'); $chatter->send($message); // ... } } DSN : Slack, Telegram, ... -
109 Une même infrastructure Abstraction haut-niveau (MailerInterface, TexterInterface, ChatterInterface, …)
Votre code Messenger Twig Transport (Dotenv, HttpClient) -
110 Une même infrastructure Abstraction haut-niveau (MailerInterface, TexterInterface, ChatterInterface, …)
Votre code Messenger Twig Transport (Dotenv, HttpClient) Notifier -
111 class NotificationController extends AbstractController { public function send(NotifierInterface $notifier)
{ $notification = new Notification( 'New subscription started!', ['sms', 'chat/slack', 'email'] ); $notifier->send( $notification, new Receiver('[email protected]') ); // ... } } -
115 use Symfony\Component\String\UnicodeString; $text = (new UnicodeString('This is a déjà-vu
situation.')) ->trimEnd('.') ->replace('déjà-vu', 'jamais-vu') ->append('!'); // $text = 'This is a jamais-vu situation!' $content = new UnicodeString('नमस्ते दु नया'); if ($content->ignoreCase()->startsWith('नमस्ते')) { // ... } -
116 use Symfony\Component\String\UnicodeString; $text = u('This is a déjà-vu situation.')
->trimEnd('.') ->replace('déjà-vu', 'jamais-vu') ->append('!'); // $text = 'This is a jamais-vu situation!' $content = u('नमस्ते दु नया'); if ($content->ignoreCase()->startsWith('नमस्ते')) { // ... } -
118 class SlugController extends AbstractController { public function index(SluggerInterface $slugger)
{ $slug = $slugger->slug('Стойността трябва да бъде лъжа'); // ... } } -
119 class SlugController extends AbstractController { public function index(SluggerInterface $slugger)
{ $slug = $slugger->slug('Стойността трябва да бъде лъжа'); // ... } } Transliteration Basé sur la locale de la requête -
121 Avant # config/packages/security.yaml security: # ... encoders: App\Entity\User: algorithm:
'bcrypt' Après # config/packages/security.yaml security: # ... encoders: App\Entity\User: algorithm: 'auto' -
Merci ! 124 Des questions ? ▪ @titouangalopin sur Twitter
▪ titouan.galopin @symfony.com