Skip to content

Commit 618ae30

Browse files
committed
Merge branch '5.4' into 6.3
* 5.4: [Cache] Add integration with Messenger [HttpKernel] Auto-register kernel as an extension
2 parents 28fd362 + fdb2407 commit 618ae30

File tree

3 files changed

+159
-1
lines changed

3 files changed

+159
-1
lines changed

cache.rst

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,3 +846,148 @@ When configuring multiple keys, the first key will be used for reading and
846846
writing, and the additional key(s) will only be used for reading. Once all
847847
cache items encrypted with the old key have expired, you can completely remove
848848
``OLD_CACHE_DECRYPTION_KEY``.
849+
850+
Computing Cache Values Asynchronously
851+
-------------------------------------
852+
853+
.. versionadded:: 5.2
854+
855+
The feature to compute cache values asynchronously was introduced in Symfony 5.2.
856+
857+
The Cache component uses the `probabilistic early expiration`_ algorithm to
858+
protect against the :ref:`cache stampede <cache_stampede-prevention>` problem.
859+
This means that some cache items are elected for early-expiration while they are
860+
still fresh.
861+
862+
By default, expired cache items are computed synchronously. However, you can
863+
compute them asynchronously by delegating the value computation to a background
864+
worker using the :doc:`Messenger component </components/messenger>`. In this case,
865+
when an item is queried, its cached value is immediately returned and a
866+
:class:`Symfony\\Component\\Cache\\Messenger\\EarlyExpirationMessage` is
867+
dispatched through a Messenger bus.
868+
869+
When this message is handled by a message consumer, the refreshed cache value is
870+
computed asynchronously. The next time the item is queried, the refreshed value
871+
will be fresh and returned.
872+
873+
First, create a service that will compute the item's value::
874+
875+
// src/Cache/CacheComputation.php
876+
namespace App\Cache;
877+
878+
use Symfony\Contracts\Cache\ItemInterface;
879+
880+
class CacheComputation
881+
{
882+
public function compute(ItemInterface $item): string
883+
{
884+
$item->expiresAfter(5);
885+
886+
// this is just a random example; here you must do your own calculation
887+
return sprintf('#%06X', mt_rand(0, 0xFFFFFF));
888+
}
889+
}
890+
891+
This cache value will be requested from a controller, another service, etc.
892+
In the following example, the value is requested from a controller::
893+
894+
// src/Controller/CacheController.php
895+
namespace App\Controller;
896+
897+
use App\Cache\CacheComputation;
898+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
899+
use Symfony\Component\Routing\Annotation\Route;
900+
use Symfony\Contracts\Cache\CacheInterface;
901+
use Symfony\Contracts\Cache\ItemInterface;
902+
903+
class CacheController extends AbstractController
904+
{
905+
/**
906+
* @Route("/cache", name="cache")
907+
*/
908+
public function index(CacheInterface $asyncCache): Response
909+
{
910+
// pass to the cache the service method that refreshes the item
911+
$cachedValue = $cache->get('my_value', [CacheComputation::class, 'compute'])
912+
913+
// ...
914+
}
915+
}
916+
917+
Finally, configure a new cache pool (e.g. called ``async.cache``) that will use
918+
a message bus to compute values in a worker:
919+
920+
.. configuration-block::
921+
922+
.. code-block:: yaml
923+
924+
# config/packages/framework.yaml
925+
framework:
926+
cache:
927+
pools:
928+
async.cache:
929+
messenger_bus: async_bus
930+
931+
messenger:
932+
transports:
933+
async_bus: '%env(MESSENGER_TRANSPORT_DSN)%'
934+
routing:
935+
Symfony\Component\Cache\Messenger\Message\EarlyExpirationMessage: async_bus
936+
937+
.. code-block:: xml
938+
939+
<!-- config/packages/framework.xml -->
940+
<?xml version="1.0" encoding="UTF-8" ?>
941+
<container xmlns="http://symfony.com/schema/dic/services"
942+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
943+
xmlns:framework="http://symfony.com/schema/dic/symfony"
944+
xsi:schemaLocation="http://symfony.com/schema/dic/services
945+
https://symfony.com/schema/dic/services/services-1.0.xsd
946+
http://symfony.com/schema/dic/symfony
947+
https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"
948+
>
949+
<framework:config>
950+
<framework:cache>
951+
<framework:pool name="async.cache" early-expiration-message-bus="async_bus"/>
952+
</framework:cache>
953+
954+
<framework:messenger>
955+
<framework:transport name="async_bus">%env(MESSENGER_TRANSPORT_DSN)%</framework:transport>
956+
<framework:routing message-class="Symfony\Component\Cache\Messenger\Message\EarlyExpirationMessage">
957+
<framework:sender service="async_bus"/>
958+
</framework:routing>
959+
</framework:messenger>
960+
</framework:config>
961+
</container>
962+
963+
.. code-block:: php
964+
965+
// config/framework/framework.php
966+
use function Symfony\Component\DependencyInjection\Loader\Configurator\env;
967+
use Symfony\Component\Cache\Messenger\EarlyExpirationMessage;
968+
use Symfony\Config\FrameworkConfig;
969+
970+
return static function (FrameworkConfig $framework): void {
971+
$framework->cache()
972+
->pool('async.cache')
973+
->earlyExpirationMessageBus('async_bus');
974+
975+
$framework->messenger()
976+
->transport('async_bus')
977+
->dsn(env('MESSENGER_TRANSPORT_DSN'))
978+
->routing(EarlyExpirationMessage::class)
979+
->senders(['async_bus']);
980+
};
981+
982+
You can now start the consumer:
983+
984+
.. code-block:: terminal
985+
986+
$ php bin/console messenger:consume async_bus
987+
988+
That's it! Now, whenever an item is queried from this cache pool, its cached
989+
value will be returned immediately. If it is elected for early-expiration, a
990+
message will be sent through to bus to schedule a background computation to refresh
991+
the value.
992+
993+
.. _`probabilistic early expiration`: https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration

components/cache.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ generate and return the value::
8484
Use cache tags to delete more than one key at the time. Read more at
8585
:doc:`/components/cache/cache_invalidation`.
8686

87+
.. _cache_stampede-prevention:
88+
8789
Stampede Prevention
8890
~~~~~~~~~~~~~~~~~~~
8991

configuration/micro_kernel_trait.rst

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,18 @@ Adding Interfaces to "Micro" Kernel
159159
When using the ``MicroKernelTrait``, you can also implement the
160160
``CompilerPassInterface`` to automatically register the kernel itself as a
161161
compiler pass as explained in the dedicated
162-
:ref:`compiler pass section <kernel-as-compiler-pass>`.
162+
:ref:`compiler pass section <kernel-as-compiler-pass>`. If the
163+
:class:`Symfony\\Component\\DependencyInjection\\Extension\\ExtensionInterface`
164+
is implemented when using the ``MicroKernelTrait``, then the kernel will
165+
be automatically registered as an extension. You can learn more about it in
166+
the dedicated section about
167+
:ref:`managing configuration with extensions <components-dependency-injection-extension>`.
168+
169+
.. versionadded:: 5.2
170+
171+
The automatic registration of the kernel as an extension when implementing the
172+
:class:`Symfony\\Component\\DependencyInjection\\Extension\\ExtensionInterface`
173+
was introduced in Symfony 5.2.
163174

164175
It is also possible to implement the ``EventSubscriberInterface`` to handle
165176
events directly from the kernel, again it will be registered automatically::

0 commit comments

Comments
 (0)