mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-09-18 07:26:40 +02:00
e96d40d391
Spelling mistake fix.
239 lines
7.9 KiB
Markdown
239 lines
7.9 KiB
Markdown
title: Partial Caching
|
|
summary: Cache SilverStripe templates to reduce database queries.
|
|
|
|
# Partial Caching
|
|
|
|
Partial caching is a feature that allows the caching of just a portion of a page.
|
|
|
|
:::ss
|
|
<% cached 'CacheKey' %>
|
|
$DataTable
|
|
...
|
|
<% end_cached %>
|
|
|
|
|
|
Each cache block has a cache key. A cache key is an unlimited number of comma separated variables and quoted strings.
|
|
Every time the cache key returns a different result, the contents of the block are recalculated. If the cache key is
|
|
the same as a previous render, the cached value stored last time is used.
|
|
|
|
Since the above example contains just one argument as the cache key, a string (which will be the same every render) it
|
|
will invalidate the cache after a given amount of time has expired (default 10 minutes).
|
|
|
|
Here are some more complex examples:
|
|
|
|
:::ss
|
|
<% cached 'database', LastEdited %>
|
|
<!-- that updates every time the record changes. -->
|
|
<% end_cached %>
|
|
|
|
<% cached 'loginblock', CurrentMember.ID %>
|
|
<!-- cached unique to the user. i.e for user 2, they will see a different cache to user 1 -->
|
|
<% end_cached %>
|
|
|
|
<% cached 'loginblock', LastEdited, CurrentMember.isAdmin %>
|
|
<!-- recached when block object changes, and if the user is admin -->
|
|
<% end_cached %>
|
|
|
|
An additional global key is incorporated in the cache lookup. The default value for this is
|
|
`$CurrentReadingMode, $CurrentUser.ID`. This ensures that the current `[api:Versioned]` state and user ID are used.
|
|
This may be configured by changing the config value of `SSViewer.global_key`. It is also necessary to flush the
|
|
template caching when modifying this config, as this key is cached within the template itself.
|
|
|
|
For example, to ensure that the cache is configured to respect another variable, and if the current logged in
|
|
user does not influence your template content, you can update this key as below;
|
|
|
|
**mysite/_config/app.yml**
|
|
|
|
:::yaml
|
|
SSViewer:
|
|
global_key: '$CurrentReadingMode, $Locale'
|
|
|
|
|
|
## Aggregates
|
|
|
|
Often you want to invalidate a cache when any object in a set of objects change, or when the objects in a relationship
|
|
change. To do this, SilverStripe introduces the concept of Aggregates. These calculate and return SQL aggregates
|
|
on sets of [api:DataObject]s - the most useful for us being the `Max` aggregate.
|
|
|
|
For example, if we have a menu, we want that menu to update whenever _any_ page is edited, but would like to cache it
|
|
otherwise. By using aggregates, we do that like this:
|
|
|
|
:::ss
|
|
<% cached 'navigation', List(SiteTree).max(LastEdited), List(SiteTree).count() %>
|
|
|
|
The cache for this will update whenever a page is added, removed or edited.
|
|
|
|
If we have a block that shows a list of categories, we can make sure the cache updates every time a category is added
|
|
or edited
|
|
|
|
:::ss
|
|
<% cached 'categorylist', List(Category).max(LastEdited), List(Category).count() %>
|
|
|
|
<div class="notice" markdown="1">
|
|
Note the use of both `.max(LastEdited)` and `.count()` - this takes care of both the case where an object has been
|
|
edited since the cache was last built, and also when an object has been deleted since the cache was last built.
|
|
</div>
|
|
|
|
We can also calculate aggregates on relationships. A block that shows the current member's favorites needs to update
|
|
whenever the relationship `Member::$has_many = array('Favourites' => Favourite')` changes.
|
|
|
|
:::ss
|
|
<% cached 'favourites', CurrentMember.ID, CurrentMember.Favourites.max(LastEdited) %>
|
|
|
|
## Cache key calculated in controller
|
|
|
|
In the previous example the cache key is getting a bit large, and is complicating our template up. Better would be to
|
|
extract that logic into the controller.
|
|
|
|
:::php
|
|
|
|
public function FavouriteCacheKey() {
|
|
$member = Member::currentUser();
|
|
|
|
return implode('_', array(
|
|
'favourites',
|
|
$member->ID,
|
|
$member->Favourites()->max('LastEdited')
|
|
));
|
|
}
|
|
|
|
Then using that function in the cache key:
|
|
|
|
:::ss
|
|
<% cached FavouriteCacheKey %>
|
|
|
|
|
|
## Cache blocks and template changes
|
|
|
|
In addition to the key elements passed as parameters to the cached control, the system automatically includes the
|
|
template name and a sha1 hash of the contents of the cache block in the key. This means that any time the template is
|
|
changed the cached contents will automatically refreshed.
|
|
|
|
## Purposely stale data
|
|
|
|
In some situations it's more important to be fast than to always be showing the latest data. By constructing the cache
|
|
key to invalidate less often than the data updates you can ensure rendering time is constant no matter how often the
|
|
data updates.
|
|
|
|
For instance, if we show some blog statistics, but are happy having them be slightly stale, we could do
|
|
|
|
:::ss
|
|
<% cached 'blogstatistics', Blog.ID %>
|
|
|
|
|
|
which will invalidate after the cache lifetime expires. If you need more control than that (cache lifetime is
|
|
configurable only on a site-wide basis), you could add a special function to your controller:
|
|
|
|
:::php
|
|
public function BlogStatisticsCounter() {
|
|
return (int)(time() / 60 / 5); // Returns a new number every five minutes
|
|
}
|
|
|
|
|
|
and then use it in the cache key
|
|
|
|
:::ss
|
|
<% cached 'blogstatistics', Blog.ID, BlogStatisticsCounter %>
|
|
|
|
|
|
## Cache block conditionals
|
|
|
|
You may wish to conditionally enable or disable caching. To support this, in cached tags you may (after any key
|
|
arguments) specify 'if' or 'unless' followed by a standard template variable argument. If 'if' is used, the resultant
|
|
value must be true for that block to be cached. Conversely if 'unless' is used, the result must be false.
|
|
|
|
Following on from the previous example, you might wish to only cache slightly-stale data if the server is experiencing
|
|
heavy load:
|
|
|
|
:::ss
|
|
<% cached 'blogstatistics', Blog.ID if HighLoad %>
|
|
|
|
|
|
By adding a `HighLoad` function to your `Page_Controller`, you could enable or disable caching dynamically.
|
|
|
|
To cache the contents of a page for all anonymous users, but dynamically calculate the contents for logged in members,
|
|
use something like:
|
|
|
|
:::ss
|
|
<% cached unless CurrentUser %>
|
|
|
|
## Uncached
|
|
|
|
The template tag 'uncached' can be used - it is the exact equivalent of a cached block with an if condition that always
|
|
returns false. The key and conditionals in an uncached tag are ignored, so you can easily temporarily disable a
|
|
particular cache block by changing just the tag, leaving the key and conditional intact.
|
|
|
|
:::ss
|
|
<% uncached %>
|
|
|
|
|
|
## Nested cache blocks
|
|
|
|
You can also nest independent cache blocks Any nested cache blocks are calculated independently from their containing
|
|
block, regardless of the cached state of that container.
|
|
|
|
This allows you to wrap an entire page in a cache block on the page's LastEdited value, but still keep a member-specific
|
|
portion dynamic, without having to include any member info in the page's cache key.
|
|
|
|
An example:
|
|
|
|
:::ss
|
|
<% cached LastEdited %>
|
|
Our wonderful site
|
|
|
|
<% cached Member.ID %>
|
|
Welcome $Member.Name
|
|
<% end_cached %>
|
|
|
|
$ASlowCalculation
|
|
<% end_cached %>
|
|
|
|
|
|
This will cache the entire outer section until the next time the page is edited, but will display a different welcome
|
|
message depending on the logged in member.
|
|
|
|
Cache conditionals and the uncached tag also work in the same nested manner. Since Member.Name is fast to calculate, you
|
|
could also write the last example as:
|
|
|
|
:::ss
|
|
<% cached LastEdited %>
|
|
Our wonderful site
|
|
|
|
<% uncached %>
|
|
Welcome $Member.Name
|
|
<% end_uncached %>
|
|
|
|
$ASlowCalculation
|
|
<% end_cached %>
|
|
|
|
<div class="warning" markdown="1">
|
|
Currently cached blocks can not be contained within if or loop blocks. The template engine will throw an error
|
|
letting you know if you've done this. You can often get around this using aggregates.
|
|
</div>
|
|
|
|
Failing example:
|
|
|
|
:::ss
|
|
<% cached LastEdited %>
|
|
|
|
<% loop $Children %>
|
|
<% cached LastEdited %>
|
|
$Name
|
|
<% end_cached %>
|
|
<% end_loop %>
|
|
|
|
<% end_cached %>
|
|
|
|
Can be re-written as:
|
|
|
|
:::ss
|
|
<% cached LastEdited %>
|
|
|
|
<% cached AllChildren.max(LastEdited) %>
|
|
<% loop $Children %>
|
|
$Name
|
|
<% end_loop %>
|
|
<% end_cached %>
|
|
|
|
<% end_cached %>
|