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.
Packaging the Monolith - PHP Tek 2024 (Breaking it down one bite at a time)
New Symfony 4.x and eZ Platform Features
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