Home » Php » How would you forget cached Eloquent models in Laravel?

How would you forget cached Eloquent models in Laravel?

Posted by: admin November 29, 2017 Leave a comment

Questions:

Theoretical question on Laravel here.

So Example of the caching I’d do is:

Article::with('comments')->remember(5)->get();

Ideally I’d like to have an event for Article updates that when the ID of a instance of that model (that’s already cached) is updated I want to forget that key (even if it’s the whole result of the query that’s forgotten instead of just that one model instance), it is possible to do so?

If not is there some way to implement this reasonably cleanly?

Answers:

So i was looking for an answer to the same question as OP but was not really satisfied with the solutions. So i started playing around with this recently and going through the source code of the framework, I found out that the remember() method accepts second param called key and for some reason it has not been documented on their site (Or did i miss that?).

Now good thing about this is that, The database builder uses the same cache driver which is configured under app/config/cache.php Or should i say the same cache system that has been documented here – Cache. So if you pass min and key to remember(), you can use the same key to clear the cache using Cache::forget() method and in fact, you can pretty much use all the Cache methods listed on the official site, like Cache::get(), Cache::add(), Cache::put(), etc. But i don’t recommend you to use those other methods unless you know what you’re doing.

Here’s an example for you and others to understand what i mean.

Article::with('comments')->remember(5, 'article_comments')->get();

Now the above query result will be cached and will be associated with the article_comments key which can then be used to clear it anytime (In my case, I do it when i update).

So now if i want to clear that cache regardless of how much time it remembers for. I can just do it by calling Cache::forget('article_comments'); and it should work just as expected.

Hope this helps everyone 🙂

Questions:
Answers:

I think a good way to do is like this:

$value = Cache::remember('users', $minutes, function()
{
    return DB::table('users')->get();
});

and then use Model Observers to detect the event of updating the model

class UserObserver {

    public function saving($model)
    {
        //
    }

    public function saved($model)
    {
        // forget from cache
        Cache::forget('users');
    }

}

User::observe(new UserObserver);

Questions:
Answers:

Currently there are no easy way. However I found this workaround, which so far worked for me.

First you have to extend Illuminate\Database\Query\Builder.

<?php 

class ModifiedBuilder extends Illuminate\Database\Query\Builder {
    protected $forgetRequested = false;

    public function forget() 
    {
        $this->forgetRequested = true;
    }

    public function getCached($columns = array('*'))
    {
        if (is_null($this->columns)) $this->columns = $columns;

        list($key, $minutes) = $this->getCacheInfo();

        // If the query is requested ot be cached, we will cache it using a unique key
        // for this database connection and query statement, including the bindings
        // that are used on this query, providing great convenience when caching.
        $cache = $this->connection->getCacheManager();

        $callback = $this->getCacheCallback($columns);

        if($this->forgetRequested) {
            $cache->forget($key);
            $this->forgetRequested = false;
        }

        return $cache->remember($key, $minutes, $callback);
    }
}

Then you have to create new class which extends Eloquent Model.

<?php

class BaseModel extends Eloquent {
    protected function newBaseQueryBuilder() {
        $conn = $this->getConnection();

        $grammar = $conn->getQueryGrammar();

        return new ModifiedBuilder($conn, $grammar, $conn->getPostProcessor());
    }
}

Now when creating Eloquent Models, instead of extending Eloquent Models extend newly created BaseModel.

Now you can remember query result as usual.

YourModel::remember(10)->get();

When you want to discard the cached result all you have to do is

YourModel::forget()->get();

If you remember the result previously, after clearing the cached result, model will continue to remember the result for that amount of time.

Hope this helps.

Questions:
Answers:

I was testing for debug mode. So I found that if you put a test for app.debug in a constructor you are able to clear the cache associated with a key. Saves you having to duplicate the code for every function.

class Events {
    public function __construct() {
        if (\Config::get('app.debug')) {
            Cache::forget('events');
        }
    }

    public static function all() {
        $events = \DB::table('events as e')
            ->select('e.*')
            ->where('enabled', 1)
            ->remember(30, 'events')
            ->get();

        return $events;
    }
}