Can

My way of using WireCache and some hints to make life easier with it

Recommended Posts

I highly recommend reading the second post, too before implementing anything, as it might simplify a lot, depending on your setup..
 
Because I just updated all MarkupCaches with newer WireCache, couple of weeks ago, and really like it, I thought why not share it.
 
So I got _init.php as prependTemplateFile, and _out.php as appendTemplateFile.
 
But let's check out the interesting part, for example an article.php template.
 
but for some pages, for example blog, it makes sense to include all children ;-)
You can include any page you like, or define a time or a template as expiration rule.
 
Here my defaults from the _init.php
$cacheNamespace = "hg";
$cacheTitle = "$page->template-" . $sanitizer->pageName($page->getLanguageValue($en, "title")) . "-$page->id-{$user->language->name}";
$cacheTitle .= $pageNum ? "-$pageNum": '';
$cacheExpire = $page;

I'm not exactly sure if there is any benefit in using a namespace, you can omit the namespace part and if needed just prefix the cache title.

Works both. You'll see why I added the namespace/prefix a little later ;-)
For the title I'm getting, the template, english page title (you can of course use the language title and omit the language name part, but I liked it better to have the caches grouped..
After language name I'm adding the page number if present.
If you need you can of course create a different, more or less specific cache title. Add get parameters or url segments for example.
Then I have $cacheExpire already set to $page as default value, so I don't need to set it in every template
 
So my markup (only the important parts) looks like this:
//You can have anything you like or need uncached above this

$cacheExpire = $page->chilren();
$cacheExpire->add($page);

$cache->getFor($cacheNamespace, $cacheTitle, "id=$cacheExpire", function($pages, $page, $users, $user) use($headline) {
    // as you can see, within the function() brackets we can pass any Processwire variable we need within our cached output.
    // If you don't need any you can of course leave the brackets empty
    // and if you need any other variable wich you had to define outside this function you can pass them via use()

    // so here goes all your markup you want to have cached
    // for example huge lists, or whatever
});

// Then I have some more uncached parts, a subscription form for example.
// After this comes another cached part, which gets -pagination appended to the title. Otherwise it would override the previous one.
// It's not only caching the pagination, I just needed a name for differentiation.

$cache->getFor($cacheNamespace, $cacheTitle.'-pagination', "id=$cacheExpire", function($pages, $page, $users, $user) use($headline) {
    // so here comes more cached stuff
});
After this your template could end or you can have more uncached and cached parts, just remember to append something to the cache title ;-)
 
Now comes, at least for me, the fun part haha :D
In my prepended _init.php template file I have the following code under the cache vars:
if($user->isSuperuser() && $input->get->cache == 'del')
{
    if($input->get->clearAllCaches == "true")
    {
        $allCaches = $cache->get("hg__*");
        foreach($allCaches as $k => $v) $cache->delete($k);
        $session->alert .= "<div class='alert alert-success closable expire'>All (".count($allCaches).") caches have been deleted. <i class='fa fa-close'></i></div>";
    }
    else
    {
        $currentPageCaches = $cache->get("hg__$page->template-" . $sanitizer->pageName($page->getLanguageValue($en, "title")) . "-$page->id*");
        foreach($currentPageCaches as $k => $v)
        {
            $cache->delete($k);
            $session->alert .= "<div class='alert alert-success closable expire'>Cache: $k has been deleted. <i class='fa fa-close'></i></div>";
        }
    }
    $session->redirect($page->url);
}
So when I append the parameter "?cache=del" to any URL all cache files within my namespace and beginning with the predefined $cacheTitle will be removed.
Means all language variations and the "-pagination & -comments" caches will be deleted, too. This is the else part.
But if I append "&clearAllCaches=true", to the first parameter, it will get all caches within my namespace and clear them.
Without the namespace it would clear Processwires caches (like the module cache), too.
I'm storing a little success message in a session called "alert" which is closable by the FontAwesome icon via jQuery and expires after some seconds, means it will remove itself, so I don't have to click ;-)
 
Maybe it makes more sense to change the cache title and have the page->id first, so you could select all related caches with
$cache->get("hg__{$page->id}*");
I liked them grouped by template in the database, but maybe I change my mind soon because of this :)
 
