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