When Symfony
met Promises
Hello!
I am Marc Morera
Owner of Apisearch and
php.coach
You can find me at:
@mmoreram
2
Summary
◎ Regular PHP applications
◎ PHP-PM
◎ ReactPHP principles
◎ ReactPHP server
◎ Reactive domain
◎ ReactPHP Symfony Kernel
◎ ReactPHP Symfony Server
3
1.
Regular PHP
applications
… until now
4
Let’s split our applications in 3 zones
FrameworkServer Domain + I/O
5
Server
HTTP
Server
New
Kernel
handle(req)
6
Server
An HTTP server take one
HTTP request, we instance
a new Kernel, and handle
a transformed PSR
request. We return an
HTTP Response
7
Server
An HTTP server take one
HTTP request, we
instance a new Kernel,
and handle a transformed
PSR request. We return an
HTTP Response
8
$kernel = new Kernel($env, $debug);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
9
Framework
Routing
Controller
View (?)
10
Framework
The framework decides
what controller should be
executed given a route,
and what view should
render, given a controller.
11
$controller = $event->getController();
$arguments = $event->getArguments();
// call controller
$response = $controller(...$arguments);
12
Domain + I/O
Controller/
domain/
Redis/
13
Domain + I/O
We have our domain,
where all happens. Our
classes, our chosen
libraries and our business
logic. We can place I/O like
Redis calls in this group
14
$element = $repository->find('123');
$element->changeSomething();
$repository->save($element);
// I'm done
15
PHP
Is a slow, A very slow, an
extremely slow language.
16
PHP slowness reasons
◎ ORMs hydration
◎ ORMs lazy loading
◎ ORMs themselves
◎ ORMs ...
17
PHP slowness reasons
◎ ORMs hydration
◎ ORMs lazy loading
◎ ORMs themselves
◎ ORMs …
◎ Kernel recreation
18
“
Rebuilding the Kernel means
rebuilding all used services,
including clients
19
~10ms
Nginx 1.10 + PHP7.3-FPM
// 100 reqs / sec / thread
20
A/B benchmark - c(50), n(1000)
Nginx PHP-PM ReactPHP
50% 53ms - -
75% 58ms - -
95% 63ms - -
21
A/B benchmark - c(100), n(5000)
Nginx PHP-PM ReactPHP
50% 98ms - -
75% 111ms - -
95% 182ms - -
22
2.
PHP-PM
… as a natural step
23
PHP-PM
PHP-PM is a process
manager, supercharger
and load balancer for PHP
applications.
(Github repository description)
24
PHP-PM
PHP-PM
N
ReactPHP
threads
(N cores)
APP
25
require {
“php-pm/php-pm”: “*”,
“php-pm/httpkernel-adapter”: “*”
}
26
vendor/bin/ppm start 
--host=0.0.0.0 
--port=80 
--bootstrap=symfony 
--app-env=prod 
--debug=0 
--workers 20
27
A/B benchmark - c(50), n(1000)
Nginx PHP-PM ReactPHP
50% 53ms 27ms -
75% 58ms 31ms -
95% 63ms 55ms -
28
Goods
- Already works with N
ReactPHP servers
- This N is variable
- Static content
- Easy to install and configure
- Adapter for Symfony kernel
built-in
- No need of Nginx
PHP-PM
Bads ?
29
Goods
- Already works only with N
ReactPHP servers
- This N is variable
- Static content
- Easy to install and configure
- Adapter for Symfony kernel
built-in
- No need of Nginx
PHP-PM
Bads
- Each ReactPHP server act as
a regular sync server. One
request after the last
- 10 servers + 10 long time
requests = fail
- Code too complex to work
with
30
3.
ReactPHP principles
… as a change of paradigm
31
32
Async
Your code that includes,
somewhere, a result of an
I/O operation, will return
something. Eventually.
33
// t=0
$result = $redis->get(‘something’);
// t=1
34
// t=0
$promise = $redis
->get(‘something’)
->then(function($result) {
// t=1
});
// t=0
35
// t=0
$promise = $redis
->get(‘something’)
->then(function($result) use ($client) {
// t=1
return $client->hget(“hash”, $result);
})
->then(function($hashResult) {
// t=2
});
// t=0
36
Non
Blocking
An event loop treats all your
Promises in a non-blocking
way.
37
// t=0
$promise = $httpClient
->get(“http://slowpage.es”)
->then(function($result) use ($client) {
// t=10
// Do whatever
});
// t=0
38
// t=0
$promise = $redis
->get(‘something’)
->then(function($result) use ($client) {
// t=1
// Do whatever
});
->then(function($result) use ($client) {
// t=6
// Do whatever
});
// t=0
// t=0
$promise = $httpClient
->get(“http://slowpage.es”)
->then(function($result) use ($client) {
// t=10 (Third resolved)
// Do whatever
});
// t=0
39
// t=0
$promise = $redis
->get(‘something’)
->then(function($result) use ($client) {
// t=1 (First resolved)
// Do whatever
});
->then(function($result) use ($client) {
// t=6 (Second resolved)
// Do whatever
});
// t=0
Promise
A class that can be fulfilled
(value?) or rejected
(Exception)
40
$promise = $redis
->get(‘somevalue’)
->then(function($value) {
// Everything OK
}, function(Exception $e) {
// Exception was thrown
});
41
Promise
A promise is fulfilled when it
returns nothing, a simple
value or another Fulfilled
promise
42
$promise = $redis
->get(‘somevalue’)
->then(function($value) {
// Everything OK
return ‘OK’;
});
43
$promise = $redis
->get(‘somevalue’)
->then(function($value) use ($redis) {
// Everything OK
return $redis->hgetall(‘hash’);
});
44
Promise
You can concat Promises in
order to make your code
pretty easy to read
45
$promise = $redis
->get(‘somevalue’)
->then(function($value) use ($redis) {
// Everything OK
return $redis->hgetall(‘hash’);
})
->then(function($hashList) {
return count($hashList);
});
46
Event Loop
The loop can manage as
many promises as you can,
even generated from other
libraries (Redis, Mysql...)
47
// Ridiculous simplification of a Loop
while (queue.waitForMessage()) {
queue.processNextMessage();
}
48
ReactPHP libraries
Some of them are part of the core of the application
◎ /http - HTTPS server for ReactPHP
◎ /promises - Promises implementation
◎ /event-loop - EventLoop implementation
◎ /stream - PHP Streams wrapper for ReactPHP
◎ /socket - Socket server and connections for ReactPHP
49
ReactPHP libraries
And some of them just side libraries to use on top of it
◎ Redis client
◎ Mysql client
◎ AMQP client
◎ Filesystem
◎ HTTP Client
◎ DNS Resolver
◎ Cache
50
4.
ReactPHP server
… as a reduction of the solution
51
PHP-PM
PHP-PM
N
ReactPHP
threads
(N cores)
APP
52
PHP-PM
1 ReactPHP
thread
APP
53
$kernel = new Kernel();
$http = new Server(function (ServerRequestInterface $httpRequest) use ($kernel) {
return new Promise(function ($resolve, $reject) use ($httpRequest, $kernel) {
$symfonyRequest = $this->toSymfonyRequest($httpRequest);
$symfonyResponse = $kernel->handle($symfonyRequest);
$httpResponse = $this->toHttpResponse($symfonyResponse);
$resolve($httpResponse);
});
});
$http->listen($socket);
$loop->run();
54
$kernel = new Kernel();
$http = new Server(function (ServerRequestInterface $httpRequest) use ($kernel) {
return new Promise(function ($resolve, $reject) use ($httpRequest, $kernel) {
$symfonyRequest = $this->toSymfonyRequest($httpRequest);
$symfonyResponse = $kernel->handle($symfonyRequest);
$httpResponse = $this->toHttpResponse($symfonyResponse);
$resolve($httpResponse);
});
});
$http->listen($socket);
$loop->run();
55
$controller = $event->getController();
$arguments = $event->getArguments();
// call controller
$response = $controller(...$arguments);
56
$kernel = new Kernel();
$http = new Server(function (ServerRequestInterface $httpRequest) use ($kernel) {
return new Promise(function ($resolve, $reject) use ($httpRequest, $kernel) {
$symfonyRequest = $this->toSymfonyRequest($httpRequest);
// t=0
$symfonyResponse = $kernel->handle($symfonyRequest);
// t=1
$httpResponse = $this->toHttpResponse($symfonyResponse);
$resolve($httpResponse);
});
});
$http->listen($socket);
$loop->run();
57
5.
Reactive domain
… as our first BIG change
58
Some
changes
Let’ see how we can turn our
domain a little bit reactive.
This is a slow step by step
process
59
Controller Domain Repository
60
$entity = $client->find(‘123’);
$entity->disable();
$client->insert($entity);
return $entity;
return $client
->find(‘123’)
->then(function($entity) use ($client) {
$entity->disable();
return $client->insert($entity);
});
Controller Domain Repository
61
$entity = $repository->disable($entity);
$eventDispatcher->dispatch(
‘entity.disabled’,
new EntityWasDisabled($entity)
);
return;
Return $repository
->disable($entity)
->then(function($entity) use ($eventDispatcher) {
$eventDispatcher->dispatch(
‘entity.disabled’,
new EntityWasDisabled($entity)
);
});
Controller Domain Repository
62
$this
->entityDisabler
->disableEntity($entity);
return JsonResponse(“Entity disabled”);
return $this
->entityDisabler
->disableEntity($entity)
->then(function() {
return new JsonResponse(‘Entity disabled”);
});
“
Controller in Symfony must return
a Response object or an array if
you use Twig
63
Let’s split our applications in 3 zones
Framework
Sync
Server
Async
Domain + I/O
Async
64
Controller Domain Repository
65
$this
->entityDisabler
->disableEntity($entity);
return JsonResponse(“Entity disabled”);
$promise = $this
->entityDisabler
->disableEntity($entity)
->then(function() {
return new JsonResponse(‘Entity disabled”);
});
return Blockawait($promise); // Async to Sync
6.
ReactPHP Symfony
Kernel
… as the solution
66
$controller = $event->getController();
$arguments = $event->getArguments();
// call controller
$response = $controller(...$arguments);
67
$controller = $event->getController();
$arguments = $event->getArguments();
// call controller
return $controller(...$arguments)
->then(function($response) {
// Do whatever
});
Symfony
Async
Kernel
Working with an async
kernel in Symfony mean
some specific changes
68
Let’s split our applications in 3 zones
Framework
Async
Server
Async
Domain + I/O
Async
69
“
Controllers must return a Promise,
which eventually will return a
Response or an Array
70
“
Event listeners can return a
Promise as part of your domain.
Check inside and outside the
Promise effects
71
/**
* Handle get Response.
*
* @param GetResponseEvent $event
*
* @return PromiseInterface
*/
public function handleGetResponsePromiseA(GetResponseEvent $event)
{
$promise = (new FulfilledPromise())
->then(function () use ($event) {
// This line is executed eventually after the previous listener
// promise is fulfilled
$event->setResponse(new Response('A'));
});
// This line is executed before the first event listener promise is
// fulfilled
return $promise;
}
72
“
Each I/O operation must be
performed by using Reactive
libraries (filesystem, Redis, Http
requests, Mysql, RabbitMQ...)
73
“
A single sync I/O operation will
make your whole application sync
74
Symfony
Async
Kernel
https://github.com/apisearch-io/symfony-async-kernel
https://github.com/apisearch-io/symfony-react-demo
75
7.
ReactPHP Symfony
server
… as the main server
76
Let’s split our applications in 3 zones
Framework
Async
Server
Async
Domain + I/O
Async
77
Async
Server
https://github.com/apisearch-io/symfony-react-server
78
require {
"apisearch-io/symfony-async-http-kernel": "^0.1",
"apisearch-io/symfony-react-server": "^0.1"
}
79
use SymfonyComponentHttpKernelAsyncKernel;
class Kernel extends AsyncKernel
{
use MicroKernelTrait;
80
php vendor/bin/server 
0.0.0.0:80 
--dev 
--debug 
--non-blocking
81
A/B benchmark - c(1), n(1000)
Nginx PHP-PM ReactPHP
50% 8ms 1ms 1ms
75% 10ms 2ms 1ms
95% 12ms 5ms 2ms
82
A/B benchmark - c(20), n(1000)
Nginx PHP-PM ReactPHP
50% 19ms 17ms 14ms
75% 23ms 20ms 16ms
95% 34ms 23ms 18ms
83
A/B benchmark - c(50), n(1000)
Nginx PHP-PM ReactPHP
50% 85ms 37ms 22ms
75% 90ms 45ms 23ms
95% 107ms ~1s 25ms
84
A/B benchmark - c(200), n(10000)
Nginx PHP-PM ReactPHP
50% - 45ms 23ms
75% - 53ms 27ms
95% - 65ms 31ms
85
A/B benchmark - c(300), n(10000)
Nginx PHP-PM ReactPHP
50% - 40ms 24ms
75% - 68ms 25ms
95% - 80ms 31ms
86
Next steps
The future of PHP will be async, so you better be ready for
that
◎ Work for a Promises PSR. Let frameworks depend on
an interface instead of an implementation
◎ Start doing some serious stuff with Promises in PHP
◎ We should start seeing some live projects on top of
ReactPHP - Apisearch soon!
87
Next steps
◎ Some libraries should be adapted to Reactive
programming, or we should create new (DBAL, Logger,
Elasticsearch client…)
◎ Start talking a little bit more about this, because a
language is not it’s code, but how far his community
can go.
88
Thanks!
Any questions?
You can find me at:
@mmoreram &
yuhu@mmoreram.com
89

When symfony met promises