Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

The sfCacheTaggingPlugin is a Symfony plugin to store caches associated with unique tags to keep cache content up-to-date based by incrementing tags version

Fetching latest commit…

Octocat-spinner-32-eaf2f5

Cannot retrieve the latest commit at this time

Octocat-spinner-32 config
Octocat-spinner-32 lib
Octocat-spinner-32 test
Octocat-spinner-32 .gitignore
Octocat-spinner-32 LICENSE
Octocat-spinner-32 README
Octocat-spinner-32 package.xml
README
# sfCacheTaggingPlugin #

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.

## Description ##

Tagging a cache is a concept that was invented in the same time by many developers
([Andrey Smirnoff](http://www.smira.ru), [Dmitryj Koteroff](http://dklab.ru/)
and, perhaps, by somebody else)

This software was developed inspired by Andrey Smirnoff`s theoretical work
["Cache tagging with Memcached (in Russian)"](http://www.smira.ru/tag/memcached/).
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).

## Repository ##

 * plugin repository @ github [http://github.com/fruit/sfCacheTaggingPlugin](http://github.com/fruit/sfCacheTaggingPlugin "Repository")
 * plugin tickets @ github [http://github.com/fruit/sfCacheTaggingPlugin/issues](http://github.com/fruit/sfCacheTaggingPlugin/issues "Issues")

## Installation ##

Install the plugin

    $ ./symfony plugin:install sfCacheTaggingPlugin

## Versions ##

### Since 1.3.0

  - Resolved problem with caching empty collections with no tags
  - extra methods and validations are added to ``Doctrine_Collection_Cachetaggable`` class

### Since 1.2.0

  Now all dql queries (update/delete) also updates version column and thier tags

## Setup ##

1.  Check ``sfCacheTaggingPlugin`` plugin is enabled (``/config/ProjectConfiguration.class.php``)

        <?php

        class ProjectConfiguration extends sfProjectConfiguration
        {
          public function setup()
          {
            # … other plugins
            $this->enablePlugins('sfCacheTaggingPlugin');
          }
        }

1.  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.yml``
      for 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.yml`` content (you can copy&paste this code
      into your brand new created file) or merge this config with each application\`s
      ``factories.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 ``sfCache``
      class. Also, it should support the caching of objects and/or arrays.

    > **Bonus**: In additional to this plugin comes ``sfFileTaggingCache``
      and ``sfSQLiteTaggingCache`` which are ready to use as backend class.

      This classes already have serialization/unserialization support.

1.  Edit Symfony\`s predefined application\`s level ``factories.yml`` files

    > If you have edited each application\`s level ``factories.yml`` file 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.yml`` file).

1.  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

1.  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)

1.  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
            log_format_extended: 0      # (0 - print 1 char)
                                        # (1 - print 1 char + tag key)
                                        #
                                        # Char explanation:
                                        #   "g": cache not found
                                        #   "G": cache is found
                                        #   "l": could not lock cache
                                        #   "L": cache is locked
                                        #   "s": could set new values to the cache
                                        #   "S": new values is setted to the cache
                                        #   "u": could not unlock cache
                                        #   "U": cache is unlocked
                                        #   "t": cache tag was expired
                                        #   "T": cache tag is up-to-date
                                        #
                                        #   Chars in lower case indicate negative operation
                                        #   Chars in upper case indicate positive operation

## Using ##

*   ### Native use:

        # Somewhere in the frontend, you need to print out latest posts
        $posts = Doctrine::getTable('BlogPost')
          ->createQuery()
          ->orderBy('id DESC')
          ->limit(3)
          ->execute();

        /* @var $tagger sfViewCacheTagManager */
        $tagger = $this->getContext()->getViewCacheManager();

        # 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
        }


*   ### non-Doctrine use:

    ##### indexSuccess.php

        <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>

    ##### code.php

          # when you want to update Daylight content
          $this->getContext()->getViewCacheManager()->getTagger()->setTag('moon', time(), 86400);

</fieldset>

*   ### Using with partials:

        <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>

*   ### Using with components (simple):

    ##### indexSuccess.php

        <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>


    ##### components.class.php

        <?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;
          }
        }

*   ### Using with components (complex - Combining posts with comments)

    ##### components.class.php

        <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>

    ##### components.class.php

        <?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;
          }
        }

## Limitations / Peculiarities ##

  * 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.

## Unit test ##

 * 861 of 861 test are successfully completed

Every combination is tested (data backend / locker backend) of listed below:

  * sfMemcacheCache
  * sfAPCCache
  * sfSQLiteTaggingCache - file (extended from sfSQLiteCache)
  * sfSQLiteTaggingCache - memory (extended from sfSQLiteCache)
  * sfFileTaggingCache (extended from sfFilecache)

Partialy tested listed below backends. If anyone could help me to run functional tests
for this backends, I will be thankful to you:

  * sfXCacheCache
  * sfEAcceleratorCache


## Contacts ##

* @: Ilya Sabelnikov `` <fruit dot dev at gmail dot com> ``
* IRC:
  * server: irc.freenode.net
  * channel: #symfony
  * username: fruit
* skype: ilya_roll
Something went wrong with that request. Please try again.