Symfony has one component called The Event Dispatcher. This component is one implementation of Mediator pattern and it’s widely used in modern frameworks, such as Symfony. Silex, as a part of Symfony, also uses this component and we can easily use it in our projects. Let me show you one little example. Imagine one simple route in Silex to create one png file containing one text:
$app->get("/gd/{text}", function($text) { $path = "/tmp/qr.png." . uniqid(); $im = imagecreate(90, 30); $background = imagecolorallocate($im, 255, 255, 255); $color = imagecolorallocate($im, 0, 0, 0); imagestring($im, 5, 5, 5, $text, $color); imagepng($im, $path); imagedestroy($im); return $app->sendFile($path); });
It works, but there’s one mistake. We need to unlink our temporally file $path, but where? We need do if after “return $app->sendFile($path);” but it’s not possible.
$app->get("/gd/{text}", function($text) { $path = "/tmp/qr.png." . uniqid(); $im = imagecreate(90, 30); $background = imagecolorallocate($im, 255, 255, 255); $color = imagecolorallocate($im, 0, 0, 0); imagestring($im, 5, 5, 5, $text, $color); imagepng($im, $path); imagedestroy($im); return $app->sendFile($path, 200, ['Content-Type' => 'image/png']);; unlink($path); // unreachable code });
We can use BinaryFileResponse instead of the helper function “sendFile”, but there’s one smarter solution: The event dispatcher.
use Symfony\Component\HttpKernel\KernelEvents; $app->get("/gd/{text}", function($text) use (app) { $im = imagecreate(90, 30); $path = "/tmp/qr.png." . uniqid(); $background = imagecolorallocate($im, 255, 255, 255); $color = imagecolorallocate($im, 0, 0, 0); imagestring($im, 5, 5, 5, $text, $color); imagepng($im, $path); imagedestroy($im); $app['dispatcher']->addListener(KernelEvents::TERMINATE, function() use ($path) { unlink($path); }); return $app->sendFile($path, 200, ['Content-Type' => 'image/png']); });
(Updated! thanks to Hakin’s recommendation)
Or even better using Silex’s Filters. In this case we after or finish. In fact those filters are nothing more than an elegant way to speak to the event dispatcher.
$app->get("/gd/{text}", function($text) use (app) { $im = imagecreate(90, 30); $path = "/tmp/qr.png." . uniqid(); $background = imagecolorallocate($im, 255, 255, 255); $color = imagecolorallocate($im, 0, 0, 0); imagestring($im, 5, 5, 5, $text, $color); imagepng($im, $path); imagedestroy($im); $app->after(function() use ($path) { unlink($path); }); return $app->sendFile($path, 200, ['Content-Type' => 'image/png']); });
We also can use the generic function to add events to the event listener:
use Symfony\Component\HttpKernel\KernelEvents; $app->get("/gd/{text}", function($text) use (app) { $im = imagecreate(90, 30); $path = "/tmp/qr.png." . uniqid(); $background = imagecolorallocate($im, 255, 255, 255); $color = imagecolorallocate($im, 0, 0, 0); imagestring($im, 5, 5, 5, $text, $color); imagepng($im, $path); imagedestroy($im); $app->on(KernelEvents::TERMINATE, function() use ($path) { unlink($path); }); return $app->sendFile($path, 200, ['Content-Type' => 'image/png']); });
Now our temporally file will be deleted once a response is sent. Life is simpler with event dispatcher 🙂
You could also use $app->after(…); or $app->get()->after(); It uses the dispatcher too and is probably better readable.
That’s what I like to write this posts. I always learn something new. Txs!
It’s exactly what I was looking for, thanks!
One question though, what if I used Ajax to retrieve the content, and use $app->after()…
The ajax request would wait for the response to be finished “receiving” data right?
It depends on the event that you are using. The answer is here (read the description of each event): https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpKernel/KernelEvents.php
Thank you for your answer.
I’am actually trying to execute some actions after sending the response (using KernelEvents::TERMINATE) and I though it was executed after the response was processed in my success callback.
But, by adding a sleep() in the callback passed to $app->on(), it turned out that the ajax call was waiting for the whole response : success & complete are called after the callback.
Did I do something wrong? Do you have any idea? I don’t want to use a queue since I only have a fews calls that require these kind of post actions execution.
The problem is that the conection is not closed (untill the scripts ends). The data is sent but your js client is waiting. The best way to do this kind of things is to decouple the procerss. I normally use Gearman (a job server) for this kind of things. Instead of doing all the things within the request, I send (enqueue) all the stuff that the user don’t really need (such as logs, background process, …) to the gearman server. Then another process (a gearman worker) do the work and the user dont’t need to wait. It adds a extra complexity, but the user experience is considerably improved.
Ok, I get it, thank you so much!
I am not sure if this is new, but try `$app->finish(function(){});` as a shortcut for the TERMINATE event.