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
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
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
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;
}
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
Slide 72
Slide 72 text
72
Options du transport défini dans
l’interface : créer des décorateurs
réellement découplés est possible
Slide 73
Slide 73 text
73
Asynchrone
Slide 74
Slide 74 text
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
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
Slide 79
Slide 79 text
79
Dans Symfony
Service http_client
Autowiring
Configuration YAML
Slide 80
Slide 80 text
80
class MailchimpClient
{
private $httpClient;
public function __construct(HttpClientInterface $httpClient)
{
$this->httpClient = $httpClient;
}
// ...
}
Slide 81
Slide 81 text
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
Slide 82
Slide 82 text
82
3.
Mailer
Notifier
Slide 83
Slide 83 text
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
Slide 86
Slide 86 text
86
composer require symfony/mailer
Slide 87
Slide 87 text
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('
See Twig integration for better HTML integration!
');
$mailer->send($email);
// ...
}
}
Slide 88
Slide 88 text
88
Dotenv
Configurer le Mailer
différemment selon
l’environnement
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'
Slide 92
Slide 92 text
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
Slide 93
Slide 93 text
93
Twig
Des e-mails riches,
responsives et compatibles
avec tous les lecteurs
Slide 94
Slide 94 text
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',
])
;
Slide 95
Slide 95 text
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 #}
104
Reprend les principes de
Mailer et les adapte à
d’autres méthodes d’envoi
Slide 105
Slide 105 text
105
class SmsController extends AbstractController
{
public function sendSms(TexterInterface $texter)
{
$sms = new SmsMessage('+33606060606', 'New subscription started!');
$texter->send($sms);
// ...
}
}
Slide 106
Slide 106 text
106
class SmsController extends AbstractController
{
public function sendSms(TexterInterface $texter)
{
$sms = new SmsMessage('+33606060606', 'New subscription started!');
$texter->send($sms);
// ...
}
}
DSN : Twilio,
nextmo, ...
Slide 107
Slide 107 text
107
class ChatController extends AbstractController
{
public function sendMessage(ChatterInterface $chatter)
{
$message = new ChatMessage('New subscription started!');
$chatter->send($message);
// ...
}
}
Slide 108
Slide 108 text
108
class ChatController extends AbstractController
{
public function sendMessage(ChatterInterface $chatter)
{
$message = new ChatMessage('New subscription started!');
$chatter->send($message);
// ...
}
}
DSN : Slack,
Telegram, ...
Slide 109
Slide 109 text
109
Une même infrastructure
Abstraction
haut-niveau
(MailerInterface,
TexterInterface,
ChatterInterface, …)
Votre
code
Messenger
Twig
Transport
(Dotenv,
HttpClient)
Slide 110
Slide 110 text
110
Une même infrastructure
Abstraction
haut-niveau
(MailerInterface,
TexterInterface,
ChatterInterface, …)
Votre
code
Messenger
Twig
Transport
(Dotenv,
HttpClient)
Notifier
Slide 111
Slide 111 text
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]')
);
// ...
}
}
Slide 112
Slide 112 text
112
5.
Autres
nouveautés
Slide 113
Slide 113 text
113
Composant String
Slide 114
Slide 114 text
114
composer require symfony/string
Slide 115
Slide 115 text
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('नमस्ते')) {
// ...
}
Slide 116
Slide 116 text
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('नमस्ते')) {
// ...
}
Slide 117
Slide 117 text
117
Locale-aware slugger
Slide 118
Slide 118 text
118
class SlugController extends AbstractController
{
public function index(SluggerInterface $slugger)
{
$slug = $slugger->slug('Стойността трябва да бъде
лъжа');
// ...
}
}
Slide 119
Slide 119 text
119
class SlugController extends AbstractController
{
public function index(SluggerInterface $slugger)
{
$slug = $slugger->slug('Стойността трябва да бъде
лъжа');
// ...
}
} Transliteration
Basé sur la locale de la requête