Problem/Motivation

#1748022: Make CacheBackendInterface::get() return a proper class will improve the return type from get a lot making it quite a bit easier to work with but handling cache misses in Drupal can be a bit complicated. Returning an object means you have to do some inspection and understand the object and the way caches behave when you you just want the caching system to help you avoid doing some costly action over and over. And lets all admit, if you haven't messed with caches in a while, you probably forget about the intermediate object the first time you prototype your code.

Proposed resolution

I suggest we add a helper that combines get/set into one handy function looking something like this:

$data = $this->cache->remember($cid, $expire, function () {
  return 'My expensive value';
});

or to steal from the core documentation is would replace most uses of this:

$cid = 'mymodule_example:' . \Drupal::languageManager()
  ->getCurrentLanguage()
  ->getId();
$data = NULL;
if ($cache = \Drupal::cache()
  ->get($cid)) {
  $data = $cache->data;
}
else {
  $data = my_module_complicated_calculation();
  \Drupal::cache()
    ->set($cid, $data);
}

with this:

$bin = \Drupal::cache();
$cid = 'mymodule_example:' . \Drupal::languageManager()
  ->getCurrentLanguage()
  ->getId();
$data = $bin->remember($cid, Cache::PERMANENT, function() {
  return my_module_complicated_calculation();
}

This idea is shamelessly stolen from Laravel's cache repository because it simplifies so much logic out of most cache uses.
https://laravel.com/api/5.4/Illuminate/Contracts/Cache/Repository.html#m...

Remaining tasks

Discuss plan and agree if its a good idea
Decide on the interface
Write implementation
CR
Update documentation

User interface changes

n/a

API changes

New API. Provided as new interface for BC but maybe merged into CacheBackendInterface for 9.x?

Data model changes

n/a

Comments

neclimdul created an issue. See original summary.

neclimdul’s picture

To open the discussion, since we use "PERMANENT" as the expiration quite often, one thing that could also be useful would be a remember permanent. It could easily be implemented like:

public function rememberPermanent($cid, Closure $callback) {
  return $this->remember($cid, Cache::PERMANENT, $callback);
}
neclimdul’s picture

Status: Active » Needs review
StatusFileSize
new6.17 KB

A quick and dirty patch to kinda show how it might work.

Because contrib won't have implemented this immediately, actually using it in core will be tricky but that can be a follow up to resolve.

Andre-B’s picture

+1 I like the style of this

jibran’s picture

Does it improve performance?

gabesullice’s picture

There's a name for this: memoization

Should we use that terminology instead? I'm not sure, just wanted to throw it out there.

neclimdul’s picture

I wasn't targeting performance with this idea so much as developer experience. A more straightforward interface to avoid bugs and codify the general standard for caching and checking caches.

Since the code path ends up mostly being the same I would guess performance is mostly the same but that's a very good question.

neclimdul’s picture

There's a name for this: memoization

Should we use that terminology instead? I'm not sure, just wanted to throw it out there.

I love that term and always forget about it. It feels possibly a bit opinionated about the use of the method but I don't feel strongly about the color of this bikeshed ;)

borisson_’s picture

I really like this, the name of the method is really useful because this is easy to remember and easy to discover. This is another extra function call, so should probably be a little slower. But I personally think that the improvement in readability is a good tradeoff.

Great work!

hussainweb’s picture

I really like the idea and was waiting for it ever since I used it in Laravel. I also like how this is done with traits that can just be used in contrib modules.

Few points for review:

  1. The comment block on CacheRememberInterface is incorrect.
  2. I'd want this to be eventually added to the CacheBackendInterface in Drupal 9. Can we add a todo item for that?
  3. I'd want to follow the signature of set method in remember methods as well. Currently, it is $cid, $data, $expire, $tags, but the remember method is $cid, $expire, $callback, $tags. I realize you might have done it because $callback is a closure but I'd like to consider keeping the signature along the same pattern anyway, even if code appears like this:
      $data = $this->cache->remember($cid, function () {
        return 'My expensive value';
      }, $expire);
      

    This is not unprecedented, BTW. Functions like array_map are written similarly.

  4. Can we use the phrase "Helper method" instead of "Shortcut" in docblocks for remember and rememberPermanent methods.
hussainweb’s picture

And thanks for doing this! I have wanted this from a long time but never got around to the patch.

neclimdul’s picture

StatusFileSize
new3.89 KB
new6.23 KB

Taking a moment to update this. A little birdie noted callable is better that \Closure for the interface so fixing that.

Re: #10 & #11
You're welcome!
1. Wrote something. I'm not the best copywriter though so if something has something better feel free to update it.
2. hmm.. Yeah sure. Need to open up an issue for that first I guess. // TODO
3. I considered that when writing the patch and don't like it. For one thing, array_map is reversed for the variadic of arrays you can pass. Very different. I also think set got the order wrong for historical reasons and this makes more sense. Finally, I think the methods read significantly different. Putting it after a long method buries important information. Arguably tags is buried too and I'm not sure how I feel about that yet.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

neclimdul’s picture

Assigned: Unassigned » neclimdul
Status: Needs review » Needs work

So this still applies _but_ I've actually been using this over the years and there's a tiny short coming the in the current patch. Specifically, it requires the computing of expires and tags _before_ calling remember but you almost never want to do that. Especially with tags, its not uncommon that the tags you need to add are computed inside the closure which leads to quite a bit of trouble.

I've got a modified version of this I'm sort of using but It needs a bit of polish and this comment is mostly to provide and update but also to remind me to update the patch with a better implementation.

Side note, I _really_ which the cache DO returned from get was a real class and documented. it would make some hints easier and the documentation a lot clearer with the new patch.

geek-merlin’s picture

I like this a lot. This is similar to: https://www.php.net/manual/en/function.apcu-entry.php

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

neclimdul’s picture

Made a library people can use to "test" this in their projects.

https://www.drupal.org/project/cache_remember
https://packagist.org/packages/drupal/cache_remember

I'm fairly happy with the current version of the methods with one exception and that's the fact its still annoying to set the expiry since we set a timestamp instead of an offset. You need to pull the time service and calculate it instead of having a central way of doing this.

Not sure if there's a good way to address this while maintaining consistency with other Drupal API's but I'm open to suggestions and we can test them out.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.