trifek's avatar

Cache in Laravel

Hi, I am beginner in Laravel. In my project I use repository pattern and Laravel 7.

I have this controller:


class PageController extends Controller
{

    protected $model;

    /**
     * PageController constructor.
     * @param PageRepositoryInterface $repository
     */
    public function __construct(PageRepositoryInterface $repository)
    {
        $this->model = $repository;
    }


    /**
     * @param Request $request
     * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\View\View
     */
    public function index(Request $request)
    {
        if ($request->input('query') != "") {
            $pages = $this->model->search($request->input('query'), 'id', 'asc', [],  30);
        } else {
            $pages = $this->model->listWithPaginate('id', 'desc', [],  30);
        }
        return view('admin.pages.list', ['pages' => $pages]);
    }


    /**
     * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\View\View
     */
    public function create()
    {
        return view('admin.pages.view');
    }


    /**
     * @param PageRequest $request
     * @return \Illuminate\Http\RedirectResponse
     */
    public function store(PageRequest $request)
    {
        if ($request->isMethod('post')) {
            $data = [
                'title' => $request->input('title'),
                'description' => $request->input('description') ?? $request->input('title'),
                'keywords' => $request->input('keywords') ?? $request->input('title'),
                'content' => $request->input('content'),
                'enable' => $request->input('enable') ?? 0,
                'slug' => Str::slug($request->input('title'), '-')
            ];
            $this->model->create($data);
            return redirect()->route('page.index')->with('success', __('messages.record_save_info'));
        }
    }


    /**
     * @param int $id
     * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\View\View
     */
    public function edit(int $id)
    {
        Cache::forget("page.{$id}");
        return view('admin.pages.view', ['page' => $this->model->findOrFail($id)]);
    }


    /**
     * @param PageRequest $request
     * @param int $id
     * @return \Illuminate\Http\RedirectResponse
     */
    public function update(PageRequest $request, int $id)
    {
        if ($request->isMethod('put')) {
            $data = [
                'title' => $request->input('title'),
                'description' => $request->input('description'),
                'keywords' => $request->input('keywords'),
                'content' => $request->input('content'),
                'enable' => $request->input('enable') ?? 0,
                'slug' => Str::slug($request->input('title'), '-')
            ];
            $this->model->update($data, $id, 'id');
            return redirect()->route('page.index')->with('success', __('messages.record_edit_info'));
        }
    }


    /**
     * @param Request $request
     * @param int $id
     * @return \Illuminate\Http\RedirectResponse
     */
    public function destroy(Request $request, int $id)
    {
        if ($request->isMethod('delete')) {
            $this->model->delete($id);
            return redirect()->route('page.index')->with('success', __('messages.record_remove_info'));
        }
    }
}

and CachingBaseRepository

abstract class CachingBaseRepository implements RepositoryInterface
{
    use ScopeActiveTrait;

    protected $model;
    protected $cacheKey;
    protected $cacheTime;

    public function all()
    {
        return Cache::remember($this->cacheKey.'.all', $this->cacheTime, function () {
            return $this->model->get();
        });
    }

    public function allEnables()
    {
        return Cache::remember($this->cacheKey.'.enables', $this->cacheTime, function () {
            return $this->model->active()->get();
        });
    }

    public function list(string $orderByColumn, string $orderBy = 'desc', array $with = [])
    {
        return Cache::remember($this->cacheKey.'.list', $this->cacheTime, function () use($with, $orderByColumn, $orderBy) {
            return $this->model->with($with)
                ->orderBy($orderByColumn, $orderBy)
                ->get();
        });
    }

    public function listWithPaginate(string $orderByColumn, string $orderBy = 'desc', array $with = [], int $perPage = 10)
    {
        return Cache::remember($this->cacheKey.'.listWithPaginate', $this->cacheTime, function () use($with, $orderByColumn, $orderBy, $perPage) {
            return $this->model->with($with)
                ->orderBy($orderByColumn, $orderBy)
                ->paginate($perPage)->appends(request()->query());
        });
    }

    public function create(array $data): int
    {
        Cache::forget($this->cacheKey);
        Cache::forget($this->cacheKey.'.all');
        Cache::forget($this->cacheKey.'.enables');
        Cache::forget($this->cacheKey.'.list');
        Cache::forget($this->cacheKey.'.listWithPaginate');
        return $this->model->create($data)->id;
    }

