Tagging a cache is a concept that was invented in the same time by many developers
(Andrey Smirnoff, Dmitryj Koteroff
and, perhaps, by somebody else)
Check sfCacheTaggingPlugin plugin is enabled (/config/ProjectConfiguration.class.php)
class ProjectConfiguration extends sfProjectConfiguration
{
public function setup ()
{
# … other plugins
$this->enablePlugins('sfCacheTaggingPlugin');
}
}
Create a new file /config/factories.yml (common for all applications)
or edit application-level /apps/%application_name%/config/factories.yml file.
Often, if you have back-end for managing data and front-end to print them out
you should enable cache in both of them. That is because you edit/create/delete
records in back-end, so object tags should be updated to invalidate front-end cache.
I recommend you to create default factories.yml for all applications you have by
creating a file /config/factories.yml (you could find explained
and short working examples bellow).
Symfony will check for this file and will load it as a default factories.yml
configuration for all applications you have in the project.
This is /config/factories.yml content (you can copy & paste this code
into your brand new created file) or merge this configuration with each application
factories.yml file.
Explained and commented working example of file /config/factories.yml
all:
view_cache:
class: sfTaggingCache
param:
data:
class: sfMemcacheTaggingCache # Content will be stored in Memcache
# Here you can switch to any other backend
# (see Restrictions block for more info)
param:
persistent: true
storeCacheInfo: true
host: localhost
port: 11211
lifetime: 86400 # default value is 1 day (in seconds)
tags: ~ # storage for tags (could be the same as
# the cache storage)
# if "tags" is NULL (~), it will
# be the same as cache (e.i. sfMemcacheTaggingCache)
logger:
class: sfFileCacheTagLogger # to disable logger, set class to "sfNoCacheTagLogger"
param:
file: %SF_LOG_DIR%/cache_%SF_ENVIRONMENT%.log
file_mode: 0640 # -rw-r----- (default: 0640)
dir_mode: 0750 # drwxr-x--- (default: 0750)
time_format: "%Y-%b-%d %T%z" # e.g. 2010-Sep-01 15:20:58+0300 (default: "%Y-%b-%d %T%z")
format: %char% # %char% - Operation char (see char explanation in sfCacheTagLogger::explainChar())
# %char_explanation% - Operation explanation string
# %time% - Time, when data/tag was accessed
# %key% - Cache name or tag name with its version
# %microtime% - Micro time timestamp when data/tag was accessed
# %EOL% - Whether to append \n in the end of line
# (e.g. "%microtime% %char% (%char_explanation%) %key%%EOL%")
view_cache_manager:
class: sfViewCacheTagManager # Extended sfViewCacheManager class
#param:
# … your parameters here
Short working example to start caching with tags using APC (location: /config/factories.yml)
dev:
view_cache:
param:
logger:
param:
# extended log format for dev environment
format: "%char% %microtime% %key%%EOL%"
all:
view_cache:
class: sfTaggingCache
param:
data:
class: sfAPCTaggingCache
param: []
tags: ~
logger:
class: sfCacheTagLogger
param:
file: %SF_LOG_DIR%/cache_%SF_ENVIRONMENT%.log
format: %char%
view_cache_manager:
class: sfViewCacheTagManager
param:
cache_key_use_vary_headers: true
cache_key_use_host_name: true
Restrictions: Backend's class should be inherited from sfCache
class. Then, it should be implement sfTaggingCacheInterface
(due to a Doctrine cache engine compatibility).
Also, it should support the caching of objects and/or arrays.
Therefor, plugin comes with additional extended backend classes:
- sfAPCTaggingCache
- sfEAcceleratorTaggingCache
- sfFileTaggingCache
- sfMemcacheTaggingCache
- sfSQLiteTaggingCache
- sfXCacheTaggingCache
And bonus one:
- sfSQLitePDOTaggingCache (based on stand alone sfSQLitePDOCache)
Add "Cachetaggable" behavior to each model, which you want to cache
Example of file ./config/doctrine/schema.yml
YourModel:
tableName: your_model
actAs:
## CONFIGURATION SHORT VERSION (for most users)
## Cachetaggable will detect your primary keys automatically
## and generates uniqueKeyFormat based on PK column count
## (e.g. '%s_%s' if table contains 2 primary keys)
Cachetaggable: ~
## CONFIGURATION EXPLAINED VERSION (for experts)
#Cachetaggable:
# uniqueColumn: id # you can customize unique column name (default is all table primary keys)
# versionColumn: object_version # you can customize version column name (default is "object_version")
# uniqueKeyFormat: '%s' # you can customize key format (default is "%s")
#
# # if you have more then 1 unique column, you could pass all of them
# # as array (tag name will be based on all of them)
#
# uniqueColumn: [id, is_enabled]
# uniqueKeyFormat: '%d-%02b' # the order of unique columns
# # matches the "uniqueKeyFormat" template variables order
# skipOnChange:
# - column_name_1 # to skip updating the column "object_version"
# - column_name_2 # if given column (-s) was changed.
# # (e.g. useful for sf_guard_user.last_login column)
# invalidateCollectionVersionOnUpdate: false
# # invalidates or not object collection tag when any
# # record was updated (BC with v2.*)
# # possible values: true|false (default is "false")
Enable cache in settings.yml and add additional helpers to
standard_helpers section
To setup cache, often, is used a separate environment named "cache",
but in the same way you can do it in any other environments which you already have.
prod:
.settings:
cache: true
cache:
.settings:
cache: true
all:
.settings:
cache: false
Add helpers to the each application:
all:
.settings:
standard_helpers:
# … other helpers
- Partial # build-in Symfony helper to work with partials/components
- Cache # build-in Symfony helper to work with cache
How to customize sfCacheTaggingPlugin in app.yml:
All given below values is default.
all:
sfcachetaggingplugin:
model_tag_name_separator: ":" # (constant sfCache::SEPARATOR)
microtime_precision: 5 # Version precision.
# 0: without micro time, version length 10 digits
# 5: with micro time part, version length 15 digits
# (allowed decimal numbers in range [0, 6]
#object_class_tag_name_provider: [] # Callable array
# # Example: [ClassName, StaticClassMethod]
# # useful for multi-application models
To link partial with content tags, you should pass them as an extra parameter
named sf_cache_tags. Cache name should be certainly passed as parameter sf_cache_key.
Does not depends on ./config/cache.yml files.
To cache objects/collection with its tags you have just to enable
result cache by calling Doctrine_Query::useResultCache():
class blogPostActions extends sfActions
{
public function executePosts (sfWebRequest $request)
{
# Somewhere in component/action, you need to print out latest posts
$posts = Doctrine::getTable('BlogPost')
->createQuery()
->useResultCache()
->addWhere('lang = ?', 'en_GB')
->addWhere('is_visible = ?', true)
->limit(15)
->execute();
$this->posts = $posts;
# if you run this action again - you will pleasantly surprised
# $posts now stored in cache and with object tags ;)
# (object serialization powered by Doctrine build-in mechanism)
# and expired as soon as you edit one of them
# or add new record to the table "blog_post"
# when Doctrine_Query has many joins, by default tags will be fetched
# recursively from all joined models
}
}
Appending tags to existing Doctrine tags:
class blogPostActions extends sfActions
{
public function executePosts (sfWebRequest $request)
{
# Somewhere in component/action, you need to print out latest posts
#
$posts = Doctrine::getTable('BlogPost')
->createQuery()
->useResultCache()
->addWhere('lang = ?')
->addWhere('is_visible = ?')
->limit(15)
->execute(array('en_GB', true));
# For example, you want to invalidate $posts when something was changed in table "culture":
$q = Doctrine::getTable('Culture')->createQuery();
$cultures = $q->execute();
# when execute was called without parameters "$q->execute();"
$this->addDoctrineTags($cultures, $q);
# when execute was called with parameters "$q->execute(array(true, 1, 'foo'));"
# $this->addDoctrineTags($posts, $q->getResultCacheHash($q->getParams()));
# or
# shorter
# $this->addDoctrineTags($posts, $q, $q->getParams());
# now if you update something in culture table, $posts will be expired
$this->posts = $posts;
}
}
Remember to enable Doctrine query cache in production:
For ease of configuration (enable/disable) add following lines to ./config/app.yml:
# config/app.yml
dev:
doctrine:
query_cache: ~
prod:
doctrine:
query_cache:
class: Doctrine_Cache_Apc # or another backend class Doctrine_Cache_*
param:
prefix: doctrine_dql_query_cache
And plug in query cache:
class ProjectConfiguration extends sfProjectConfiguration
{
public function configureDoctrine (Doctrine_Manager $manager)
{
$doctrineQueryCache = sfConfig::get('app_doctrine_query_cache');
if ($doctrineQueryCache)
{
list($class, $param) = array_values($doctrineQueryCache);
$manager->setAttribute(Doctrine_Core::ATTR_QUERY_CACHE, new $class($param));
}
}
}
All we want to make our application fast, so here goes some tips, how to speed up this plugin.
One of solutions to create direct proxy methods to Doctrine_Template_Cachetaggable class.
By extending sfDoctrineRecord class with build-in sfCachetaggableDoctrineRecord
we make frequently used methods as proxy (i.e. faster):
class ProjectConfiguration extends sfProjectConfiguration
{
# …
public function configureDoctrine (Doctrine_Manager $manager)
{
sfConfig::set(
'doctrine_model_builder_options',
array('baseClassName' => 'sfCachetaggableDoctrineRecord')
);
}
}
And REMEMBER TO rebuild your models after this changes:
./symfony doctrine:build-model --env=YOUR_ENV
In case, when model has translations (I18n behavior), it is enough to add
"actAs: Cachetaggable" to the model. I18n behavior should be free from Cachetaggable
behavior.
You can`t pass to skipOnChange columns from I18n table.
Doctrine $q->count() DQL can't be cached with tags
# Example (somewhere in action) can't be cached:
$q = Doctrine::getTable('Car')->createQuery();
$q->where('sipp_code = ?', 'A');
$this->count = $q->count();
To make count query cached with tags, the only one (ugly) solution is to hydrate all
and collection object tags:
# Example:
$q = Doctrine::getTable('Car')->createQuery();
$q->where('sipp_code = ?', 'A');
$collection = $q->execute();
$this->count = $collection->count();
$this->setActionTags($collection);
Be careful with caching DQL with joined I18n tables.
Due the unresolved ticket it could be impossible.