![]() |
|
sfCacheTaggingPlugin - 1.1.0Cache tagging plugin compatible with any cache backends |
|
The sfCacheTaggingPlugin is a Symfony plugin, that helps to store cache with
associated tags and to keep cache content up-to-date based by incrementing tag
version when cache objects are edited/removed or new objects are ready to be a
part of cache content.
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)
This software was developed inspired by Andrey Smirnoff`s theoretical work "Cache tagging with Memcached (in Russian)". Some ideas are implemented in the real world (i.e. tag versions based on datetime and microtime, cache hit/set logging, cache locking) and part of them are not (atomic counter).
Install the plugin
$ ./symfony plugin:install sfCacheTaggingPlugin
Check sfCacheTaggingPlugin plugin is enabled (/config/ProjectConfiguration.class.php)
<?php
class ProjectConfiguration extends sfProjectConfiguration
{
public function setup()
{
# … other plugins
$this->enablePlugins('sfCacheTaggingPlugin');
}
}
Create a new file /config/factories.yml or edit each application`s level
/apps/%app%/config/factories.yml file
Cache tagging works for you each time you save/update/delete Doctrine record or fetch them from DB. So you should enable caching (sf_cache: true) in all applications you work with. I recommend you to create default
factories.ymlfor all applications you have by creating a file/config/factories.yml(bellow that will be this file content). Symfony will check this file and load it as a default factories.yml configuration to all applications you have in the project.This is
/config/factories.ymlcontent (you can copy&paste this code into your brand new created file) or merge this config with each application`sfactories.yml(applications, where you need the data to be fetched/written from/to cache)Example: file
/config/factories.yml
all:
view_cache:
class: sfTagCache
param:
logging: true # logging is enabled ("false" to disable)
cache:
class: sfMemcacheCache # Content will be stored in Memcache
# Here you can switch to any other backend
# (see Restrictions block for more info)
param:
persistent: true
storeCacheInfo: false
host: localhost
port: 11211
timeout: 5
lifetime: 86400
locker:
class: sfAPCCache # Locks will be stored in APC
# Here you can switch to any other backend sf*Cache
# (see Restrictions block for more info)
param: {}
view_cache_manager:
class: sfViewCacheTagManager # Extended sfViewCacheManager class
param:
cache_key_use_vary_headers: true
cache_key_use_host_name: true
Easter eggs: If you remove "
all_view_cache_param_locker" section, locker will be the same as section "all_view_cache_param_cache".Restrictions: Backend`s class should be inheritable from
sfCacheclass. Also, it should support the caching of objects and/or arrays.Bonus: In additional to this plugin comes
sfFileTaggingCacheandsfSQLiteTaggingCachewhich are ready to use as backend class.
This classes already have serialization/unserialization support.
Edit Symfony`s predefined application`s level factories.yml files
If you have edited each application`s level
factories.ymlfile in 2nd step - go to 4th step.In each application you want to use cache tagging please remove "
all_view_cache_manager" section (you have already configured it in global/config/factories.ymlfile).
Add "Cachetaggable" behavior to each model, which you want to be a part of cache content.
Exmaple: file
/config/doctrine/schema.yml
BlogPost:
tableName: blog_post
actAs:
Cachetaggable: ~
#Cachetaggable:
# uniqueColumn: id # you can customize unique column name (default is "id")
# versionColumn: object_version # you can customize column name to store versions (default is "object_version")
# uniqueKeyFormat: '%d' # you can customize key format (default is "%d")
#
# # if you have more then 1 unique column, you could pass all of them
# # as array (tag name will be based on them)
#
# uniqueColumn: [id, is_enabled]
# uniqueKeyFormat: '%d-%02b' # the order of unique columns
# # matches the "uniqueKeyFormat" template variables order
columns:
id:
type: integer
primary: true
autoincrement: true
unsigned: true
title: string(255)
relations:
BlogPostComment:
class: BlogPostComment
type: many
local: id
foreign: blog_post_id
BlogPostComment:
tableName: blog_post_comment
actAs:
Cachetaggable: ~
columns:
id:
type: integer
primary: true
autoincrement: true
unsigned: true
blog_post_id:
type: integer
unsigned: true
notnull: false
author: string(20)
message: string(255)
indexes:
blog_post_id: { fields: [blog_post_id] }
relations:
BlogPost:
onUpdate: CASCADE
onDelete: CASCADE
Enable cache in settings.yml and add additionals helpers to standart_helpers
To setup cache, often, used a separate environment named "cache", but in the same way you can do it in any other environments which you have.
prod:
.settings:
cache: true
cache:
.settings:
cache: true
all:
.settings:
cache: false
Add helpers to the frontend application
all:
.settings:
standard_helpers:
- Partial # symfony build in helper (mandatory)
- CacheTag # sfCacheTaggingPlugin helper (mandatory)
- PartialTag # sfCacheTaggingPlugin helper (mandatory)
Customize sfCacheTaggingPlugin in the /config/app.yml
all:
sfcachetaggingplugin:
template_lock: "lock_%s" # name for locks
template_tag: "tag_%s" # name for tags
microtime_precision: 5 # version precision (0, or positive number)
# (0 - without micro time, version will be 10 digits length)
# (5 - with micro time part, version will be 15 digits length)
lock_lifetime: 2 # seconds to keep lock, if failed to unlock after locking it
# Somewhere in the frontend, you need to print out latest posts
$posts = Doctrine::getTable('BlogPost')
->createQuery()
->orderBy('id DESC')
->limit(3)
->execute();
# write data to the cache ($posts is instance of the Doctrine_Collection_Cachetaggable)
$tagger->set('my_posts', $posts, 86400, $posts->getTags());
# fetch latest post to edit it
$post = posts->getFirst();
# prints something like "126070596212512"
print $post->getObjectVersion();
$post->setTitle('How to use sfCacheTaggingPlugin');
# save and update/upgrade version of the tag
$post->save();
# prints something like "126072290862231" (new version of the tag)
print $post->getObjectVersion();
# will return null
# $post object was updated, so, all $posts in cache "my_posts†is invalidated automatically)
if ($data = $tagger->get('my_posts'))
{
# this block will not be executed
}
# save new data to the cache
$tagger->set('my_posts', $posts, null, $posts->getTags());
# will return data (objects are fresh)
if ($data = $tagger->get('my_posts'))
{
# this code block will be executed
}
$post = new BlogPost();
$post->setTitle('New post should be in inserted to the cache results');
$post->save();
# will return null, because 'my_posts' cache knows that it contains BlogPost objects
# and listens on new objects with same type that are newer
if ($data = $tagger->get('my_posts'))
{
# this block will not be executed
}
$posts = Doctrine::getTable('BlogPost')
->createQuery()
->orderBy('id DESC')
->limit(3)
->execute();
$tagger->set('my_posts', $posts, null, $posts->getTags());
# will return data
if ($data = $tagger->get('my_posts'))
{
# this block will be executed
}
<fieldset>
<legend>Daylight</legend>
<?php if (! cache_tag('daylight_content')) { ?>
<h1>Text to cache No-<?php rand(1, 1000000) ?></h1>
Text text text text text text text text text text text text text.
<?php cache_tag_save(array('sun' => time(), 'moon' => time())); ?>
<?php } ?>
</fieldset>
# when you want to update Daylight content
$this->getContext()->getViewCacheManager()->getTagger()->setTag('moon', time(), 86400);
<fieldset>
<legend>Partial</legend>
<?php if (! cache_tag('latest-blog-posts-index-on-page')) { ?>
<?php foreach ($posts as $post) { ?>
<?php include_partial('posts/one_post', array('post' => $post) ?>
<?php } ?>
<?php cache_tag_save($posts->getTags()); ?>
<?php } ?>
</fieldset>
<fieldset>
<legend>Component</legend>
<?php if (! cache_tag('latest-blog-posts-index-on-page')) { ?>
<?php include_component_tag('posts', 'listOfPosts') ?>
<?php cache_tag_save(); ?>
<?php } ?>
</fieldset>
<?php
class postsComponents extends sfComponents
{
public function executeListOfPosts($request)
{
$posts = Doctrine::getTable('BlogPost')
->createQuery('bp')
->select('bp.*')
->orderBy('bp.id DESC')
->limit(3)
->execute();
$this->getContext()->getViewCacheManager()->setTags($posts->getTags());
$this->posts = $posts;
}
}
<fieldset>
<legend>Component (posts and comments)</legend>
<?php if (! cache_tag('posts_and_comments')) { ?>
<?php include_component_tag('post', 'listOfPostsAndComments') ?>
<?php cache_tag_save(); ?>
<?php } ?>
</fieldset>
<?php
class postsComponents extends sfComponents
{
public function executeListOfPostsAndComments($request)
{
$posts = Doctrine::getTable('BlogPost')
->createQuery('bp')
->addSelect('bp.*, bpc.*')
->innerJoin('bp.BlogPostComments bpc')
->orderBy('bp.id DESC')
->limit(3)
->execute();
foreach ($posts as $post)
{
# 1st variant (shorter)
# our cache should be updated if one of comment will be edited/deleted
# therefore, we are collecting comment's tags
$posts->addTags($post->getBlogPostComment()->getTags());
# or shorter
# $posts->addTags($post->getBlogPostComment());
}
$this->getContext()->getViewCacheManager()->setTags($posts->getTags());
$this->posts = $posts;
}
}
Do not use Doctrine::getTable('BlogPost')->delete()->where('id > ?', 10)->execute();
Object deletion should be performed using Doctrine_Record::delete() method.
This is necessary to remove object tags when object is fizicaly removed.
Otherwise your cache will be not expired.
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.
Every combination is tested (data backend / locker backend) of listed below:
Partialy tested listed below backends. If anyone could help me to run functional tests for this backends, I will be thankful to you:
<fruit dot dev at gmail dot com>