    public function update(array $data, int $id, string $attribute = 'id'): void
    {
        $this->model->where($attribute, '=', $id)->update($data);
        Cache::forget($this->cacheKey);
        Cache::forget($this->cacheKey.".{$id}");
        Cache::forget($this->cacheKey.'.all');
        Cache::forget($this->cacheKey.'.enables');
        Cache::forget($this->cacheKey.'.list');
        Cache::forget($this->cacheKey.'.listWithPaginate');
    }

    public function delete(int $id): void
    {
        $this->model->destroy($id);
        Cache::forget($this->cacheKey);
        Cache::forget($this->cacheKey.'.all');
        Cache::forget($this->cacheKey.'.enables');
        Cache::forget($this->cacheKey.'.list');
        Cache::forget($this->cacheKey.'.listWithPaginate');
    }

    public function find(int $id)
    {
        return Cache::remember($this->cacheKey.".{$id}", $this->cacheTime, function () use ($id) {
            return $this->model->find($id);
        });
    }

    public function getModel()
    {
        return Cache::remember($this->cacheKey.".all", $this->cacheTime, function (){
            return $this->model;
        });
    }

    public function findOrFail(int $id)
    {
        return Cache::remember($this->cacheKey.".{$id}", $this->cacheTime, function () use ($id) {
            return $this->model->findOrFail($id);
        });
    }
}

I have problem with paginate. When I go to pagination 2,3,7 or 10 - I always see the same as on page 1.

Is my code optimal? Can duplication be replaced - one function (I have remove in controller - in edit too):

:: Cache forget ($ this-> cacheKey);
         :: Cache forget ($ this-> cacheKey.. 'All');
         :: Cache forget ($ this-> cacheKey. '. Enables');
         :: Cache forget ($ this-> cacheKey.. 'Letter');
         :: Cache forget ($ this-> cacheKey. '. ListWithPaginate');

some one function?

0 likes
15 replies
MichalOravec's avatar

Add to your cache key for pagination this

request()->query('page', 1)

like

return Cache::remember($this->cacheKey.'.listWithPaginate'.request()->query('page', 1), $this->cacheTime, function () use($with, $orderByColumn, $orderBy, $perPage) {
    return $this->model->with($with)
        ->orderBy($orderByColumn, $orderBy)
        ->paginate($perPage)->appends(request()->query());
});

Next think is that it will be better if you use Cache Tags for pagination

Documentation: https://laravel.com/docs/7.x/cache#cache-tags

Because when you delete cache for pagination it will be problem to remove cache for all pagination sites without tags.

You can do something like this

public function removePagination($key)
{
    $i = 1;

    while (Cache::has($key.$i)) {
        Cache::forget($key.$i);

        $i++;
    }
}

But if somebody skip for example ?page=3 so your deleting of cache will be stopped. So you definitelly need to use Cache Tags.

Snapey's avatar

you need to include the page number in the cache key. At present the code in the closure is never called because the cache thinks you already have cached the results.

The laravel paginator function never gets to run because its answered from cache with content from page 1

trifek's avatar

thank you. It's help. :)

At the moment I have this code:

public function listWithPaginate (string $ orderByColumn, string $ orderBy = 'desc', array $ with = [], int $ perPage = 10)
    {
        return Cache :: remember ($ this-> cacheKey. '. listWithPaginate.'. request () -> query ('page', 1), $ this-> cacheTime, function () use ($ with, $ orderByColumn, $ orderBy, $ perPage) {
            return $ this-> model-> with ($ with)
                -> orderBy ($ orderByColumn, $ orderBy)
                -> paginate ($ perPage) -> appends (request () -> query ());
        });
    }

When I edit a record, the list is inactive.

I think in this function I should add deleting changed value, yes?

public function update (array $ data, int $ id, string $ attribute = 'id'): void
    {
        $ this-> model-> where ($ attribute, '=', $ id) -> update ($ data);
        :: Cache forget ($ this-> cacheKey);
        :: Cache forget ($ this-> cacheKey.. "{$ Id}");
        :: Cache forget ($ this-> cacheKey.. 'All');
        :: Cache forget ($ this-> cacheKey. '. Enables');
        :: Cache forget ($ this-> cacheKey.. 'Letter');
        :: Cache forget ($ this-> cacheKey. '. ListWithPaginate');
    }

