Contact

FUxCon 2013


Tagging

CakePHP

The CakePHP implementation uses the Tags Plugin to implement tagging on projects. This plugin implements a new behavior on the Project model. This is the top of our model in file app/Model/Project.php

<?php
/**
 * Project Model
 *
 * @property User $User
 */
class Project extends AppModel {
  public $actsAs = array('Tags.Taggable', 'Containable');
  // more domain model goodness
}

The plugin adds two new tables tags and tagging and thus implements the m:n relationship between projects and tags. The plugin relies on a field tags in the model which it automatically links to. A user can enter tags as a comma separated list in the edit form which is converted to related tags behind the scenes by the plugin behavior:

CakePHP tag display

… is entered as

CakePHP tag entry

Django

There are several alternative extensions to implement tagging in Django. Unfortunately, the most popular one is also no longer maintained. We chose django-tagging and installed it with

source env/bin/activate
pip install django-tagging

As usual, the extension is registered as an installed app in projects/settings.py:

INSTALLED_APPS = (
    'taggit',
)

As explained in the documentation of the extension, it relies on a field in the model that is a TaggableManager. With that in place, taggit automatically converts a comma separated list of tags into the proper entries in the two tables taggit_tag and taggig_taggeditem that the extension adds to the database. With this

Django tag display

… is entered as

Django tag entry

Drupal

In Drupal, the basic infrastructure for tagging goes under the name of taxonomies which has been a core module basically from day one of Drupal’s inception as a powerful community system.

Since the introduction of entities in Drupal 7, similar to nodes, users, and comments, taxonomy terms have been promoted to the status of entity and can this be extended with fields. They are very well documented in the Drupal Online Handbook.

In our humble project, we don’t use much of the power of taxonomies in Drupal. Basically´, we use the pre-configured Tags vocabulary and configure it to be applicable to our project content type by adding an appropriate field to it:

Drupal project fields

With this

Drupal tag display

… is entered as

Drupal tag entry

Drupal even makes this field into an ajax autocomplete widget if configured this way in the content type.

Symfony

We use fpn/tag-bundle and fpn/doctrine-extensions-taggable to implement tagging in our Symfony implementation. The documentation page goes to some length at explaining what you need in terms of configuration and extending your model classes to enjoy tagging.

As we want to use YAML configuration for our Doctrine entities and want to actually enter tags in the edit forms of our projects, we had to do some additional leg work to get the bundle to work.

Our project model needs a setter for tags:

<?php
namespace FUxCon2013\ProjectsBundle\Entity;

use DoctrineExtensions\Taggable\Taggable;
use Doctrine\Common\Collections\ArrayCollection;
// more use clauses

/**
 * Project
 */
class Project implements Taggable
{
  public function getTags()
  {
      $this->tags = $this->tags ?: new ArrayCollection();
      return $this->tags;
  }

  public function getTaggableType()
  { return 'project'; }

  public function getTaggableId()
  { return $this->getId(); }

  // We added this to be able to save tags from a form  
  public function setTags($tags)
  {
      $this->tags = is_array($tags) ? new ArrayCollection($tags) : $tags;
  }
}

Our Doctrine configuration files Tag.orm.yml and Tagging.orm.yml in src/FUxCon2013/ProjectsBundle/Resources/config/doctrine look like this:

FUxCon2013\ProjectsBundle\Entity\Tag:
    type: entity
    table: tag
    id:
            id:
                type: integer
                generator:
                    strategy: AUTO
    oneToMany:
        tagging:
            targetEntity: Tagging
            mappedBy: tag
            fetch: EAGER
FUxCon2013\ProjectsBundle\Entity\Tagging:
    type: entity
    table: tagging
    id:
            id:
                type: integer
                generator:
                    strategy: AUTO
    manyToOne:
        tag:
            targetEntity: Tag

The bundle documentation only explains how to read tags. We want to edit them as well. This requires several extensions. The createAction() and in src/FUxCon2013/ProjectsBundle/Controller/ProjectController.php need to actively save the tags:

<?php
/**
 * Creates a new Project entity.
 *
 * @Route("/", name="project_create")
 * @Method("POST")
 * @Template("FUxCon2013ProjectsBundle:Project:new.html.twig")
 */
public function createAction(Request $request)
{
    $project = new Project();
    $form = $this->createForm(new ProjectType(), $project);
    $form->bind($request);

    if ($form->isValid()) {
        $em = $this->getDoctrine()->getManager();

        $em->persist($project);
        $em->flush();

        $this->get('fpn_tag.tag_manager')->saveTagging($project);
        // more work to save pictures
    }
    // error handling done here
}

The same statement needs to be added to editAction() and updateAction().

To actually be able to edit tags in a form, we need to add them to our edit form. We created a new widget type that converts between comma-separated list and list of tags. First the form in src/FUxCon2013/ProjectsBundle/Form/ProjectType.php:

<?php
namespace FUxCon2013\ProjectsBundle\Form;

class ProjectType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('tags', 'tags_entry', array('attr' => array(
                'class' => 'tags-field input-block-level'
            )))
        ;
    }
}

The new widget type tags_entry is created in its own class in src/FUxCon2013/ProjectsBundle/Form/TagsType.php:

<?php
namespace FUxCon2013\ProjectsBundle\Form;

class TagsType extends AbstractType
{
    public function __construct(TagManager $tagManager)
    { $this->tagManager = $tagManager; }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $transformer = new TagsTransformer($this->tagManager);
        $builder->addModelTransformer($transformer);
    }

    public function getParent()
    { return 'text'; }

    public function getName()
    { return 'tags_entry'; }
}

This class needs to be registered in app/config/config.yml to be recognized by Symfony and to have it pass a tag manager as parameter:

services:
    fuxcon2013.form.tags_entry:
        class: FUxCon2013\ProjectsBundle\Form\TagsType
        arguments: [ "@fpn_tag.tag_manager" ]
        tags:
            - { name: form.type, alias: tags_entry }

Actually, the class TagsType doesn’t do do any of the actual work. The conversion between text entry field and lists of tags in turn is handled by yet another class, a TagsTransformer in file src/FUxCon2013/ProjectsBundle/Form/TagsTransformer.php:

<?php
/**
 * @see http://symfony.com/doc/current/cookbook/form/data_transformers.html
 */
namespace FUxCon2013\ProjectsBundle\Form;

use Symfony\Component\Form\DataTransformerInterface;

class TagsTransformer implements DataTransformerInterface
{
    private $tagManager;

    public function __construct($tagManager)
    { $this->tagManager = $tagManager; }

    public function transform($tags)
    { return join(', ', $tags->toArray()); }

    public function reverseTransform($tags)
    {
        return $this->tagManager->loadOrCreateTags(
            $this->tagManager->splitTagNames($tags)
        );
    }
}

The TagsTransformer finally uses some API functions of the tag manager to load or create tags entered by a user.



comments powered by Disqus