The document discusses several new features in Symfony 4.x that can be used in eZ Platform v3.x, including Symfony Flex, the Lock Component, and the Messenger Component. It also covers the Symfony Maker Bundle, Cache Component improvements, the new HttpClient Component, and options for the HttpClient. Finally, it mentions the Symfony Mime Component for creating emails.
Symfony 4.0 + - Track Technique eZ Roadshow 2019 - PARIS
1. NewinSymfony4.xFeatures to take advantage of in eZ Platform v3.xโจ
โฆ and eZ Platform v2.5 for back ported features
Andrรฉ Rรธmcke
@andrerom
Nicolas Grekas
@nicolasgrekas
Expert Sponsor : Avec le support de :Roadshow Sponsor :
3. Some Honorable mentions
โข Symfony Flex - 3.4/4.0
New, more advance way to handle projects and packages in Symfony.
โข Lock Component - 3.4/4.0
Guarantee exclusive access to some shared resource. For example to ensure that a command
is not executed more than once at the same time (on the same or across different servers).
โข Messenger Component - 4.1
Provides a message bus with the ability to send messages and then handle them immediately
in your application or send them through transports (e.g. queues) to be handled later on.
13. 4.2: Cache Stampede Protection at expiry
$cache->get($key, $callback, $beta = null)
Probabilistic early expiration:
โข Between not favor (0), & highly favor (INF) early expiry
โขBy default 1.0
โข This will affect TTL on save
โข Protects against lots of cache expiring at same time
14. New in Symfony Cache 4.3
โข Optimized TagAware Redis & FileSystem adapters
โข Stores tags as โrelationโ => Avoids tag lookups on reads
โข File => Symlinks for Tags
โข Redis => Redis Set datatype for tags
โข Memcached? => No control over eviction of tags
Part of eZ Platform v2.5,
contributed to Symfony 4.3
15. [Digression] New in eZ Platform 2.5: In-Memory cache
เน Domain specific, so not contributed to Symfony
เน Content (Content, Location, UrlAlias) is cached for 300ms
เน Meta data (Type, Section, User, Language) is cached for 3s
เน In both cases limit of 100 objects & LFU to pick what to clear
In sum 2.5 has greatly improved performance!
Especially when using Redis!
19. by @nicolasgrekas & @andrerom
namespace SymfonyContractsHttpClient;โจ
โจ
// Some pieces removed to not spoil the talkโจ
โจ
interface ResponseInterfaceโจ
{โจ
public function getStatusCode(): int;โจ
public function getHeaders(): array;โจ
public function getContent(): string;โจ
public function toArray(): array;โจ
}โจ
20. by @nicolasgrekas & @andrerom
The resiliency of the Web relies on redirects, youโre part of it
Robust and failsafe by default
/**โจ
* Gets the HTTP headers of the response.โจ
*โจ
* @param bool $throw Whether an exception should be thrown on 3/4/5xx status codesโจ
*โจ
* @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โจ
*/โจ
public function getHeaders(bool $throw = true): array;
But what happens when the redirection limit is reached? or on 4xx / 5xx? Yours to decide:
21. by @nicolasgrekas & @andrerom
Abstraction is fully decoupled
from the component
Stateless (autowired) service
22. by @nicolasgrekas & @andrerom
Headers? Body? Options to the rescue
Options, there are a lot
of them!
24. by @nicolasgrekas & @andrerom
Optionstotherescue
'auth_basic' => null, // array|string - an array containing the username as first value, and optionally theโจ
// password as the second one; or string like username:password - enabling HTTP Basicโจ
// authentication (RFC 7617)โจ
'auth_bearer' => null, // string - a token enabling HTTP Bearer authorization (RFC 6750)โจ
'query' => [], // string[] - associative array of query string values to merge with the request's URLโจ
'headers' => [], // iterable|string[]|string[][] - headers names provided as keys or as part of valuesโจ
'body' => '', // array|string|resource|Traversable|Closure - the callback SHOULD yield a stringโจ
// smaller than the amount requested as argument; the empty string signals EOF; whenโจ
// an array is passed, it is meant as a form payload of field names and valuesโจ
'json' => null, // array|JsonSerializable - when set, implementations MUST set the "body" option toโจ
// the JSON-encoded value and set the "content-type" headers to a JSON-compatibleโจ
// value it is they are not defined - typically "application/json"โจ
'user_data' => null, // mixed - any extra data to attach to the request (scalar, callable, object...) thatโจ
// MUST be available via $response->getInfo('user_data') - not used internallyโจ
'max_redirects' => 20, // int - the maximum number of redirects to follow; a value lower or equal to 0 meansโจ
// redirects should not be followed; "Authorization" and "Cookie" headers MUSTโจ
// NOT follow except for the initial host nameโจ
'http_version' => null, // string - defaults to the best supported version, typically 1.1 or 2.0โจ
'base_uri' => null, // string - the URI to resolve relative URLs, following rules in RFC 3986, section 2โจ
'buffer' => true, // bool - whether the content of the response should be buffered or notโจ
'on_progress' => null, // callable(int $dlNow, int $dlSize, array $info) - throwing any exceptions MUST abortโจ
// the request; it MUST be called on DNS resolution, on arrival of headers and onโจ
// completion; it SHOULD be called on upload/download of data and at least 1/sโจ
'resolve' => [], // string[] - a map of host to IP address that SHOULD replace DNS resolutionโจ
'proxy' => null, // string - by default, the proxy-related env vars handled by curl SHOULD be honoredโจ
'no_proxy' => null, // string - a comma separated list of hosts that do not require a proxy to be reachedโจ
'timeout' => null, // float - the inactivity timeout - defaults to ini_get('default_socket_timeout')โจ
'bindto' => '0', // string - the interface or the local socket to bind toโจ
'verify_peer' => true, // see https://php.net/context.ssl for the following optionsโจ
'verify_host' => true,โจ
'cafile' => null,โจ
'capath' => null,โจ
'local_cert' => null,โจ
'local_pk' => null,โจ
'passphrase' => null,โจ
'ciphers' => null,โจ
'peer_fingerprint' => null,โจ
'capture_peer_cert_chain' => false,โจ
'extra' => [], // array - additional options that can be ignored if unsupported, unlike regular optionsโจ
25. by @nicolasgrekas & @andrerom
โข proxy โ get through an HTTP proxy
โข on_progress โ display a progress bar / abort a request
โข base_uri โ resolve relative URLs / build a scoped client
โข resolve โ protect webhooks against calls to internal endpoints
โข peer_fingerprint โ pin public keys of remote certificates
โข max_redirects โ disable or limit redirects
Showcase of some options
26. by @nicolasgrekas & @andrerom
โข body โ array|string|resource|Traversable|Closure
Options: Streameable uploads using body
$client->request('POST', 'http://upload.example.com', [โจ
'body' => $mimeParts->toIterable(), // e.g. symfony/mime (in a bit)โจ
]);โจ
27. by @nicolasgrekas & @andrerom
Options: Streameable downloads
$url = 'http://releases.ubuntu.com/18.04.1/ubuntu-18.04.1-desktop-amd64.iso';โจ
$response = $client->request('GET', $url, [โจ
'on_progress' => 'dump', // let's spam the console for funโจ
'buffer' => false, // skip buffering the response in memoryโจ
]);โจ
โจ
// Responses are lazy! This waits only for the headersโจ
if (200 !== $response->getStatusCode()) {โจ
throw new Exception('...');โจ
}โจ
โจ
$h = fopen('./ubuntu.iso', 'w');โจ
foreach ($client->stream($response) as $chunk) {โจ
fwrite($h, $chunk->getContent());โจ
}โจ
28. by @nicolasgrekas & @andrerom
Responses are lazy & requests are concurrent
for ($i = 0; $i < 379; ++$i) {โจ
$uri = "https://http2.akamai.com/demo/tile-$i.png";โจ
$responses[] = $client->request('GET', $uri);โจ
}โจ
โจ
foreach ($responses as $response) {
// block until completion of $response
// but monitor all other responses meanwhileโจ
$response->getContent();
}
29. by @nicolasgrekas & @andrerom
Asynchronous requests
for ($i = 0; $i < 379; ++$i) {โจ
$uri = "https://http2.akamai.com/demo/tile-$i.png";โจ
$responses[] = $client->request('GET', $uri);โจ
}โจ
โจ
foreach ($client->stream($responses) as $response => $chunk) {โจ
if ($chunk->isLast()) {โจ
// $response completedโจ
} else {โจ
// $response's got network activity or timeoutโจ
}โจ
}
โข Note: $client->stream($responses, 0.0);
379 requests
completed in
0,4 s!
Max number of
seconds to wait
before yielding a
timeout chunk
30. by @nicolasgrekas & @andrerom
Symfony Provided Clients
โข NativeHttpClient โ The most portable, uses PHPโs HTTP stream wrapper
โข CurlHttpClient โ Multiplexing, uses HTTP/2 and PUSH when available
Decorators:
โข ScopingHttpClient โ Auto-configure the options based on the request URL
โข MockHttpClient โ A client that doesn't make actual HTTP requests
โข CachingHttpClient โ Adds caching on top of an HTTP client
โข Psr18Client โ You already know about it
โข with TraceableHttpClient and record & replay coming
31. by @nicolasgrekas & @andrerom
Usage with FrameworkBundle / Autowiring
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โจ
36. use SymfonyComponentMimeEmail;
$email = (new Email())
->from('fabien@symfony.com')
->to('fabien@sensiolabs.com')
->subject('Some subject')
->text('Some text message')
->html('<b>Some HTML message</b>')
->attach('doc.txt')
;
The basics
38. $email = (new Email())
->text('Some text message')
->html('<b>Some HTML message</b>')
;
$email = (new Swift_Message())
->setBody('Some text message')
->addPart('<b>Some HTML message</b>', 'text/html')
;
A better data object model
16k serialized
38 objects
complex serialization
"fixed" headers
2k serialized
7 objects
simple serialization
"dynamic" headers
42. $email = (new Email())
->from('fabien@symfony.com')
->to('fabien@sensiolabs.com')
->subject('Some subject')
->text('Some text')
->html('<b>The new logo: <img src="cid:logo.jpg"></b>')
->embedFromPath('logo-small.jpg', 'logo.jpg')
;
Embeds
43. use SymfonyBridgeTwigMimeBodyRenderer;
use SymfonyBridgeTwigMimeTemplatedEmail;
use TwigEnvironment;
use TwigLoaderFilesystemLoader;
use SymfonyComponentMimeNamedAddress;
$twig = new Environment($loader = new FilesystemLoader(__DIR__.'/templates'));
$loader->addPath(__DIR__.'/images', 'images');
$email = (new TemplatedEmail())
->from('fabien@symfony.com')
->to(new NamedAddress('fabien@sensiolabs.com', 'Fabien'))
->text('Some text content')
->htmlTemplate('simple.html.twig')
->context([
'city' => 'Lille'
])
;
$renderer = new BodyRenderer($twig);
$renderer->render($email);
echo $email->toString();
Native integration with Twig
44. <p>
Welcome <b>{{ email.toName }}</b> from {{ city }}!
</p>
<p>
<img src="{{ email.image('@images/photo.jpg') }}">
</p>
Native integration with Twig
SymfonyBridgeTwigMime
WrappedTemplatedEmail
Twig template name
Template context
56. use SymfonyComponentMailerBridgeAmazoneSmtpSesTransport;
use SymfonyComponentMailerBridgeGoogleSmtpGmailTransport;
use SymfonyComponentMailerBridgeMailchimpSmtpMandrillTransport;
use SymfonyComponentMailerBridgeMailgunSmtpMailgunTransport;
use SymfonyComponentMailerBridgePostmarkSmtpPostmarkTransport;
use SymfonyComponentMailerBridgeSendgridSmtpSendgridTransport;
new SesTransport('user', 'pass', 'eu-west-1');
new GmailTransport('user', 'pass');
new MandrillTransport('user', 'pass');
new MailgunTransport('user', 'pass');
new PostmarkTransport(โkey');
new SendgridTransport('key');
SMTP: Shortcuts
59. SMTP / HTTP / API?
SMTP HTTP API
Offline Yes via Mailcatcher No / Mock No / Mock
Mailcatcher Yes No No
Standard Yes No No
Symfony generated Yes Yes No
Fast No Yes Yes
64. // Under the hood this is done when async
$transport = Transport::fromDsn('smtp://localhost'));
$transport->send($email);
โจ
$bus->dispatch($email);
Messages via Messenger
65. Emails are sent async via AMQP
framework:
messenger:
routing:
'SymfonyComponentMailerEnvelopedMessage': amqp
Emails are sent immediately
framework:
messenger:
routing:
#'SymfonyComponentMailerEnvelopedMessage': amqp
Sync or Async,your choice