Torkil Johnsen

My personal piece of cyberspace

Table Behaviors in Nooku Framework

Posted in Nooku on Dec 7th, 2010

One of the great timesavers in Nooku Framework are the database table behaviors. As the name suggests, you can use these to add some behaviors to your database. Here is a tutorial on how these work, plus a few practical examples.

Table of contents

  1. Dual purpose
  2. How to add a behavior
    1. By identifier
    2. By instance
  3. Where to add a behavior
    1. In the table class
    2. In the model class
  4. Nooku’s built in behaviors
  5. Creating your own behaviors
  6. Comparing to Joomla 1.6
  7. Revelation: Automated plugin events/hooks
  8. Revelation 2: Row mixins
  9. References and further reading


Dual purpose

The behaviors are something of a cross-breed between database triggers and database row mixins. Confused?

Database behaviors can first of all intercept table commands, meaning they can act on any select, update, delete and insert being run on your database, much like a database trigger, but with PHP-code, and thus a lot more flexible. For example: When selecting an article from a database table that has a column called “hits” and a behavior called “hittable”, the value of the hit column will increment automatically.

At the same time, the behaviors function as row and rowset mixins. Mixins are a way of doing multiple inheritance, something PHP does not support natively. So while your own table class may extend KDatabaseTable, it can also mix in KDatabaseBehaviorLockable, so you’ll get access to the public functions from the latter too.

The main idea is to make a flexible and reusable implementation based on composition, rather than one which relies on inheritance; Behaviors can be added to your code and triggered dynamically, so instead of having for example a “hit” method in a base model that all models inherit from, or worse, copying a “hit” method across your models, you can just mix in the behavior where it’s needed.


How to add a behavior

Behaviors are added to database table classes, either when initializing the database table class, or when constructing the related model class. When adding behaviors, you can either add by identifier or add by instance.


Adding by identifier

If you’re using a Nooku core behavior, you can use the behavior’s shortname directly, like “lockable” or “creatable”.

If you wish to use a behavior you created yourself, or reuse a behavior from another component, you can easily do so by specifying the full identifier. For instance: “admin::com.shop.database.behavior.notifiable” to reuse the notifiable behavior from com_shop.

When adding by identifier, the behavior object itself is created for you, with it’s default configuration.


Adding by instance

You can also add a ready-made object in itself if you wish, by calling a factory method to create the object first:

// using the behavior's shortname
$sluggable = KDatabaseBehavior::factory('sluggable');
 
// using an identifier string
$notifiable = KDatabaseBehavior::factory('admin::com.shop.database.behavior.notifiable');

Some behaviors also accept a configuration array, which can be specified like this:

// use id and title for item slugs, instead of just title
$sluggable = KDatabaseBehavior::factory(
    'sluggable', array('columns' => array('id', 'title'))
);

In this case, “sluggable” can also of course be replaced by a full identifier.


Where to add a behavior

To use a behavior with your table class, you can either add it during database table initialization, or you can add it during model construction.


Adding during table initialization

You can add behaviors by shortname, identifier string or instance, and you can add multiple at a time. Here, I’m adding two behaviors to my books table:

class ComLibraryDatabaseTableBooks extends KDatabaseTableAbstract 
{
    protected function _initialize(KConfig $config)
    {
        // make the library's books lockable and hittable
        $config->behaviors = array('lockable', 'hittable');
        parent::_initialize($config);
    }
}


Adding during model construction

Here I’m adding the behavior in the models constructor, and also doing it by adding a pre-configured object instance.

class ComLibraryModelBooks extends KModelDefault
{
    public function __construct(KConfig $config) 
    {
        // use id and title for item slugs
        $sluggable = KDatabaseBehavior::factory(
            'sluggable', array('columns' => array('id', 'title'))
        );
 
        $config->append(array(
            'table_behaviors' => array($sluggable)
        ));
 
        parent::__construct($config);
    }
}

Notice how I am not calling KFactory to create the behavior objects. Instead I am calling KDatabaseBehavior::factory which wraps around KFactory::tmp. This enables me to instantiate the core behavior classes with just their shortnames, instead of their full identifier string. It will also verify that the object created is in fact extending KDatabaseBehaviorInterface.

You could also have written this to get a sluggable behavior object created for you:

$sluggable = KFactory::tmp('lib.koowa.database.behavior.sluggable');

So where should you add the behavior? If you have either a model or a table class, then add it to the class that already exists. If you have both, add it to your table class. If you have none, you’ll need a table class to add the behavior too.


Nooku’s built-in behaviors

Nooku’s core database table behaviors can be found in libraries/koowa/database/behavior of the Nooku Framework repository. At the time of this writing, the core behaviors are:

