mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
DOC Update Partial Template Cache documentation
- create a new documentation section explaining the "cached" block - clean up the Performance documentation section, moving explanations about "cached" block away, adding new warnings and recommendations
This commit is contained in:
parent
1eb66e2258
commit
e632ee3e52
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: Caching
|
title: Caching
|
||||||
summary: Reduce rendering time with cached templates and understand the limitations of the ViewableData object caching.
|
summary: How template variables are cached.
|
||||||
icon: rocket
|
icon: rocket
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -35,13 +35,11 @@ When we render `$Counter` to the template we would expect the value to increase
|
|||||||
|
|
||||||
## Partial caching
|
## Partial caching
|
||||||
|
|
||||||
Partial caching is a feature that allows the caching of just a portion of a page. Instead of fetching the required data
|
Partial caching is a feature that allows caching of a portion of a page as a single string value. For more details read [its own documentation](partial_template_caching).
|
||||||
from the database to display, the contents of the area are fetched from a [cache backend](../performance/caching).
|
|
||||||
|
|
||||||
|
Example:
|
||||||
```ss
|
```ss
|
||||||
<% cached 'MyCachedContent', LastEdited %>
|
<% cached $CacheKey if $CacheCondition %>
|
||||||
$Title
|
$CacheableContent
|
||||||
<% end_cached %>
|
<% end_cached %>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,335 @@
|
|||||||
|
---
|
||||||
|
title: Partial Template Caching
|
||||||
|
summary: Cache a section of a template Reduce rendering time with cached templates and understand the limitations of the ViewableData object caching.
|
||||||
|
icon: tags
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partial template caching
|
||||||
|
|
||||||
|
Partial template caching is a feature that allows caching of rendered portions of templates. Cached content
|
||||||
|
is fetched from a [cache backend](../performance/caching), instead of being regenerated repeatedly.
|
||||||
|
|
||||||
|
|
||||||
|
### Base syntax
|
||||||
|
|
||||||
|
```ss
|
||||||
|
<% cached $CacheKey if $CacheCondition %>
|
||||||
|
$CacheableContent
|
||||||
|
<% end_cached %>
|
||||||
|
```
|
||||||
|
|
||||||
|
This is not a definitive example of the syntax, but it shows the most common use case.
|
||||||
|
|
||||||
|
[note]
|
||||||
|
See also [Complete Syntax definition](#complete-syntax-defintition) section
|
||||||
|
[/note]
|
||||||
|
|
||||||
|
The key parts are `$CacheKey`, `$CacheCondition` and `$CacheableContent`.
|
||||||
|
The following sections explain every one of them in more detail.
|
||||||
|
|
||||||
|
|
||||||
|
#### $CacheKey
|
||||||
|
|
||||||
|
Defines a unique key for the cache storage.
|
||||||
|
|
||||||
|
[warning]
|
||||||
|
Avoid heavy computations in `$CacheKey` as it is evaluated for every template render.
|
||||||
|
[/warning]
|
||||||
|
|
||||||
|
The formal definition is
|
||||||
|
- Optional list of template expressions delimited by comma
|
||||||
|
|
||||||
|
The syntax is
|
||||||
|
|
||||||
|
```ss
|
||||||
|
<% cached [$key1[, $key2[, ...[, $keyN]]]] ... %>
|
||||||
|
```
|
||||||
|
|
||||||
|
The final value is concatenated by the Template Engine into a string. When doing so, Template Engine
|
||||||
|
adds some extra values to the mix to make it more unique and prevent clashing between cache keys from
|
||||||
|
different templates.
|
||||||
|
|
||||||
|
Here is how it works in detail:
|
||||||
|
|
||||||
|
1. `SilverStripe\View\SSViewer::$global_key` hash
|
||||||
|
|
||||||
|
With the current template context, value of the `$global_key` variable is rendered into a string and hashed.
|
||||||
|
|
||||||
|
`$global_key` content is inserted into the template "as is" at the compilation stage. Changing its value
|
||||||
|
won't have any effect until template recompilation (e.g. on cache flush).
|
||||||
|
|
||||||
|
By default it equals to `'$CurrentReadingMode, $CurrentUser.ID'`.
|
||||||
|
This ensures the current [Versioned](api:SilverStripe\Versioned\Versioned) state and user ID are used.
|
||||||
|
At runtime that will become something like `'LIVE, 0'` (for unauthenticated users in live mode).
|
||||||
|
|
||||||
|
As usual, you may override its value via YAML configs. For example:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
# app/_config/view.yml
|
||||||
|
SilverStripe\View\SSViewer:
|
||||||
|
global_key: '$CurrentReadingMode, $CurrentUser.ID, $Locale'
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Block hash
|
||||||
|
|
||||||
|
Everything between the `<% cached %> ... <% end_cached %>` is taken as text (with no rendering) and hashed.
|
||||||
|
|
||||||
|
This is done at the template compilation stage, so
|
||||||
|
the compiled version of the template contains the hash precalculated.
|
||||||
|
|
||||||
|
`Block hash` main purpose is to invalidate cache when template itself changes.
|
||||||
|
|
||||||
|
3. `$CacheKey` hash
|
||||||
|
|
||||||
|
All keys of `$CacheKey` are processed, concatenated and the final value is hashed.
|
||||||
|
If there are no values defined, this step is skipped.
|
||||||
|
|
||||||
|
4. Make the final key vaule
|
||||||
|
|
||||||
|
A string produced by concatenation of all the values mentioned above is used as the final value.
|
||||||
|
|
||||||
|
Even if `$CacheKey` is omitted, `SilverStripe\View\SSViewer::$global_key` and `Block hash` values are still
|
||||||
|
getting used to generate cache key for the caching backend storage.
|
||||||
|
|
||||||
|
[note]
|
||||||
|
##### Cache key calculated in controller
|
||||||
|
|
||||||
|
If your caching logic is complex or re-usable, you can define a method on your controller to generate a cache key
|
||||||
|
fragment.
|
||||||
|
|
||||||
|
For example, a block that shows a collection of rotating slides needs to update whenever the relationship
|
||||||
|
`Page::$many_many = ['Slides' => 'Slide']` changes. In `PageController`:
|
||||||
|
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function SliderCacheKey()
|
||||||
|
{
|
||||||
|
$fragments = [
|
||||||
|
'Page-Slides',
|
||||||
|
$this->ID,
|
||||||
|
// identify which objects are in the list and their sort order
|
||||||
|
implode('-', $this->Slides()->Column('ID')),
|
||||||
|
// works for both has_many and many_many relationships
|
||||||
|
$this->Slides()->max('LastEdited')
|
||||||
|
];
|
||||||
|
return implode('-_-', $fragments);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then reference that function in the cache key:
|
||||||
|
|
||||||
|
|
||||||
|
```ss
|
||||||
|
<% cached $SliderCacheKey if ... %>
|
||||||
|
```
|
||||||
|
[/note]
|
||||||
|
|
||||||
|
|
||||||
|
#### $CacheCondition
|
||||||
|
|
||||||
|
Defines if caching is required for the block.
|
||||||
|
|
||||||
|
Condition is optional and if omitted, `true` is implied.
|
||||||
|
|
||||||
|
If the value is `false`, the block skips `$CacheKey` evaluation completely, does not lookup
|
||||||
|
the data in the cache storage, neither preserve any data in the storage.
|
||||||
|
The template within the block keeps working as is, same as it would do without
|
||||||
|
`<% cached %>` block surrounding it.
|
||||||
|
|
||||||
|
Although `$CacheCondition` is optional, it is highly recommended. For example,
|
||||||
|
if you use `$DataObject->ID` as your `$CacheKey`, you may use
|
||||||
|
`$DataObject->ID > 0` as the condition.
|
||||||
|
|
||||||
|
Without it:
|
||||||
|
- your cache backend will always be queried for cache (for every template render)
|
||||||
|
- your cache backend may be cluttered with redundant and useless data
|
||||||
|
|
||||||
|
|
||||||
|
[warning]
|
||||||
|
The `$CacheCondition` value is evaluated on every template render and should be as lightweight as possible.
|
||||||
|
[/warning]
|
||||||
|
|
||||||
|
|
||||||
|
#### $CacheableContent
|
||||||
|
|
||||||
|
The content block may contain any usual template syntax.
|
||||||
|
|
||||||
|
|
||||||
|
### Cache storage
|
||||||
|
|
||||||
|
The cache storage may be re-configured via `Psr\SimpleCache\CacheInterface.cacheblock` key for [Injector](../extending/injector).
|
||||||
|
By default, it is initialised by `SilverStripe\Core\Cache\DefaultCacheFactory` with the following parameters:
|
||||||
|
|
||||||
|
- `namespace: "cacheblock"`
|
||||||
|
- `defaultLifetime: 600`
|
||||||
|
|
||||||
|
[note]
|
||||||
|
The defaultLifetime 600 means every cache record expires in 10 minutes.
|
||||||
|
If you have good `$CacheKey` and `$CacheCondition` implementations, you may want to tune these settings to
|
||||||
|
improve performance.
|
||||||
|
[/note]
|
||||||
|
|
||||||
|
|
||||||
|
### Nested cached blocks
|
||||||
|
|
||||||
|
Every nested cache block is processed independently.
|
||||||
|
|
||||||
|
Let's consider the following example:
|
||||||
|
```ss
|
||||||
|
<% cached $PageKey %>
|
||||||
|
<!-- Header -->
|
||||||
|
<% cached $BodyKey %> <!-- Body --> <% end_cached %>
|
||||||
|
<!-- Footer -->
|
||||||
|
<% end_cached %>
|
||||||
|
```
|
||||||
|
|
||||||
|
The template processor will transparently flatten the structure into something similar to the following pseudo-code:
|
||||||
|
|
||||||
|
```ss
|
||||||
|
<% cached $PageKey %><!-- Header --><% end_block %>
|
||||||
|
<% cached $BodyKey %><!-- Body --><% end_cached %>
|
||||||
|
<% cached $PageKey %><!-- Footer --><% end_cached %>
|
||||||
|
```
|
||||||
|
|
||||||
|
[note]
|
||||||
|
`$PageKey` is used twice, but evaluated only once per render because of [template object caching](caching/#object-caching).
|
||||||
|
[/note]
|
||||||
|
|
||||||
|
|
||||||
|
### Uncached
|
||||||
|
|
||||||
|
The tag `<% uncached %> ... <% end_uncached %>` disables caching for its content.
|
||||||
|
|
||||||
|
```ss
|
||||||
|
<% cached $PageKey %>
|
||||||
|
<!-- Header -->
|
||||||
|
<% uncached %><!-- Body --><% end_uncached %>
|
||||||
|
<!-- Footer -->
|
||||||
|
<% end_cached %>
|
||||||
|
```
|
||||||
|
|
||||||
|
Because of the nested block flattening (see above), it works seamlessly on any level of depth.
|
||||||
|
|
||||||
|
[warning]
|
||||||
|
The `uncached` block only works on the lexical level.
|
||||||
|
If you have a template that caches content rendering another template with included uncached blocks,
|
||||||
|
those will not have any effect on the parent template caching blocks.
|
||||||
|
[/warning]
|
||||||
|
|
||||||
|
|
||||||
|
### Nesting in FOR and IF blocks
|
||||||
|
|
||||||
|
Currently, a cache block cannot be included in `if` and `loop` blocks.
|
||||||
|
The template engine will throw an error letting you know if you've done this.
|
||||||
|
|
||||||
|
[note]
|
||||||
|
You may often get around this using aggregates or by un-nesting the block.
|
||||||
|
|
||||||
|
E.g.
|
||||||
|
|
||||||
|
```
|
||||||
|
<% cached $LastEdited %>
|
||||||
|
<% loop $Children %>
|
||||||
|
<% cached $LastEdited %>
|
||||||
|
$Name
|
||||||
|
<% end_cached %>
|
||||||
|
<% end_loop %>
|
||||||
|
<% end_cached %>
|
||||||
|
```
|
||||||
|
|
||||||
|
Might be re-written as something like that:
|
||||||
|
|
||||||
|
```
|
||||||
|
<% cached $LastEdited %>
|
||||||
|
<% cached $AllChildren.max('LastEdited') %>
|
||||||
|
<% loop $Children %>
|
||||||
|
$Name
|
||||||
|
<% end_loop %>
|
||||||
|
<% end_cached %>
|
||||||
|
<% end_cached %>
|
||||||
|
```
|
||||||
|
[/note]
|
||||||
|
|
||||||
|
|
||||||
|
### Unless (syntax sugar)
|
||||||
|
|
||||||
|
`if` keyword may be swapped with keyword `unless`, which inverts the boolean value evaluation.
|
||||||
|
|
||||||
|
The two following forms produce the same result
|
||||||
|
|
||||||
|
```ss
|
||||||
|
<% cached unless $Key %>
|
||||||
|
"unless $Cond" === "if not $Cond"
|
||||||
|
<% end_cached %>
|
||||||
|
```
|
||||||
|
|
||||||
|
```ss
|
||||||
|
<% cached if not $Key %>
|
||||||
|
"unless $Cond" === "if not $Cond"
|
||||||
|
<% end_cached %>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Complete Syntax definition
|
||||||
|
|
||||||
|
```ss
|
||||||
|
<% [un]cached [$CacheKey[, ...]] [(if|unless) $CacheCondition] %>
|
||||||
|
$CacheContent
|
||||||
|
<% end_[un]cached %>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```ss
|
||||||
|
<% cached %>
|
||||||
|
The key is: hash of the template code within the block with $global_key.
|
||||||
|
This content is always cached.
|
||||||
|
<% end_cache %>
|
||||||
|
```
|
||||||
|
|
||||||
|
```ss
|
||||||
|
<% cached $Key %>
|
||||||
|
Cached separately for every distinct $Key value
|
||||||
|
<% end_cached %>
|
||||||
|
```
|
||||||
|
|
||||||
|
```ss
|
||||||
|
<% cached $KeyA, $KeyB %>
|
||||||
|
Cached separately for every combination of $KeyA and $KeyB
|
||||||
|
<% end_cached %>
|
||||||
|
```
|
||||||
|
|
||||||
|
```ss
|
||||||
|
<% cached $Key if $Cond %>
|
||||||
|
Cached only if $Cond == true
|
||||||
|
<% end_cached %>
|
||||||
|
```
|
||||||
|
|
||||||
|
```ss
|
||||||
|
<% cached $Key unless $Cond %>
|
||||||
|
Cached only if $Cond == false
|
||||||
|
<% end_cached %>
|
||||||
|
```
|
||||||
|
|
||||||
|
```ss
|
||||||
|
<% cached $Key if not $Cond %>
|
||||||
|
Cached only if $Cond == false
|
||||||
|
<% end_cached %>
|
||||||
|
```
|
||||||
|
|
||||||
|
```ss
|
||||||
|
<% cached 'contentblock', $LastEdited, $CurrentMember.ID if $CurrentMember && not $CurrentMember.isAdmin %>
|
||||||
|
<!--
|
||||||
|
Hash of this content block is also included
|
||||||
|
into the final Cache Key value along with
|
||||||
|
SilverStripe\View\SSViewer::$global_key
|
||||||
|
-->
|
||||||
|
<% uncached %>
|
||||||
|
This text is always dynamic (never cached)
|
||||||
|
<% end_uncached %>
|
||||||
|
<!--
|
||||||
|
This bit is cached again
|
||||||
|
-->
|
||||||
|
<% end_cached %>
|
||||||
|
```
|
@ -6,124 +6,107 @@ icon: tachometer-alt
|
|||||||
|
|
||||||
# Partial Caching
|
# Partial Caching
|
||||||
|
|
||||||
Partial caching is a feature that allows the caching of just a portion of a page.
|
[Partial template caching](../templates/partial_template_caching) is a feature that allows caching of rendered portions a template.
|
||||||
|
|
||||||
|
|
||||||
|
## Cache block conditionals
|
||||||
|
|
||||||
|
Use conditions whenever possible. The cache tag supports defining conditions via either `if` or `unless` keyword.
|
||||||
|
Those are optional, however is highly recommended.
|
||||||
|
|
||||||
|
[warning]
|
||||||
|
Avoid performing heavy computations in conditionals, as they are evaluated for every template rendering.
|
||||||
|
[/warning]
|
||||||
|
|
||||||
|
If you cache without conditions:
|
||||||
|
- your cache backend will always be queried for the cache block (on every template render)
|
||||||
|
- your cache may be cluttered with heaps of redundant and useless data (especially the default filesystem backend)
|
||||||
|
|
||||||
|
As an example, if you use `$DataObject->ID` as a key for the block, consider adding a condition that ID is greater than zero:
|
||||||
|
|
||||||
```ss
|
```ss
|
||||||
<% cached 'CacheKey' %>
|
<% cached $MenuItem.ID if $MenuItem.ID > 0 %>
|
||||||
$DataTable
|
|
||||||
...
|
|
||||||
<% end_cached %>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Each cache block has a cache key. A cache key is an unlimited number of comma separated variables and quoted strings.
|
To cache the contents of a page for all anonymous users, but dynamically calculate the contents for logged in members,
|
||||||
Every time the cache key returns a different result, the contents of the block are recalculated. If the cache key is
|
use something like:
|
||||||
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
|
```ss
|
||||||
<% cached 'database', $LastEdited %>
|
<% cached unless $CurrentUser %>
|
||||||
<!-- 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 [Versioned](api:SilverStripe\Versioned\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;
|
|
||||||
|
|
||||||
**app/_config/app.yml**
|
|
||||||
|
|
||||||
|
|
||||||
```yml
|
|
||||||
SilverStripe\View\SSViewer:
|
|
||||||
global_key: '$CurrentReadingMode, $Locale'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Aggregates
|
## Aggregates
|
||||||
|
|
||||||
Often you want to invalidate a cache when any object in a set of objects change, or when the objects in a relationship
|
Sometimes you may want to invalidate cache when any object in a set changes, or when objects in a relationship
|
||||||
change. To do this, SilverStripe introduces the concept of Aggregates. These calculate and return SQL aggregates
|
change. To do this, you may use [DataList](api:SilverStripe\ORM\DataList) aggregate methods (which we call Aggregates).
|
||||||
on sets of [DataObject](api:SilverStripe\ORM\DataObject)s - the most useful for us being the `max` aggregate.
|
These perform SQL aggregate queries on sets of [DataObject](api:SilverStripe\ORM\DataObject)s.
|
||||||
|
|
||||||
For example, if we have a menu, we want that menu to update whenever _any_ page is edited, but would like to cache it
|
Here are some useful methods of the [DataList](api:SilverStripe\ORM\DataList) class:
|
||||||
|
- `int count()` : Return the number of items in this DataList
|
||||||
|
- `mixed max(string $fieldName)` : Return the maximum value of the given field in this DataList
|
||||||
|
- `mixed min(string $fieldName)` : Return the minimum value of the given field in this DataList
|
||||||
|
- `mixed avg(string $fieldName)` : Return the average value of the given field in this DataList
|
||||||
|
- `mixed sum(string $fieldName)` : Return the sum of the values of the given field in this DataList
|
||||||
|
|
||||||
|
To construct a `DataList` over a `DataObject`, we have a global template variable called `$List`.
|
||||||
|
|
||||||
|
For example, if we have a menu, we may 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:
|
otherwise. By using aggregates, we do that like this:
|
||||||
|
|
||||||
|
```ss
|
||||||
|
<% cached
|
||||||
|
'navigation',
|
||||||
|
$List('SilverStripe\CMS\Model\SiteTree').max('LastEdited'),
|
||||||
|
$List('SilverStripe\CMS\Model\SiteTree').count()
|
||||||
|
%>
|
||||||
|
```
|
||||||
|
|
||||||
|
The cache for this will update whenever a page is added, removed or edited.
|
||||||
|
|
||||||
|
[note]
|
||||||
|
The use of the fully qualified classname is necessary.
|
||||||
|
[/note]
|
||||||
|
|
||||||
|
[note]
|
||||||
|
The use of both `.max('LastEdited')` and `.count()` makes sure we check for any object
|
||||||
|
edited or deleted since the cache was last built.
|
||||||
|
[/note]
|
||||||
|
|
||||||
|
[warning]
|
||||||
|
Be careful using aggregates. Remember that the database is usually one of the performance bottlenecks.
|
||||||
|
Keep in mind that every key of every cached block is recalculated for every template render, regardless of caching
|
||||||
|
result. Aggregating SQL queries are usually produce more load on the database than simple select queries,
|
||||||
|
especially if you query records by Primary Key or join tables using database indices properly.
|
||||||
|
|
||||||
|
Sometimes it may be cheaper to not cache altogether, rather than cache a block using a bunch of heavy aggregating SQL
|
||||||
|
queries.
|
||||||
|
|
||||||
|
Let us consider two versions:
|
||||||
|
|
||||||
```ss
|
```ss
|
||||||
<% cached 'navigation', $List('SilverStripe\CMS\Model\SiteTree').max('LastEdited'), $List('SilverStripe\CMS\Model\SiteTree').count() %>
|
# Version 1 (bad)
|
||||||
|
|
||||||
|
<% cached
|
||||||
|
$List('SilverStripe\CMS\Model\SiteTree').max('LastEdited'),
|
||||||
|
$List('SilverStripe\CMS\Model\SiteTree').count()
|
||||||
|
%>
|
||||||
|
Parent title is: $Me.Parent.Title
|
||||||
|
<% end_cached %>
|
||||||
```
|
```
|
||||||
|
|
||||||
The cache for this will update whenever a page is added, removed or edited. (Note: The use of the fully qualified classname is necessary).
|
|
||||||
|
|
||||||
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
|
```ss
|
||||||
<% cached 'categorylist', $List('Category').max('LastEdited'), $List('Category').count() %>
|
# Version 2 (better performance than Version 1)
|
||||||
|
|
||||||
|
Parent title is: $Me.Parent.Title
|
||||||
```
|
```
|
||||||
|
|
||||||
[notice]
|
`Version 1` always generates two heavy aggregating SQL queries for the database on every
|
||||||
Note the use of both `.max('LastEdited')` and `.count()` - this takes care of both the case where an object has been
|
template render.
|
||||||
edited since the cache was last built, and also when an object has been deleted since the cache was last built.
|
`Version 2` always generates a single and more performant SQL query fetching the record by its Primary Key.
|
||||||
[/notice]
|
|
||||||
|
|
||||||
We can also calculate aggregates on relationships. The logic for that can get a bit complex, so we can extract that on
|
[/warning]
|
||||||
to the controller so it's not cluttering up our template.
|
|
||||||
|
|
||||||
## Cache key calculated in controller
|
|
||||||
|
|
||||||
If your caching logic is complex or re-usable, you can define a method on your controller to generate a cache key
|
|
||||||
fragment.
|
|
||||||
|
|
||||||
For example, a block that shows a collection of rotating slides needs to update whenever the relationship
|
|
||||||
`Page::$many_many = ['Slides' => 'Slide']` changes. In `PageController`:
|
|
||||||
|
|
||||||
|
|
||||||
```php
|
|
||||||
public function SliderCacheKey()
|
|
||||||
{
|
|
||||||
$fragments = [
|
|
||||||
'Page-Slides',
|
|
||||||
$this->ID,
|
|
||||||
// identify which objects are in the list and their sort order
|
|
||||||
implode('-', $this->Slides()->Column('ID')),
|
|
||||||
$this->Slides()->max('LastEdited')
|
|
||||||
];
|
|
||||||
return implode('-_-', $fragments);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Then reference that function in the cache key:
|
|
||||||
|
|
||||||
|
|
||||||
```ss
|
|
||||||
<% cached $SliderCacheKey %>
|
|
||||||
```
|
|
||||||
|
|
||||||
The example above would work for both a has_many and many_many relationship.
|
|
||||||
|
|
||||||
## 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
|
## Purposely stale data
|
||||||
|
|
||||||
@ -157,126 +140,36 @@ and then use it in the cache key
|
|||||||
<% cached 'blogstatistics', $Blog.ID, $BlogStatisticsCounter %>
|
<% 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
|
## Cache backend
|
||||||
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
|
The template engine uses [Injector](../extending/injector) service `Psr\SimpleCache\CacheInterface.cacheblock` as
|
||||||
heavy load:
|
caching backend. The default definition of that service is very conservative and relies on the server filesystem.
|
||||||
|
This is the most common denominator for most of the applications out there. However,
|
||||||
|
this is not the most robust neither performant cache implementation. If you have a better solution
|
||||||
|
available on your platform, you should consider tuning that setting for your application.
|
||||||
|
All you need to do to swap the cache backend for partial template cache blocks is to redefine this service for the Injector.
|
||||||
|
|
||||||
|
Here's an example of how it could be done:
|
||||||
|
|
||||||
```ss
|
```yml
|
||||||
<% cached 'blogstatistics', $Blog.ID if $HighLoad %>
|
# app/_config/cache.yml
|
||||||
|
|
||||||
|
---
|
||||||
|
Name: app-cache
|
||||||
|
After:
|
||||||
|
- 'corecache'
|
||||||
|
---
|
||||||
|
SilverStripe\Core\Injector\Injector:
|
||||||
|
Psr\SimpleCache\CacheInterface.cacheblock: '%$App\Cache\Service.memcached'
|
||||||
```
|
```
|
||||||
|
|
||||||
By adding a `HighLoad` function to your `PageController`, you could enable or disable caching dynamically.
|
[note]
|
||||||
|
For the above example to work it is necessary to have the Injector service `App\Cache\Service.memcached` defined somewhere in the configs.
|
||||||
To cache the contents of a page for all anonymous users, but dynamically calculate the contents for logged in members,
|
[/note]
|
||||||
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 %>
|
|
||||||
```
|
|
||||||
|
|
||||||
[warning]
|
[warning]
|
||||||
Currently a nested cache block can not be contained within an if or loop block. The template engine will throw an error
|
The default filesystem cache backend does not support auto cleanup of the residual files with expired cache records.
|
||||||
letting you know if you've done this. You can often get around this using aggregates or by un-nesting the block.
|
If your project relies on Template Caching heavily (e.g. thousands of cache records daily), you may want to keep en eye on the
|
||||||
|
filesystem storage. Sooner or later its capacity may be exhausted.
|
||||||
[/warning]
|
[/warning]
|
||||||
|
|
||||||
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 %>
|
|
||||||
```
|
|
||||||
|
|
||||||
Or:
|
|
||||||
|
|
||||||
```ss
|
|
||||||
<% cached $LastEdited %>
|
|
||||||
(other code)
|
|
||||||
<% end_cached %>
|
|
||||||
|
|
||||||
<% loop $Children %>
|
|
||||||
<% cached $LastEdited %>
|
|
||||||
$Name
|
|
||||||
<% end_cached %>
|
|
||||||
<% end_loop %>
|
|
||||||
```
|
|
||||||
|
Loading…
Reference in New Issue
Block a user