how to do it when eg the modified record is on page 4?

trifek's avatar

I use cache filesystem. I can't use tags

MichalOravec's avatar

If I can't use Cache Tags I often use it like this

<?php

namespace App\Services;

use Illuminate\Support\Facades\Cache;

class CacheService
{
    public function remove($key)
    {
        if (Cache::has($key)) {
            Cache::forget($key);
        }
    }

    public function removePagination($key)
    {
        $i = 1;

        while (Cache::has($key.$i)) {
            Cache::forget($key.$i);

            $i++;
        }
    }

    public function flush()
    {
        Cache::flush();
    }
}

If I can use Cache Tags I often use it like this

<?php

namespace App\Services;

use Illuminate\Support\Facades\Cache;

class CacheService
{
    protected $tags = null;

    public function tags($tags)
    {
        $this->tags = $tags;

        return $this;
    }

    public function remove($key)
    {
        if ($this->tags) {
            if (Cache::tags($this->tags)->has($key)) {
                Cache::tags($this->tags)->forget($key);
            }

            $this->resetTags();
        } elseif (Cache::has($key)) {
            Cache::forget($key);
        }
    }

    public function flush()
    {
        if ($this->tags) {
            Cache::tags($this->tags)->flush();

            $this->resetTags();
        } else {
            Cache::flush();
        }
    }

    protected function resetTags()
    {
        $this->tags = null;
    }
}

And you can use it like this:

use App\Services\CacheService;

protected $cache;

public function __construct(CacheService $cache)
{
    $this->cache = $cache;
}

// and in some method like
$this->cache->removePagination('key');

// with tags
$this->cache->tags('key.pagination')->flush();
trifek's avatar

thank you.

I make this code:

public function update(array $data, int $id, string $attribute = 'id'): void
    {
        $this->model->where($attribute, '=', $id)->update($data);
        $this->cache->removePagination($this->cacheKey);
        $this->cache->removePagination($this->cacheKey.'.listWithPaginate');

        $this->cache->remove($this->cacheKey);
        $this->cache->remove($this->cacheKey.'.listWithPaginate');
//        Cache::forget($this->cacheKey);
//        Cache::forget($this->cacheKey.".{$id}");
//        Cache::forget($this->cacheKey.'.all');
//        Cache::forget($this->cacheKey.'.enables');
//        Cache::forget($this->cacheKey.'.list');
//        Cache::forget($this->cacheKey.'.listWithPaginate');
    }

but it's not working. I hav error: Call to a member function remove() on null

My $this->cacheKey - has value "page" (I check by dd($this->cacheKey) )

MichalOravec's avatar

@trifek To your CachingBaseRepository you have to add constructor

use App\Services\CacheService;

protected $cache;

public function __construct(CacheService $cache)
{
    $this->cache = $cache;
}
trifek's avatar

Yes, I have:

use App\Repositories\Interfaces\RepositoryInterface;
use App\Traits\ScopeActiveTrait;
use Cache;
use App\Services\CacheService;


abstract class CachingBaseRepository implements RepositoryInterface
{
    use ScopeActiveTrait;

    protected $model;
    protected $cacheKey;
    protected $cacheTime;
    protected $cache;

    public function __construct(CacheService $cache)
    {
        $this->cache = $cache;
    }
...
}

but it's not helping

trifek's avatar

