sfTagtoolsPlugin - 0.3.3

Typeahead and admin page for tags

You are currently browsing
the website for symfony 1

Visit the Symfony2 website

« Back to the Plugins Home


Forgot your password?
Create an account



advanced search
Information Readme Dependencies Releases Changelog Contribute
Show source



You MUST have the sfPropelActAsTaggablePlugin as well. This plugin provides additional services to applications that utilize that plugin. There is no point in using this plugin without it.

To use the admin page and typeahead features, you must also have Prototype (prototype.js) loading in your pages.


This plugin provides many additional services above and beyond those standard in the excellent sfPropelActAsTaggablePlugin. This plugin is less mature than sfPropelActAsTaggablePlugin but provides quite a bit of user interface "glue" to flesh it out.


sfTagtoolsPlugin was created by the team at [http://www.punkave.com/ P'unk Avenue]. Please address questions, concerns and patches to [mailto:tom@punkave.com Tom Boutell].


  • Typeahead for tags, with autocomplete on tab or mouseclick. Similar to Flickr's solution, which is familiar to many users. See: lib/helper/tagaheadHelper.php

  • The concept of featured tags. These are just a handy way to mark certain tags as "featured" for your own UI's purposes. If a tag has a corresponding !FeatureTag object, you know it's a featured tag. We provide this so that you can mark tags as "featured" in...

  • A nicely AJAXified admin page for renaming, merging, deleting, prioritizing and featuring tags. Note that you don't have to use !FeaturedTag to get this admin page, that's an optional feature.

  • The concept of prioritized tags, implemented by the !PriorityTag class. A tag with a higher priority appears first in results returned by sfTagtoolsToolkit::getBeginningWith(). Again, this is an optional feature, you can use the typeahead and the admin page without !PriorityTag. "But what is sfTagtoolsToolkit::getBeginningWith()?" you ask. This leads us to:

  • A way to retrieve tags ranked in order by popularity, optionally requiring that they begin with a particular string. See sfTagtoolsToolkit::getBeginningWith(). This method began life as part of the typeahead mechanism but with a blank initial string it's quite useful for creating tag browsers, too.

  • A way to fetch objects of a particular taggable model class which are related to a particular set of tags, ranking them in descending order by the number of related tags, with high priority tags receiving an additional boost.


sfPropelActAsTaggablePlugin works its magic by storing the name of the tagged object class as a string in the database (taggable_model). This is great in a lot of ways but it causes problems when we want to rank things meaningfully so that stale taggings pointing to dead objects are not considered. This can be worked around to an extent using SQL's provision for an "IN" clause (exposed by the retrieveByPKs method of peer classes in Propel), and Xavier Lacot's code makes heroic use of this feature. However this tends to fall apart if you want to, let's say, quickly count the tagged objects of a particular model that still exist without the overhead of hydrating them all (for instance, to build a tag admin page). It also falls apart if you have hundreds of thousands of records, creating IN clauses that are too large for a MySQL packet (and inefficient in any case). It's true that an id is a whole lot smaller than an actual object, but when you have enough of them it's still bad.

So if you want to count the tagged objects of a particular model that still exist and meet a particular criterion of "live-ness" or "published-ness" without hydrating them all... welcome to custom SQL query land! Happily I have coded those queries for you.

So although it can be used without, sfTagtoolsPlugin works best when you describe your taggable classes to it via app.yml. This allows sfTagtoolsPlugin to work around this design limitation of sfPropelActAsTaggablePlugin by joining with the tables that contain the taggable classes when making custom queries to efficiently fetch just the information needed.

"Isn't that still slow?" There is a small performance hit relative to selecting against the tags and taggings alone, but it's not significant in "big-O notation" terms (at least, not if MySQL does its job and optimizes things properly). Still, if you have a zillion taggable classes, you might want to go with a single taggable class instead and reference it from tables for each of the "real" objects. Sigh... wouldn't it be nice if Propel truly understood multiple inheritance?


Just install the plugin. Okay, it's not quite that simple.

You'll need to enable the tagtools module in your application. In my apps/frontend/config/settings.yml, I have:

# Activated modules from plugins or from the symfony core
enabled_modules:        [default, sfGuardAuth, sfGuardUser, sfGuardGroup, sfGuardPermission, tagtools]

Your list of modules will of course vary, just be sure to add tagtools to the settings you have for enabled_modules.

You will also need to enable prototype in your pages. In the default section of apps/frontend/config/view.yml, you can use:

javascripts:    [%SF_PROTOTYPE_WEB_DIR%/js/prototype]

To use our typeahead support, you will also need tagahead.js:

javascripts:    [%SF_PROTOTYPE_WEB_DIR%/js/prototype, %SF_WEB_ROOT%/tagtools/js/tagahead.js]

Your site may use other JavaScript libraries which can be loaded in a comma-separated list.

Finally, to make the typeahead ("tagahead") support work properly, you must have at least a few bare-bones CSS settings to correctly display the suggested tags. See sfTagtoolsPlugin/web/css/suggested.css for details.

That's it for essential configuration. But to take full advantage, you should also describe your taggable classes in app.yml. And you may wish to enable the optional !FeaturedTag and !PriorityTag support although this plugin works quite well without those. Here's an example:

    featured: true
    priority: true
      - name: Release
        label: Releases
        cssclass: releases
      - name: PressKit
        label: Presskits
        cssclass: presskits
      - name: Media
        label: Media
        cssclass: media
    criteria_live: "TAGGABLE.live = true"
    criteria_public: "TAGGABLE.published = true"

"What are criteria_live and criteria_public?" These criteria are applied when deciding if the object a tagging refers to is good enough to qualify as a "live" object. If your application, like many of ours, involves objects that users abandon halfway through the creation process, then you know exactly what I'm talking about. criteria_public is similar, but is applied only if the user is not logged in (isAuthenticated fails on the current sfUser). The word TAGGABLE is automatically replaced with the name of the table for the appropriate object class when the query is run.

"What are label and cssclass?" These are used in the admin page, so they are optional if you don't use the admin page. You do need the name field, which must be the actual PHP class name (not table name) for that type of object.

"Can I use typeahead and the admin page without any of these settings?" Yes, but sfTagtools won't be able to distinguish stale taggings pointing to long-deleted objects from taggings that point to live objects. So I strongly recommend that you at least define your taggables:

      - name: Release
        label: Releases
        cssclass: releases
      - name: PressKit
        label: Presskits
        cssclass: presskits
      - name: Media
        label: Media
        cssclass: media

Otherwise you'll see typeahead suggestions for tags that are no longer actually in use anywhere, perhaps for a good reason. And you'll also see an inflated count of total occurrences for each tag in the tagadmin module, because taggings are never deleted and sfTagtools doesn't have enough information to join with the taggables and count only the taggings that still matter.

Don't forget to "symfony cc"!

Implementing Typeahead

To implement the typeahead feature, use code like the following in your templates:

<?php use_helper("tagahead") ?>
<div class="form-row">
<label for="tags">Tags</label>
  <?php echo tagahead_input_tag("tags", $tags) ?>
<div class="form-row">
<label for="tags-suggestions">Suggested Tags</label>
<div id="tags-suggestions"></div>
<?php echo tagahead_observer('tags') ?>

The tagahead_input_tag helper outputs an input tag with the specified name (which will also be its ID). The tagahead_observer helper outputs JavaScript code to dynamically update the tag suggestions based on what the user has typed so far.

Implementing Tag Admin

The tag admin page was activated when you enabled the tags module. It was also secured with a rule in sfTagtoolsPlugin/modules/tagtools/config/security.yml. However this rule simply prevents users from accessing the admin page if they are not authenticated. If your site has multiple levels of user privileges, you will want to adjust these settings so that only admins can delete, rename and merge tags!

To access the tag admin page, just visit:


Again, you must be authenticated first if you are using our provided security.yml rule.

version 0.33 - 2008-08-05

getBeginningWith: when joining with multiple taggables we do separate queries consecutively and merge the results in order to avoid serious performance issues when the database fails to spot potential optimizations and actually examines a * b * c * d * e rows, where a * b * c * d * e tends to be a rather large number.

An empty taggables array is now treated the same as no taggables setting at all.

Further markdown corrections for the wiki.

version 0.32 - 2008-08-05

Another try at markdown format for documentation compatibility with the wiki.

version 0.31 - 2008-08-05

README code examples now in markdown format for compatibility with the new Symfony plugins wiki.

version 0.3 - 2008-07-31

package.xml added. It's a proper package now. Recreated plugin in the new Symfony web site plugin CMS.

version 0.2 - 2008-07-30

The model classes were missing! To make matters worse, config/schema.yml didn't have a package attribute, so they were rebuilt in the main app folder. And in any case my custom methods were missing. Fixed. Thanks to Xavier Lacot for pointing out the problem.

version 0.1 - 2008-07-28

Initial public release. Thanks to Xavier Lacot for wiki:sfPropelActAsTaggableBehaviorPlugin, which makes this plugin possible.