Checkbox Block to Represent Many-to-Many Relationship

It is very common to have a many-to-many relationship among data entities where the only significant fact is the existence of the relationship. For example, in this developer blog, each article can belong to several categories. So, how can I implement a block of checkboxes to represent that relationship?

Form Element

In the application module where articles are edited, there must be an IWCheckboxBlockElement form element that is part of the formElements() method that points to the other "one" table in the many-to-many relationship.

Partial Listing: rsrc/editor/ArticleEditor.php

function modelObjectClass() { return 'Article'; }

function formElements()
{
    $title = new IWFormElement('Title:', new IWTextElement(50, 100));
    $category_id = new IWFormElement(
        'Categories:', new IWCheckboxBlockElement(new Category, 5)
    );
    $publish_date = new IWFormElement(
        'Publish Date:', new IWDateElement(2010, date('Y') + 1)
    );
    $ready_to_publish = new IWFormElement(
        '', new IWCheckboxElement, 'Ready to Publish'
    );
    $article = new IWFormElement('Article:', new IWTextAreaElement(50, 120));

    return array(
        'title'            => $title,
        'category_id'      => $category_id,
        'publish_date'     => $publish_date,
        'ready_to_publish' => $ready_to_publish,
        'article'          => $article
    );
}

The key used for the form elements array must be the primary key field of the other "one" table (in this case Category). The numeric value after "new Category" is the number of columns that should be used to display the categories.

Model Object methods

In the referenced model object class (in this case, Category), there are two required methods:

Partial Listing: rsrc/model/application/Category.php

class Category
{
   function nameProperty() { return 'name'; }

   function articleCheckboxBlock(Article $article)
   {
      $m2m = new ORMM2MObject;
      $m2m->initialize($article, $this, new ArticleCategory);

      return $m2m;
   }

   ...
}

The nameProperty() method returns the column name in the category table that should be displayed, and is also used to sort the checkbox labels. The articleCheckboxBlock() method establishes the special ORMM2MObject that manages the data relationship.

The query used to retrieve the categories can also be modified using the optional restrictCheckboxBlockForArticle() method.

class Category
{
   ...

   function restrictCheckboxBlockForBook($select)
   {
       $select->addCriteria(new ORMCriterion('flag_archive', false))
              ->addCriteria(new ORMCriterion('flag_publish', true));
       $select->setOrderBy('lower(name)');
   }
}

If the setOrderBy() method is called, that sorting supersedes the nameProperty() column.

With those three pieces in place, the work is done: five columns of checkboxes representing the categories for an article are completely managed.