![]() |
|
sfCacheTaggingPlugin - 1.0.1Cache 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")
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
cascade: [delete]
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] }
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.
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>