May 15, 2008

Dynamic Caching in TYPO3

Category: Zachary Davis

By: Zachary Davis

When we build social networking sites, we often run into cache-related issues. Read on for a discussion of how to use authentication services to accomplish more dynamic page caching in TYPO3.

(This article is cross-posted at with inline code snippets).

By default TYPO3 offers frontend content caching mechanisms that work for most "normal" websites and most "normal" website functionality. It's easy to enable and disable the page cache in the page configuration (pages record) and through calls in extensions to $GLOBALS['TSFE']->set_no_cache(). Additionally, logged in users who belong to different usergroups will see different cached versions of the page, which is a great feature in TYPO3. On top of this different cached versions of pages will be created for each TypoScript condition in the TypoScript setup (so go light on the conditions! every condition leads to another cached version of the page -- multiple conditions in TypoScript can lead to the exponential growth of the cache tables).

In some situations, however, a site needs something different. More and more of our clients are asking for social networking functionality in TYPO3 which means that content sometimes needs to be highly tailored to individual users on the frontend. Imagine, for a moment, that we're making a social networking site that focuses on books (something like, for example, On Jane's (our user's) profile page we're going to list the books that she has on her shelf and the other users on the site that are have agreed to be her "friend". Let's also say, for example, that only users that are her "friend" are allowed to see additional details on her profile page. In this scenario, there are three different types of users who could be looking at this profile page: Jane (the page's owner), One of Jane's friends, and a user who is not in Jane's list of friends. This situation presents a challenge in TYPO3 because we there's no readily available model for caching the three different views of the content (the owner's view, the friend's view, and the non-friend view).

Technically speaking, here's the challenge: we have a plugin instance on a page. The plugin instance displays a single frontend user's profile. The profile is publicly accessible and anyone can view it, logged in or not. If the frontend user is logged in, however, a determination needs to be made as to whether they are in the "friend network" of the user whose profile is being displayed. If the frontend user whose profile is being displayed views the page and they are logged in, it should show different content, and allow them to edit the content page (via links, AJAX stuff, etc). When they save changes to the page, the cache should be cleared for their profile only so that changes are immediately seen by other users viewing the profile. In other words, Jane needs to see a non-cached version of the page, while the friend user and non-friend user need to see different cached versions of the page.

So, there are three items we have to solve:

  • Turn off caching if the page is being viewed by the owner of the profile
  • Different page cache based on whether or not the viewing user is a friend of the user whose profile is being viewed
  • Clear the page cache only for that user's profile when the content is changed

Let's start with the first challenge. We want to turn off caching of a certain page based on who's logged in, and certain GET var values (we have to be sure they are viewing their own profile). The GET var we can expect is something like &tx_myprofile_pi1[uid]=456. If the logged in user's feuser uid is 456, then we want to turn off caching on this page. (You may also want to show the user's profile if they are logged in and there is no profile uid specified, a logged in user can just go to and see their profile, but I'm not going to talk about that here, it's pretty easy though).

There are probably a number of solutions to this problem, but the one I've used a couple times is to create a new auth service that extends tx_sv_authbase and which does checks the following conditions:

  • Determine if the user is logged in
  • Determine the page being viewed is the single profile page
  • Determine if the user is viewing their own profile

If all those conditions are met, then we call: $GLOBALS['TSFE']->set_no_cache(). Otherwise, we will dynamically choose whether to assign the user to a generic "friend" group and let normal TYPO3 caching occur.

Here's a quick run through of creating this auth service. First, kickstart an extension, create a new service, save it. This snippet should be in the ext_localconf.php of your new extension.

Click here to view the first code snippet

Then, you have to actually create the service class that extends tx_sv_authbase. It'll start like this:

Click here to view the second code snippet

Copy the code from the parent class for the method getGroups(). We're going to add some code in the the part that checks that $this->mode=='getGroupsFE'.

Click here to view the third code snippet

Ok, so, to make that work, just make sure that you have a page id comma list in $this->profilePageIdList. And you'll need that method getFriendshipGroup() method created too (that's part of the next problem, but it's in the code here for simplicity).

There's the dynamic caching (note: adapted from functioning code, but not tested, my require tweaks!)

On to the next problem, which you'll recall is: "different page cache based on whether the viewing user is in the network of the user whose profile is being viewed".

You noticed the $this->getFriendshipGroup() call, which adds the frontend user to usergroup based on something. In our example of a friend network situation, you'll need to have some means of making that determination, a table in your database that keeps track of who's friend with who. Then you want to create two new frontend usergroups in the backend, I usually call them something conspicuous like "__FRIEND__", "__BFF__" and add a comment to the record so that other users know it's a pseudo-usergroup that shouldn't really be assigned to frontend users.

So, those new usergroups will have uids, and that function getFriendshipGroup() needs to return the uid of the group that this user belongs to.

Hurray! Part two done! This is pretty cool, but probably could be done in other ways. I'd be curious to see if anyone has come up with other solutions for this dynamic caching challenge.

The last challenge is: "clear the page cache only for that user's profile when the content is changed." This is something every developer should know about because it's very useful. On the cache_pages record for a given page, you can set a register value (in the field reg1) specific to something about that page, in this case, let's set it to the uid of the feuser who's profile is being viewed, so that we have an easy way to find all profile pages for a given profile, for friends and enemies alike.

In your profile extension, once you know who's profile you're displaying, do this:

Click here to view the fourth code snippet

When/if TYPO3 caches the page, that cache record will have that user's uid. Then, you have this function:

Click here to view the fifth code snippet

When the content is change (page is saved), call that method with the page uid as the first argument and user's profile as the second. All caches are then cleared and the profile is generated afresh!

And look at that, we've extended TYPO3's caching system so that we can make determinations about the state of the cache on the fly, show different users different content, and clear specific caches when we want to! Let me know what you think, or if you have anything to add, any comments, etc, I'd love to hear them.





No comments yet. Be the first to comment on this!

Sorry, comments are closed for this post.