For not having to type those params manually I have two buttons in my _out.php template file.
I have a little, fixed to the left bottom corner admin menu with buttons for backend, edit (current page), and now clear cache button which unveils the clear all caches button on hover, so it's harder to click it by mistake.
 
When someone writes a comment, I added similar lines as above, after saving the comment, to clear comment caches.
Ah, the comment caches look like "-pagination" just with "-comments" appended instead.
 
I don't know if there is an easy way to expire a cache when a new children (especially backend created) is created, other than building a little hook module.
 
With MarkupCache it could be a pain to delete all those folders and files in /assets/ folder, especially with slow connection.
The database driven WireCache makes it much faster, and easier when set up those few lines of code to make it for you.
 
more about WireCache
 
 
Hope it helps someone and is okay for Tutorial section, if you have any questions, suggestions or ideas feel free to share.
 
Hasta luego
Can
  • Like 10

Share this post


Link to post
Share on other sites

a simple way of caching (nearly) everything using wirecache and 2 page render hooks in site/ready.php

// the next ensures that the following code will only run on front end (otherwise back end would get cached, too which results in problems)
// make sure to place anything you need in the backend before this line or change it to your needs..
if ((strpos($page->url, wire('config')->urls->admin) !== false) || ($page->id && $page->is('parent|has_parent=2'))) return;

$cacheName = "prefix__$page->id-$page->template-{$user->language->name}";
if ($urlSegment1) $cacheName .= "-$urlSegment1";
if ($urlSegment2) $cacheName .= "-$urlSegment2";
if ($urlSegment3) $cacheName .= "-$urlSegment3";
if ($input->pageNum > 1) $cacheName .= "-page$input->pageNum";

// if already cached exit here printing cached content (only 1 db query)
$wire->addHookBefore('Page::render', function() use($cache, $cacheName) {

    $cached = $cache->get($cacheName);
    if ($cached) {
        exit($cached);
    }

});

// not cached so far, continue as usual but save generated content to cache
$wire->addHookAfter('Page::render', function($event) use($cache, $cacheName) {

    $cached = $cache->get($cacheName);

    if (!$cached) {
        $cache->save($cacheName, $event->return);
    }

    unset($cached);

});

of course not the same as a proper flat file cache like procache but at least saving many database queries

make sure to adjust the $cacheName as needed, you can even cache query strings, almost everything you'd like

and you could wrap everything in a condition to only run if no incoming post or query strings so form submits keep working

example

if (!count($input->get) && !count($input->post) && !$config->ajax) {
    // cache logic from above
}

have fun ;)

Edited by Can
add backend cache prevention
  • Like 5

Share this post


Link to post
Share on other sites

glad you like it @szabesz :)

Right now I'm not using it productively but I'm planning to use it for a small site, too as it simplifies a lot because you can still fully use processwire/php dynamically

  • Like 1

Share this post


Link to post
Share on other sites

I have not yet used WireCache, but this is the kind of simplicity that I'm always looking for, that is why sooner or later I will try it out. Thanks!

  • Like 1

Share this post


Link to post
Share on other sites

@adrianmak you mean the site/ready.php hook method mentioned in my second post?

Then it really doesn't matter how you output your content, because site/ready.php will be invoked way before any page template.
The second hook "addHookAfter(Page::render" runs after all you template files have been processed and the output is almost ready, at this point $event->return in this hook contains your whole markup and the hook puts all of it in one database row using wirecache.

The first hook checks if there is a cached version of this page and outputs it using exit() which will just print the whole cached html markup and stops any further php, so you end up using only 1 database request for markup creation, if no cache file present it won't do anything, so everything will be output as normal (as if there were no caching at all) and then the second after hook will cache it...

the second posts approach is much simpler to implement and cleaner than the first posts one ;-)

Share this post


Link to post
Share on other sites

as mentioned above, wirecache uses database tables so you have to go to adminer or phpmyadmin (or similar) and check out the caches table of your page to see cached content, and you could change the page content before caching by e.g. adding a "cached" class to body or html tag

sidenote: there is template cache and I think some predecessor to wirecache which use file based cache

using the hooks you could implement your own caching logic/system, for example writing the page markup to a file using file_put_contents() and the before hook could check for the existence of the file and then file_get_contents() output it..

Share this post


Link to post
Share on other sites

@Can

