Slide 1

Slide 1 text

Symfony 5 Titouan GALOPIN

Slide 2

Slide 2 text

2 Titouan Galopin Product Manager SymfonyInsight insight.symfony.com

Slide 3

Slide 3 text

Agenda 1. Symfony 5 2. Messenger / Mercure 3. Mailer / Notifier 4. HttpClient 5. Autres nouveautés 3

Slide 4

Slide 4 text

4 1. Symfony 5

Slide 5

Slide 5 text

5

Slide 6

Slide 6 text

6 PHP 7.2+

Slide 7

Slide 7 text

7 Versionnement sémantique

Slide 8

Slide 8 text

8 Symfony 5 = 4.4 sans les dépréciations

Slide 9

Slide 9 text

9 Evolution, pas Révolution Symfony 5 est proche de Symfony 4

Slide 10

Slide 10 text

10 Mais Symfony 5, c’est aussi de nouvelles fonctionnalités

Slide 11

Slide 11 text

11 La démarche : rendre Symfony plus simple, agréable et productif

Slide 12

Slide 12 text

12 Comment ? DX, DX, DX

Slide 13

Slide 13 text

13 Symfony 5 reprend les bases de Symfony 4 et y intègre de nouveaux composants

Slide 14

Slide 14 text

14 L’objectif ? Construire des abstractions haut-niveau au dessus de nos abstractions bas-niveau

Slide 15

Slide 15 text

15 2. Messenger Mercure

Slide 16

Slide 16 text

16 L’asynchrone, c’est génial

Slide 17

Slide 17 text

17 Mais en fait, pourquoi ?

Slide 18

Slide 18 text

18 Depuis l’invention de PHP, deux modèles s’opposent

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

22 Et si on faisait les deux ?

Slide 23

Slide 23 text

23 L’asynchrone n’est pas utile partout

Slide 24

Slide 24 text

24 Les consumers

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

26 Envoyer des e-mails Générer des miniatures Synchroniser avec une API ...

Slide 27

Slide 27 text

27 Messenger = Une librairie pour créer et utiliser des consumers

Slide 28

Slide 28 text

28 composer require symfony/messenger

Slide 29

Slide 29 text

29 Process PHP Requête 1 Consumer PHP RabbitMQ, Redis, PDO, ... Consumer PHP Process PHP Requête 1 Process PHP Requête 1 ...

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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)

Slide 35

Slide 35 text

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)

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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)

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

39 php bin/console messenger:consume

Slide 40

Slide 40 text

40 Mais comment retourner un résultat au client ?

Slide 41

Slide 41 text

41 Mercure https://symfony.com/doc/current/mercure.html

Slide 42

Slide 42 text

42 Un binaire en Go Transmet les messages de votre consumer à vos clients

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

44 4. HttpClient

Slide 45

Slide 45 text

45 Comment fait-on une requête HTTP en PHP ?

Slide 46

Slide 46 text

46 fopen

Slide 47

Slide 47 text

47 $url = 'https://symfony.com/versions.json'; $response = fopen($url, 'rb'); $versions = json_decode( stream_get_contents($response), true );

Slide 48

Slide 48 text

48 Méthode ? Headers ? SSL ?

Slide 49

Slide 49 text

49 Compliqué ...

Slide 50

Slide 50 text

50 ext-curl

Slide 51

Slide 51 text

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);

Slide 52

Slide 52 text

52 Mieux ! Plus complet et fonctionne plutôt bien par défaut

Slide 53

Slide 53 text

53 Mais ... Complexe d’utilisation Difficile à mocker Requiert l’extension

Slide 54

Slide 54 text

54 ...

Slide 55

Slide 55 text

55 Les clients HTTP

Slide 56

Slide 56 text

56 Guzzle 3 Buzz Httplug Httpful PHP-HTTP PSR-18 Guzzle 5 Guzzle 6

Slide 57

Slide 57 text

57 Qui a déjà développé une librairie réutilisable utilisant un client HTTP ?

Slide 58

Slide 58 text

58 3 packages virtuels php-http/client-implementation:^1.0 php-http/client-implementation:^2.0 psr/http-client-implementation

Slide 59

Slide 59 text

59 Incompatibles entre eux donc impossible de créer un client pour les 3

Slide 60

Slide 60 text

60 PSR18 $url = 'https://symfony.com/versions.json'; $request = $client->createRequest('GET', $url); $response = $client->sendRequest($request); $versions = json_decode( $response->getBody()->getContents(), true );

Slide 61

Slide 61 text

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; }

Slide 62

Slide 62 text

62 Asynchrone ? HTTP 2/3 ? Push ? Streams ? Options et erreurs de transport ?

Slide 63

Slide 63 text

63 Non-défini dans les interfaces = Inutilisable

Slide 64

Slide 64 text

64 Peut-on améliorer la situation ?

Slide 65

Slide 65 text

65 composer require symfony/http-client

Slide 66

Slide 66 text

66 Fournit des décorateurs pour tous les packages virtuels

Slide 67

Slide 67 text

67 Symfony HttpClient = Un contrat = Une interface

Slide 68

Slide 68 text

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; }

Slide 69

Slide 69 text

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; }

Slide 70

Slide 70 text

70 use Symfony\Component\HttpClient\HttpClient; $client = HttpClient::create(); $url = 'https://symfony.com/versions.json'; $response = $client->request('GET', $url); $versions = $response->toArray();

Slide 71

Slide 71 text

71 Asynchrone

Slide 72

Slide 72 text

72 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

Slide 73

Slide 73 text

73 Streamable

Slide 74

Slide 74 text

74 $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()); }

Slide 75

Slide 75 text

