Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
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
PHP

Fetching latest commit…

Cannot retrieve the latest commit at this time

Failed to load latest commit information.
config
lib
test
.gitignore
LICENSE
README
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.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

## 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();

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

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