Thank you so much for your help and patience. My previous topic is solved correctly. (from yesterday) Now, if this problem is solved, I will convert my code to a repozitory-free pattern. At the moment I must have to run it on actual form what I have now :(

When I'm debbug in CachingBaseRepository:

public function delete(int $id): void
    {
dd($this->cache);
        $this->cache->removePagination($this->cacheKey);
        $this->cache->removePagination($this->cacheKey.'.listWithPaginate');
        $this->cache->remove($this->cacheKey);
        $this->cache->remove($this->cacheKey.'.listWithPaginate');
....
}

I have return: null.

MichalOravec's avatar
Level 75

@trifek If you have problem with construtor so you can do this

But problem with constructor will be how I mentioned before with bind your repository in AppServiceProvider, you have pass there CacheService also.

public function update(array $data, int $id, string $attribute = 'id'): void
{
    $cache = new CacheService;
    $this->model->where($attribute, '=', $id)->update($data);
    $cache->removePagination($this->cacheKey);
    $cache->removePagination($this->cacheKey.'.listWithPaginate');

    $cache->remove($this->cacheKey);
    $cache->remove($this->cacheKey.'.listWithPaginate');

    //        Cache::forget($this->cacheKey);
    //        Cache::forget($this->cacheKey.".{$id}");
    //        Cache::forget($this->cacheKey.'.all');
    //        Cache::forget($this->cacheKey.'.enables');
    //        Cache::forget($this->cacheKey.'.list');
    //        Cache::forget($this->cacheKey.'.listWithPaginate');
}
trifek's avatar

Thank you very much :) It's working :)

How would you organize such deleting of many caches in different methods? Or it's okey?

public function create (array $ data): int
    {
        $ This-> cache-> remove ($ this-> cacheKey);
        $ This-> cache-> remove ($ this-> cacheKey.. 'All');
        $ This-> cache-> remove ($ this-> cacheKey.. "Enables');
        $ This-> cache-> remove ($ this-> cacheKey.. 'Letter');
        $ This-> cache-> removePagination ($ this-> cacheKey.. 'Search.');
        $ This-> cache-> removePagination ($ this-> cacheKey. '. ListWithPaginate.');
        return $ this-> model-> create ($ data) -> id;
    }

    public function update (array $ data, int $ id, string $ attribute = 'id'): void
    {
        $ This-> cache-> remove ($ this-> cacheKey.. "$ {Id}");
        $ This-> cache-> remove ($ this-> cacheKey);
        $ This-> cache-> remove ($ this-> cacheKey.. 'All');
        $ This-> cache-> remove ($ this-> cacheKey.. "Enables');
        $ This-> cache-> remove ($ this-> cacheKey.. 'Letter');
        $ This-> cache-> removePagination ($ this-> cacheKey.. 'Search.');
        $ This-> cache-> removePagination ($ this-> cacheKey. '. ListWithPaginate.');

        $ this-> model-> where ($ attribute, '=', $ id) -> update ($ data);
    }

    public function delete (int $ id): void
    {
        $ This-> cache-> remove ($ this-> cacheKey.. "$ {Id}");
        $ This-> cache-> remove ($ this-> cacheKey);
        $ This-> cache-> remove ($ this-> cacheKey.. 'All');
        $ This-> cache-> remove ($ this-> cacheKey.. "Enables');
        $ This-> cache-> remove ($ this-> cacheKey.. 'Letter');
        $ This-> cache-> removePagination ($ this-> cacheKey.. 'Search.');
        $ This-> cache-> removePagination ($ this-> cacheKey. '. ListWithPaginate.');

        $ This-> model-> destroy ($ id);
    }
MichalOravec's avatar

@trifek Create another method and put there common things

public function create (array $data): int
{
    $this->cache->remove($this->cacheKey);

    $this->handleCache();
    
    return $this->model->create($data)->id;
}

public function update (array $data, int $id, string $attribute = 'id'): void
{
    $this->cache->remove($this->cacheKey."${Id}");

    $this->handleCache();

    $this->model->where($attribute, '=', $id)->update($data);
}

public function delete (int $id): void
{
    $this->cache->remove($this->cacheKey."${Id}");
    
    $this->handleCache();

    $this->model->destroy($id);
}

protected function handleCache()
{
    $this->cache->remove($this->cacheKey);
    
    $this->cache->remove($this->cacheKey.'All');
    
    $this->cache->remove($this->cacheKey.'Enables');
    
    $this->cache->remove($this->cacheKey.'Letter');
    
    $this->cache->removePagination($this->cacheKey.'Search.');
    
    $this->cache->removePagination($this->cacheKey.'.ListWithPaginate.');
}

And also coul you mark this thread as solved?

trifek's avatar

Yes, it's all :) Thank. you. How can I mark this thread as solved?

Please or to participate in this conversation.