Your code save me a lot. But it is still not quite working on my problem

 

My $cacheName is look like this

"$config->prefix__$page->id-$page->template-{$user->language->name}";

When I checked back the cache table, the cache name has no such a prefix . As a result,  mobile devices are still received the cache of desktop template.

Is the $config variable is not vabailable in ready.php scope ?

Share this post


Link to post
Share on other sites

you define the cache name above/outside of the functions, like in my example right? It should actually work..

uh I'm quite sure you need to wrap $config->prefix in curly braces like so

"{$config->prefix}__$page->id-$page->template-{$user->language->name}";

without them php thinks the two underscores are part of the variable but there is no variable called $config->prefix__

  • Like 1

Share this post


Link to post
Share on other sites

I tried to append string <!--cached page--> before cache saved into db

I altered the code into

 

 if (!$cached) {
        $cached .= "<!--cached page-->";
        $cache->save($cacheName, $event->return);        
    }
 

 

However, checking with the cache table the string didn't append

Share this post


Link to post
Share on other sites

glad you solved it :)

$cached is not being saved to cache, my code example might not be the most logical but it's short ;-)

WireCache->save("cacheName", "data") so we directly save $event->return to cache and not $cached

shortest way to achieve what you what would be 

if (!$cached) {
    $event->return .= "<!--cached page-->";
    $cache->save($cacheName, $event->return);        
}

or even

if (!$cached) {
    $cache->save($cacheName, "$event->return<!--cached page-->");        
}

edit

usually flat files are faster, this one is just using the easiest way as everything is already there with WireCache
but as I said you can replace  it quite easily with some flat file logic, but you would need to alter cache name too..

easier and more efficient would then be to use ProCache as it uses flat files and bypasses any php mysql serving just static files

Share this post


Link to post
Share on other sites
2 hours ago, Can said:

usually flat files are faster, this one is just using the easiest way as everything is already there with WireCache
but as I said you can replace  it quite easily with some flat file logic, but you would need to alter cache name too..

easier and more efficient would then be to use ProCache as it uses flat files and bypasses any php mysql serving just static files

There's also the MarkupCache module, which is similar to WireCache, but file based. ProCache is indeed a nice solution, but like TemplateCache it's not going to solve @adrianmak's problem of caching different markup for mobile/desktop requests.

  • Like 2

Share this post


Link to post
Share on other sites

I found that there is some minor issue with the code.

All admins pages, even the pw's admin backend login page (cached), it is not possible to login to the backend.

How could excluded all admin related pages not to cache?

 

Updated:

I solved by myself.

Simply checking the template whether it is a admin template.

If it is a admin template, dont read or write cache

 

Share this post


Link to post
Share on other sites

thanks for mentioning @LostKobrakai good to know :) right markupcache was the thing I was thinking about

@adrianmak glad you solved it yourself, actually I forgot that I got my ready.php kind of split, first code blocks are intended to run on back and front end, like save hooks and stuff, then i got this code

if ((strpos($page->url, wire('config')->urls->admin) !== false) || ($page->id && $page->is('parent|has_parent=2'))) return;

i think i found this or something similar somewhere in the forums, am not entirely sure if the second part is really needed as the first part checking if url contains admin path (usually processwire) you could extend it by placing / before and after and I don't know if there are any possibilities of admin pages not containing admin url..
anyway, everything after this line will only run on front end..

  • Like 1

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.

  • Similar Content

    • By gRegor
      I'm using "quiet" saving in the Webmention module and was wondering if that prevents the page's cache from being cleared. I'm just using the built-in page caching with the option "Clear cache for saved page only (default)"
      If it matters, the page-save I'm calling is specifying the Webmention Field, rather than just $page->save();
      For reference, the code is https://github.com/gRegorLove/ProcessWire-Webmention/blob/master/Webmention/Webmention.module#L840
    • By Can
      Hey folks,

      just got an "Unable to encode array data for cache.." error from WireCache.

      Had an ´ (acute accent) in a page title, after changing to ' (single quote)  everything worked again.

      I have to say, that the acute accent didn't looked good anyway, got this weird black thing^^

      The only thing I tried so far, was changing line 318 of WireCache.php to
      $data = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); couldn't recognize any difference..
      So am I better off changing those (don't know if there are more..) or what else can I do?