Jump to content
LostKobrakai

WireCache and JSON data quirks

Recommended Posts

WireCache is a really nice way to quickly cache data to the database, but when working with json data there are some quirks. Imagine the following examples:

$data = $cache->get('my-key', WireCache::expireHourly);
if(!$data){
	$data = […]
	$cache->save('my-key', $data, WireCache::expireHourly);
}

// API response
$response = json_decode($data);
$image = $response->data[0]->image;

// or 
$html = "<div data-json='$data'></div>";

Both should work from the quick look. Both will fail as soon as the cache kicks in. This is because the implementation of WireCache tries to be smart and does automatically decode data, which is detected to be json. But it doesn't just decode it like in my example, but rather uses json_decode()'s second parameter to decode the json as associative array(s) instead of stdobject(s).

If you prefer the object syntay to traverse your json data or you really want to store raw json, then I've got two hooks for you, which do prevent the automatic json detection of WireCache, so you can work with the stringified json as you need to. Just replace the get() and save() calls in the example with getRaw() and saveRaw().

$wire->addHook('WireCache::saveRaw', function(HookEvent $event){
	$args = $event->arguments();
	$args[1] = '::RAW::' . $args[1];
	return $event->return = call_user_func_array([$event->object, 'save'], $args);
});

$wire->addHook('WireCache::getRaw', function(HookEvent $event){
	$args = $event->arguments();
	return $event->return = str_replace('::RAW::', '', call_user_func_array([$event->object, 'get'], $args));
});
  • Like 11

Share this post


Link to post
Share on other sites

Or get + set combined:

$data = $cache->get('my-key', WireCache::expireHourly, function() {

	return []; // return array (for example), if returns absolute false, won't get cached.
});

Share this post


Link to post
Share on other sites

I don't like the one line syntax, because I'd need to explicitly 'use' all variables declared somewhere else in the template, so they are available in the anonymous function. And secondly it's not possible to add the things I added above without recreating the whole get() function in a hook.

Share this post


Link to post
Share on other sites

@LostKobrakai,

  Thank you for taking the time to point this out. I have not yet done any coding involving Wire Cache, I've just started playing around with Hanna codes :lol:  , but I am definitely going to be saving this link for future reference. Thanks again.

Share this post


Link to post
Share on other sites

Thanks for this @LostKobrakai!

I just came across this very problem, thankfully I found this thread. Took me a while to understand how your solution works, certainly a creative solution to just add a string to make the cached value invalid JSON 😄

I modified your code slightly:

const RAW_PREFIX = '::RAW::';

$wire->addHook('WireCache::saveRaw', function (HookEvent $event) {
    $args = $event->arguments();
    $args[1] = RAW_PREFIX . $args[1];
    return $event->return = call_user_func_array([$event->object, 'save'], $args);
});

$wire->addHook('WireCache::getRaw', function (HookEvent $event) {
    $args = $event->arguments();
    $cached_val = call_user_func_array([$event->object, 'get'], $args);
    return $event->return = $cached_val === null
        ? null
        : substr($cached_val, strlen(RAW_PREFIX));
});

I made two notable changes:

  • If the Cache API returns null (i.e. no cached value exists), getRaw will also return null instead of an empty string.
  • Use substr instead of str_replace, since it will be faster for long strings and it won't break anything in case the RAW_PREFIX (::HOOK::) appears anywhere inside the cached value (I'm a bit paranoid 😄
  • Like 1

Share this post


Link to post
Share on other sites

@MoritzLost 

Do you have an example of using your getRaw hook with a function as a fallback (see last example: https://processwire.com/api/ref/wire-cache/get/)

In this scenario, the returned value of the function is set as the cache value, but this falls back to the $cache->save where, ideally, it would use the saveRaw.

Would you recommend not using the function fallback and instead using getRaw and saveRaw (based on the value returned from getRaw).

Thanks!

Share this post


Link to post
Share on other sites

@baronmunchowsen Hm, that is true. I don't often use the callback argument, so I haven't encountered that problem yet. The hooked method passes on the argument, but if it's used the WireCache class will still use the normal save() method to save the return value, as you said. I suppose you could replicate the functionality of the protected renderCacheValue method inside the hook method, and execute the fallback function manually instead of passing it on to $cache->get. But for my taste that would be too much code duplication. So yes, I'd say it's best to not use the fallback function with the saveRaw method.

Share this post


Link to post
Share on other sites

Thanks @MoritzLost I implemented similar to as follows, which itself could be abstracted to a function or method:

$cache_key = '...some cache key...';            
$response = $response = $this->cache->getRaw($cache_key, 120);
if(!$response) {
    $response = '{...some json response...}';
    $this->cache->saveRaw($cache_key, $response, 120);
}
return $response;

 

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...