Version 14, last updated by pagchen at November 07, 2011 18:06 UTC

This wiki has been updated for version 0.7-alpha4

1. Getting data

A model aggregates data from one or more data sources. This encapsulates the data, so the rest of your code doesn't need to know what the data sources are, how to extract data from them, and how to format it in a usable way. Whenever your code uses data, it should get that data from a model's getList() or getItem() method (depending on whether you need a single entity or a set of entities).

1.1. Getting a single item

However, you'll want to manipulate exactly which data you'll be getting. In the case of a single item, you'll usually provide a unique key to the model.

$model = KService::get('com://admin/harbour.model.boats')
                   ->set('id', 5);
$boat = $model->getItem(); // returns boat with id=5

Note 1: id in the above example is automatically mapped to the identity_column of the table (your_table_id) by KDatabaseTableAbstract's constructor. Using ...->set('your_table_id',5) is not going to work.

Note 2: In most cases you will use the primary key of the table but you can use any unique key defined in your table schema. For example :

$model = KService::get('com://admin/harbour.model.boats')
               ->set('alias', 'the-titanic');
$boat = $model->getItem(); // returns boat with alias=the-titanic

With this table :

CREATE TABLE IF NOT EXISTS `#__harbour_boats` (
  `harbour_boat_id` SERIAL,
  `name` VARCHAR(255) NOT NULL DEFAULT '',
  `alias` VARCHAR(255) NOT NULL DEFAULT '',
  UNIQUE KEY `alias` (`alias`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

1.2 Getting a list of items

Typically, for lists you'll want to set some states to filter the result set.

$model = KService::get('com://admin/harbour.model.boats')
                 ->set('limit', 20) // limit the list to 20 items
                 ->set('offset', 40) // start the list at the 40th item
                 ->set('sort', 'captain') // sort by the 'captain' field
                 ->set('direction', 'asc') // order ascending
$boats = $model->getList(); // returns boats 41 to 60, ordered by captain in ascending order

The states in this example are all available by default in KModelTable (which is a specialized version of KModelAbstract, and is very useful to get data from a database table).

1.3 Getting items from non-database sources

There may be occasions when it is necessary or preferable to get items from non-database sources (off-site data sources, xml files etc.). To do this, it is necessary to override the getItems() method of KModelTable. To maintain compatibility with the rest of the framework, this should return a KDatabaseRowset object.

public function getList()
{
    if(!$this->_list)
    {
        //set table = false to avoid unnecessary requests to the database for a table that doesn't exist :)
        $rowset = KService::get('com://admin/foo.database.rowset.bars',
                        array('table' => false));
         ...
        $rowset->insert( $row );
        ...
        $this->_list = $rowset;
     }
     return $this->_list;
}

2. Fluent Interface

Setting states is even easier and more readable using the fluent interface. The above example can be rewritten as follows:

$model = KService::get('com://admin/harbour.model.boats')
    ->limit(20)->offset(40)->sort('captain')->direction('asc');
    // the order of these methods doesn't matter
$boats = $model->getList();

The examples below use this style of notation.

3. Adding more states

In some cases you might need more states to manipulate filter your data. For example, we only want to display boats that are sailing under a specific flag, and only boats that have been published.

First, the model needs to be aware of those states. We do that by inserting the state in the model's state object:

$model->getState()
    ->insert('flag', 'string')
    ->insert('published', 'int', 1);
  • The first argument is the name of the state.
  • The second argument is a filter for the type of value expected. This can in fact be any available KFilterobject, such as koowa:filter.email, or custom filters such as com://admin/harbour.filter.country.
  • The final argument is an optional default value.

Usually, we perform this insert in the model's constructor. Example :

class ComHarbourModelBoats extends ComDefaultModelDefault
{
   public function __construct(KConfig $config)
   {
       parent::__construct($config);
       $model->getState()
            ->insert('flag', 'string')
            ->insert('published', 'int', 1);
    }
}

4. Using the states

So now the model knows about the two new states. The next step is to actually use them to manipulate the data set.

class ComHarbourModelBoats extends ComDefaultModelDefault
{
    // constructor: see above

    protected function _buildQueryWhere(KDatabaseQuery $query)
    {
        $state=$this->_state;
        if($state->flag) {
            $query->where('flag', '=', $state->flag);
        }

        $query->where('published', '=', $state->published);
    }
}

Tip: If you use the IN comparison operator, you can pass an array and get a list of values:

  $query->where('flag', 'IN', $state->flag);

so calling with a url like ...&flag[]=Netherlands&flag[]=United Kingdom etc or $boats = $model->flag(array('Panama','Belize'))->getList(); would enable you to get multiple flags in 1 query.

5. Client code

Finally, the client code sets the state in the model:

$model = KService::get('com://admin/harbour.model.boats')
                  ->limit(20)->published(1);
$boats = $model->getList(); // returns the first 20 published boats
$boats = $model->flag('Panama')->getList();
// returns the first 20 published boats sailing under Panamanian flag

When you are using KControllerBread, you don't even need to set these states yourself. KControllerBread handles this out of the box for you. Have a look at KControllerBread::loadState functions to understand how it all works.

6. Unique and non-unique columns

A key concept when working with Nooku Framework is to make full use of the database schema. This means if a column contains unique data your database schema should include a unique index for that column.

6.1. Unique columns

e.g. Using a UUID

You need to add a UNIQUE KEY uuid (uuid) to your database table to tell the database your UUID is unqiue. You don't need to add a state to your model. The uuid is unique which means that the model will add it automatically as a unique state. Finaly, you can use the identifiable database behavior to make it easy to generate uuid's.

Check com_harbour boat and port models and tables on how to do this. Support for UUID's is implemented there.

6.2. Non-unique columns

e.g. user_id

The user_id will not be a unique column so this will need to be added to the state and the where query. The way to handle this is :

public function __construct(KConfig $config) {

 parent::__construct($config);
 $this->_state->insert('user_id', int);

}

protected function _buildQueryWhere(KDatabaseQuery $query) {

 $state = $this->_state;
 if($state->user_id)
 {
     $query->where('tbl.user_id', '=', $state->user_id);
 }
 parent::_buildQueryWhere($query);

}

7. Getting state out of the model

You can easily get state out of the model to use elsewhere:

$flag = $model->get('flag');

or:

$state   = $model->getState();
$flag    = $state->flag;
$enabled = $state->enabled;

8. Persistent states

Nooku Framework implements a transparent and auto-persistent model state system. This system is implemented through KControllerBread::saveState function.

KControllerBread expects the state information to be part of the GET request and not the POST request, or said otherwise the information needs to be part of the URI itself.

To taken advantage of the auto-persistent model state KView's need to use GET requests when implementing filters (like a search box).

The state is pushed in the session in the KControllerBread::saveState and loadState functions. They load and save the model state in the session.

8.1 KModelState

The models implement the state as a KModelState object. This is a special model that is designed to keep model state. KModelState is implemented in KModelAbstract as a sub-model.

You can get the state model by calling KModelAbstract::getState() and then you can use the functions as defined in KModelState.

8.2 Default behavior

On the backend interface of any Nooku powered extension, the model state persistency is enabled by default as long as the default dispatcher (ComDefaultDispatcher) is being used. A closer look at the ComDefaultDispatcher backend class located at:

administrator/components/com_default/dispatcher.php

reveals the following piece of code in the _initialize method of the dispatcher:

$config->append(array(
    'request_persistent' => true
));

By having the request_persistent parameter set to true, ComDefaultDispatcher forces the abstract dispatcher to instantiate the requested controller with model state persistency enabled.

On the other hand, model state persistency is disabled by default in the frontend interface while using the default dispatcher. For enabling this feature, we would just need to extend the ComDefaultDispatcher frontend class by creating our own frontend dispatcher.

Just create a file called dispatcher.php in the root frontend directory of your extension, e.g. components/com_foo for a component named Foo, with the following code inside:

class ComFooDispatcher extends ComDefaultDispatcher {

    protected function _initialize(KConfig $config) {
        $config->append(array('request_persistent' => true));
        parent::_initialize($config);
    }

}

As you might already noticed, we are just setting the request_persistent parameter to true, just as it's done in the backend part of the extension.

8.3 Disabling model state persistency for a given controller

While model state persistency is extremely useful in the great majority of cases, sometimes it might be necessary to disable this feature on one or more controllers.

This can be easily done in the controller _initialize method by just adding the following line on it:

$config->persistent = false;

By doing this, we are forcing (not appending) the persistent parameter value to the configuration object.