Hey guys,
We all know that Cache
facade plays a good role across the Laravel application lifecycle, isn't it?
Cache simply boosts up the requests by retaining the computed data from file storage or memory (aka RAM) depending on our configuration.
Today I'm gonna show you an awesome way to maintain your cache flow.
The Problems
Not only for other facades but also Cache
provide us with a really simple way to use things (the beauty of Laravel - simple yet elegant)
However, it's a double-edged sword. We code things and we see it's simple, no biggie.
But by the nature of software development, from time to time, the codebase increases, and maintaining becomes harder.
Then people start to blame Laravel. It's not Laravel's fault, right? Laravel doesn't do anything, Laravel allows us to use any design patterns, and any approaches out there.
So let's get back to the main topic. We usually use cache
like this:
// for example, Country is an entity that we don't change much
// CountryController.php
public function index(): JsonResponse
{
$countries = Cache::rememberForever(
'countries-list',
fn () => Country::query()->pluck('name', 'code'),
);
return new JsonResponse(compact('countries'));
}
public function store(CountryStoreRequest $request): JsonResponse
{
$country = Country::create($request->validated());
Cache::forget('countries-list');
return new JsonResponse();
}
public function update(CountryUpdateRequest $request, Country $country): JsonResponse
{
$country->update($request->validated());
Cache::forget('countries-list');
return new JsonResponse();
}
public function destroy(CountryDestroyRequest $request, Country $country)): JsonResponse
{
$country->delete();
Cache::forget('countries-list');
return new JsonResponse();
}
The problems:
Hardcoded cache key across the codebase, which can easily leak to human error when developing features.
The literal
'countries-list'
could be anything in the codebase, imagine a big codebase and you want to search for the cache stuff only, it's a nightmare.
And that's just for a fixed cache key, imagine you want to cache for specific users, or businesses, etc. The complex just becomes bigger.
The solution
From the statement above:
Laravel allows us to use any design patterns, and any approaches out there.
Yes, don't limit our implementation, let's add more spicy stuff into the codebase. Let's go above and beyond ✈️
I'll create an AbstractDataCache with some simple implementations:
<?php
namespace App\DataCache;
use Carbon\CarbonImmutable;
use Illuminate\Support\Facades\Cache;
abstract class AbstractDataCache
{
/**
* Concrete class must implement and return the cache key
*/
abstract public function getCacheKey(): string;
/**
* Concrete class must know how to compute and return the data that need to be cached
*/
abstract public function computeCache(): mixed;
/**
* By default, data will be cached for an hour
*
* @return CarbonImmutable
*/
public function getCacheExpiration(): CarbonImmutable
{
return CarbonImmutable::now()->addHour();
}
public function get(): mixed
{
return Cache::remember(
$this->getCacheKey(),
$this->getCacheExpiration(),
fn () => $this->computeCache()
);
}
/**
* Clear the cache data
*/
public function clear(): void
{
Cache::forget($this->getCacheKey());
}
/**
* Clear and re-cache the data
*/
public function rebuild(): void
{
$this->clear();
$this->get();
}
}
Then I'll create CountriesListDataCache
and extends and implements the required methods:
class CountriesListDataCache extends AbstractDataCache
{
protected function getCacheKey(): string
{
return 'countries-list';
}
protected function computeCache(): array
{
return Country::query()->pluck('name', 'id');
}
protected function getCacheExpiration(): CarbonImmutable
{
return CarbonImmutable::now()->addYear();
}
}
Taking into action:
public function index(CountriesListDataCache $dataCache): JsonResponse
{
$countries = $dataCache->get();
return new JsonResponse(compact('countries'));
}
public function store(
CountryStoreRequest $request,
CountriesListDataCache $dataCache
): JsonResponse {
$country = Country::create($request->validated());
$dataCache->rebuild(); // or clear
return new JsonResponse();
}
Achievements
So with that, what do we achieve?
A new layer to manage the caches (key, time, values)
No more hardcoded string or string concat/manipulation
No more duct tape caching stuff
Maintaining the cache across the app is way better.
- We also have a dedicated folder for cache stuff.
Can use the dependency injection just fine.
Testable and super easy-to-write test cases.
Q/A
Q: What if I need some additional entities to compute the cache data?
- A: pass them to the
constructor
and then you can use them normally fine (same approaches as Queue)
- A: pass them to the
Q: How to use dependency injection?
A: use the helper
app
function to get your desired instance for your computation.- Additionally, I'd love to make the
computeCache
as same as thehandle
of Queue class, will do soon
- Additionally, I'd love to make the
Conclusion
Well, that's that for the DataCache topic. I hope you guys enjoy it. I have been using that approach for some of my projects, so far so good.
If you have any other ideas or questions, comment below!
Cheers, and see you next time!