creatable
Use this to record who creates items and when. Triggered before an insert, and will auto-fill any created_by and created_on columns in your table. You won’t even need to have these fields in your form for this to be stored in the database.
hittable
Requires a hits column that will count the number of hits your items get. Just call the hit method whenever you want to increment the hit counter.
identifiable
Requires a column named uuid. This behavior will auto-create a Universally Unique IDentifier when a row is created.
lockable
For people who know Joomla, you will know this behavior by the name check in/check out. This behavior will make a row editable or uneditable, to prevent simultaneous editing by two people. This behavior requires two columns in your table: locked_by and locked_on.
modifiable
This behavior helps you manage the columns modified_on and modified_by, which will keep track of who edited an item last, and when they edited it.
orderable
Requires an ordering column in your table. Mixes in an order method which you can use to reorder elements. It also keeps your your items in order when updating items in, or deleting items from, your database table.
sluggable
Will automatically create a slug for a newly created item. It will create the slug from the column or columns you specify, default is the title column. It also uses the separator that you specify, and defaults to a dash. You can also configure this to be updatable or not, and set a length limit for the slugs created.


Creating your own behaviors

Behaviors should be placed in (/administrator)/components/com_yourcomponent/databases/behaviors/, for instance with the file name behaviorname.php. The class should then be named ComYourcomponentBehaviorBehaviorname and it should extend KDatabaseBehaviorAbstract.

Here is a very simple example example in the shape of a “notifiable” behavior, which sends a notification when a new row is inserted in a database table. I am writing this for an imaginary component called “com_shop”, where I need notifications when new orders come in.

< ?php
class ComShopDatabaseBehaviorNotifiable extends KDatabaseBehaviorAbstract
{
    protected function _afterTableInsert(KCommandContext $context)
    {
        $row = $context->data; // the new row object
        return mail(
            'myname@mydomain.com', 
            'New order placed by '.$row->customer_name, 
            'Here are the order details (more data here)'
        );
    }
}

I have here used _afterTableInsert, but remember that you can use any trigger after and before both insert, select, update and delete. For instance _afterTableDelete, if you need to do some cleanup after a delete for instance.

I then need to use the notifiable behavior in my orders table, where the new orders are registered:

< ?php
class ComShopDatabaseTableOrders extends KDatabaseTableAbstract
{
    protected function _initialize(KConfig $config)
    {
        $config->behaviors = array('notifiable');
        parent::_initialize($config);
    }
}

This is of course very simple and you’d of course never use the mail() function for something like this, but it’s just an example.

So why did I pick “notifiable” as an example? Well, notifications is something that could be useful in many cases. For instance: New client signups, new orders, new comment submissions, altered order statuses (use _afterTableUpdate for that) and so on. The trick is to make the code generic enough so it can be reused in multiple components.

If a behavior is deemed to be general and useful enough, it might be accepted into the core too, so perhaps you’re feeling up for the task and want to become the next Nooku Contributor rockstar?


Comparing to Joomla 1.6

Let’s look at how the Nooku core database behaviors are solved in Joomla 1.6. I am comparing this to 1.6 beta 15, as that is the most recent release at the time of this writing.

creatable
You will have to submit created and created_by variables with your form.
hittable
You’ll need to write this code yourself. For com_content there is a hardcoded function called hit in ContentModelArticle, but it will only work for com_content.
identifiable
Non-existant in Joomla 1.6 as far as I know.
lockable
A quick search in the codebase reveals at least eight different definitions of a function called “checkin”, where many seem to do the exact same thing; checkin/unlock a row item. So it appears as if there is a lot of code duplication here.
modifiable
You will have to submit modified and modified_by variables with your form.
orderable
If you extend JTableNested, you’ll have access to the functions orderUp, orderDown, move, moveByReference and saveorder, as far as I can tell.
sluggable
Slug is called alias in Joomla. The generation of an alias usually happens in a table class, but it’s all hardcoded in all core components, which means a lot of code duplication and copy pasting when writing components.


Revelation: Automated plugin events/hooks

This also reveals a small piece of another puzzle, the table command chain. We have now seen how we can perform actions on certain events like the afterTableInsert example above. In Joomla (both 1.5 and 1.6) you have to manually trigger events like “onContentBeforeSave”; in Nooku, these exist by default, in all components.

One might argue that there are more plugin triggers in Joomla, like in the example class plgContentExample you can find onContentChangeState for instance. In Nooku, all actions also translates to one of the core BREAD actions; Browse, Read, Edit, Add, Delete. The difference between Browse and Read is that Browse equals “read multiple”, while Read equals “read one”. Changing the state of an item usually means changing the state column of a database row, which equals an edit operation. So if you want to create something equivalent to onContentChangeState, you’d just use for instance afterTableUpdate and do $row->getModified() to see if “state” has changed.

You can find the same command chain design pattern in the controllers, where events both before and after the different BREAD actions are triggered, like controllerBeforeRead. Nicholas Dionysopoulos used this concept in a great tutorial where he demonstrated server side validation by command chain usage in Nooku Framework.


Revelation 2: Row mixins

Let’s not forget the mixin part of the equation here, the lockable behavior is a good example of that: It exposes the lock, unlock, locked and lockMessage functions to the row object.

These are in turn used in the core controllers, like KControllerView, to control access to the row object. Easy reuse of code in other words.


References and further reading


Creative Commons License
This work by Torkil Johnsen is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.