75 HTTP 2 Push natif

Slide 76

Slide 76 text

76 Dans Symfony Service http_client Autowiring Configuration YAML

Slide 77

Slide 77 text

77 class MailchimpClient { private $httpClient; public function __construct(HttpClientInterface $httpClient) { $this->httpClient = $httpClient; } // ... }

Slide 78

Slide 78 text

78 3. Mailer Notifier

Slide 79

Slide 79 text

79 Symfony 5 : Fabriquer de nouvelles abstractions haut-niveau au dessus de celles bas-niveau

Slide 80

Slide 80 text

80 SwiftMailer => Symfony Mailer HttpClient Messenger Twig Dotenv

Slide 81

Slide 81 text

81 Grâce aux abstractions bas-niveau, on peut faire beaucoup plus de choses en moins de code

Slide 82

Slide 82 text

82 composer require symfony/mailer

Slide 83

Slide 83 text

83 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('

See Twig integration for better HTML integration!

'); $mailer->send($email); // ... } }

Slide 84

Slide 84 text

84 Dotenv Configurer le Mailer différemment selon l’environnement

Slide 85

Slide 85 text

85 // SMTP MAILER_DSN=smtp://user:[email protected]

Slide 86

Slide 86 text

86 // 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 // …

Slide 87

Slide 87 text

87 // 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'

Slide 88

Slide 88 text

88 // 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

Slide 89

Slide 89 text

89 Twig Des e-mails riches, responsives et compatibles avec tous les lecteurs

Slide 90

Slide 90 text

90 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', ]) ;

Slide 91

Slide 91 text

91 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 #} Logo

Slide 92

Slide 92 text

92 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')) %}

Welcome {{ username }}!

{# ... #} {% endapply %}

Slide 93

Slide 93 text

93 Inky : responsive et cross-platform composer require twig/extra-bundle twig/inky-extra {% apply inky_to_html %}

Welcome {{ email.toName }}!

{# ... #} {% endapply %}

Slide 94

Slide 94 text

94 Messenger Des e-mails envoyés en asynchrone

Slide 95

Slide 95 text

95 # config/packages/messenger.yaml framework: messenger: transports: async: "%env(MESSENGER_TRANSPORT_DSN)%" routing: 'Symfony\Component\Mailer\Messenger\SendEmailMessage': async

Slide 96

Slide 96 text

96 Les e-mails sont des messages ...

Slide 97

Slide 97 text

97 Les SMS sont des messages ...

Slide 98

Slide 98 text

98 Les notifications sont des messages ...

Slide 99

Slide 99 text

99 Symfony Notifier

Slide 100

Slide 100 text

100 Reprend les principes de Mailer et les adapte à d’autres méthodes d’envoi

Slide 101

Slide 101 text

101 class SmsController extends AbstractController { public function sendSms(TexterInterface $texter) { $sms = new SmsMessage('+33606060606', 'New subscription started!'); $texter->send($sms); // ... } }

Slide 102

Slide 102 text

102 class SmsController extends AbstractController { public function sendSms(TexterInterface $texter) { $sms = new SmsMessage('+33606060606', 'New subscription started!'); $texter->send($sms); // ... } } DSN : Twilio, nextmo, ...

Slide 103

Slide 103 text

103 class ChatController extends AbstractController { public function sendMessage(ChatterInterface $chatter) { $message = new ChatMessage('New subscription started!'); $chatter->send($message); // ... } }

Slide 104

Slide 104 text

104 class ChatController extends AbstractController { public function sendMessage(ChatterInterface $chatter) { $message = new ChatMessage('New subscription started!'); $chatter->send($message); // ... } } DSN : Slack, Telegram, ...

Slide 105

Slide 105 text

105 Une même infrastructure Abstraction haut-niveau (MailerInterface, TexterInterface, ChatterInterface, …) Votre code Messenger Twig Transport (Dotenv, HttpClient)

Slide 106

Slide 106 text

106 Une même infrastructure Abstraction haut-niveau (MailerInterface, TexterInterface, ChatterInterface, …) Votre code Messenger Twig Transport (Dotenv, HttpClient) Notifier

Slide 107

Slide 107 text

107 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]') ); // ... } }

Slide 108

Slide 108 text

108 5. Autres nouveautés

Slide 109

Slide 109 text

109 Composant String

Slide 110

Slide 110 text

110 composer require symfony/string

Slide 111

Slide 111 text

111 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('नमस्ते')) { // ... }

Slide 112

Slide 112 text

112 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('नमस्ते')) { // ... }

Slide 113

Slide 113 text

113 Locale-aware slugger

Slide 114

Slide 114 text

114 class SlugController extends AbstractController { public function index(SluggerInterface $slugger) { $slug = $slugger->slug('Стойността трябва да бъде лъжа'); // ... } }

Slide 115

Slide 115 text

115 class SlugController extends AbstractController { public function index(SluggerInterface $slugger) { $slug = $slugger->slug('Стойността трябва да бъде лъжа'); // ... } } Transliteration Basé sur la locale de la requête

Slide 116

Slide 116 text

116 Et bien d’autres ! https://symfony.com/blog/category/living-on-the-edge

Slide 117

Slide 117 text

insight.symfony.com // [email protected] Jeudi 9 juillet Mettre en place des process qualité avec Symfony Partons à la découverte des process qualité de Symfony en tant que framework pour les appliquer à nos propres applications et ainsi faciliter leur maintenance. Rendez-vous sur @symfonyinsight (Twitter) pour vous inscrire !

Slide 118

Slide 118 text

Merci ! 118 Des questions ? ▪ @titouangalopin sur Twitter ▪ titouan.galopin @symfony.com