2
\$\begingroup\$

I'm using Cache::remember to cache 3 results in my action, 2 in the same call to Cache::remember. It works, but I have 2 questions about how I can join this Cache::remember call:

  1. Could I use two Cache::remembers in the same method?

  2. Could I join these two Cache::remembers (I need change the first param)?

  3. Is it possible load model outer Cache::remember?

public function favoriteStores()
{
    $userId = $this->Auth->user('id');
    $username = $this->Auth->user('username');

    list($fullBanners, $smallBanners,) = Cache::remember(
        'banners', function(){
        $this->loadModel('Banners');
        $fullBanners = $this->Banners->full();
        $smallBanners = $this->Banners->small();
        return [$fullBanners, $smallBanners];
    });

    list($stores, $favoriteStores) = Cache::remember(
        'stores', function(){
        $stores = $this->Stores->myStores($this->Auth->user('id'));
        $favoriteStores = $this->Stores->favoriteStores();
        return [$stores, $favoriteStores];
    });

    $this->set(compact('fullBanners', 'smallBanners', 'userId', 'username',
        'stores', 'favoriteStores'));
}
\$\endgroup\$
3
\$\begingroup\$

Could I use two Cache::remembers in the same method?

Of course. Using Cache::remember or not should not affect the decision on whether or not to use Cache::remember again for something else - the decision should be: do I want this data to be cached?

Could I join these two Cache::remembers (I need change the first param)?

You could but it is a bad idea.

Consider this contrived example:

$userId = $this->Auth->user('id');
$everything = Cache::remember('everything.' . $userId, function() use ($userId) {
    $allStuff = $this->Stuff->find('all');
    $myStuff = $this->Stuff->find('all')->where(['user_id' => $userId]);
    return compact('allStuff', 'myStuff');
});
...

And compare to this:

$allStuff = Cache::remember('allStuff', function()) {
    return $this->Stuff->find('all');
});

$userId = $this->Auth->user('id');
$myStuff = Cache::remember('mystuff.' . $userId, function() use ($userId) {
    return $this->Stuff->find('all')->where(['user_id' => $userId]);
});
...

In the first example, the same cache key is used for two purposes; One is to cache getting some data which applies to all users, and the second is to get some data that is one-user specific. There are a number of (potentially dire) consequences do doing that, most obviously:

  • There is one copy of "allstuff" in the cache store per user
  • The Cached data which is not user-specific cannot be reused

Attempting to bundle cached data together, unless it actually belongs together (e.g. total number of votes, total number of up votes, total number of down votes) is at best inefficient and at worst results in cache collisions, cache churn or similar.

The right way to manage cached data is atomically and independently; This does not mean it'll never be the case that two cache calls cannot be combined.

Is it possible load model outer Cache::remember

I'm not sure what this question means, code outside a cache call should not assuming the code in a cache call will run and vice versa. Also be aware that using $this inside a cache callback ties the code to the class where the callback is written.

Complete example

It is not clear why there are cache calls around the store information, it was suggested to wrap the banner information in a cache call in a previous question because the full/small banner information would be the same for all requests, for all users.

If the store information in the question should be cached, the user id should be in the cache key and should expect it as a parameter to the Cache::remember.

public function favoriteStores()
{
    $userId = $this->Auth->user('id');
    $username = $this->Auth->user('username');

    list($fullBanners, $smallBanners,) = Cache::remember(
        'banners', function(){
        $this->loadModel('Banners');
        $fullBanners = $this->Banners->full();
        $smallBanners = $this->Banners->small();
        return [$fullBanners, $smallBanners];
    });

    $myStores = Cache::remember(
        'stores.' . $userId, function() use ($userId) {
        $stores = $this->Stores->myStores($userId);
        $favoriteStores = $this->Stores->favoriteStores();
        return [$stores, $favoriteStores];
    });

    $this->set(compact('fullBanners', 'smallBanners', 'userId', 'username',
        'stores', 'favoriteStores'));
}

If favoriteStores is not user-specific it does not belong in that cache call. Without knowing the meaning/intended use of this data I would suggest to remove caching from the stores/favotite stores data:

public function favoriteStores()
{
    $userId = $this->Auth->user('id');
    $username = $this->Auth->user('username');

    list($fullBanners, $smallBanners,) = Cache::remember(
        'banners', function(){
        $this->loadModel('Banners');
        $fullBanners = $this->Banners->full();
        $smallBanners = $this->Banners->small();
        return [$fullBanners, $smallBanners];
    });

    // Don't needlessly cache this data
    $stores = $this->Stores->myStores($userId);
    $favoriteStores = $this->Stores->favoriteStores();

    $this->set(compact('fullBanners', 'smallBanners', 'userId', 'username',
        'stores', 'favoriteStores'));
}

Warning

If in doubt do not cache anything.

This may seem like a contradiction but if you're considering caching data "just because" without any real need or reason that's likely to end badly. Use caching to improve performance and address specific concerns/problems - when it's needed.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.