ExtendableBehavior
ExtendableBehavior allows you to subclass models and use them to categorize different types of model data in the same table.
The Problem
Ever made an application that had different types of users, but referred to the same users table in your database? Maybe you had a table called "users" and a model called "User"…Well, if you're like me, you may have had similar cases where you wanted to have separate classes for your same users table, that would distinguish the type of user for you.
I had recently been writing an application that had a User, Developer and Designer model classes. I didn't want to create a separate table to manage the types of users, nor did I want to always refer to a particular field in my User model when I wanted to find/save a Developer or Designer record.
Instead, I wanted to be able to subclass the User model and simply use that class to select the correct record set. e.g., if I did `$this->Developer->findAll()', then I'd want to select all users who are of type Developer. How did I do this without creating a separate table? Well, I used the ExtendableBehavior!
The Solution
The ExtendableBehavior class allows you to transparently find and save different types of data to the same table without using the same model class.The default behavior for this is that it uses the `type' field in your database table to identify the model alias (usually the model class name itself). If you have a Developer and Designer class (which extends the User class) and did a findAll() on both model classes, you would be selecting from the same "users" table by finding only the results with the correct `type' field value… for the Developer model, the `type' field would be set to "Developer", and "Designer" for the Designer model.
The Code
All this talk is probably already boring you, and at this point you're probably grinning at your monitor screen wondering when I'm going to shut up. Well then, here's the code :)2008-06-12 UPDATE (works with RC1 now):
Model Class:
Download code
<?php
/**
* The ExtendableBehavior allows a model to record its "type" into the database
* table. If you created a model and then created another model which inherits
* from the model you previously created, then the extending class' model name
* will be recorded in the database table's designated `type' column.
*
* You must apply this behavior through the $actsAs property on your model, and
* any children models must define a $useTable property that corresponds to their
* parent, otherwise finding/saving will not work.
*
* The only possible configuration option this behavior accepts is the
* `typeField' key, which should be named something to your liking. By default
* this will be set to 'type'.
*
* @author Matthew Harris <shugotenshi@gmail.com>
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
class ExtendableBehavior extends ModelBehavior {
/**
* The most root class that has extended AppModel. This class acts as
* the parent and doesn't have its `type' recorded in the table.
*
* @var string
*/
var $rootClass;
/**
* The name of the type column, default 'type'
*
* @var string
*/
var $typeField;
/**
* Set up the behavior.
* Finds root class and determines type field settings.
*/
function setup(&$model, $config = array()) {
$this->settings = am(array('typeField' => 'type'), $config);
$this->rootClass = $this->__getRootClassName($model);
$this->typeField = $this->settings['typeField'];
}
/**
* Filter query conditions with the correct `type' field condition.
*/
function beforeFind(&$model, $queryData)
{
if (array_key_exists($this->typeField, $model->_schema) && $model->alias != $this->rootClass) {
if (!isset($queryData['conditions'])) {
$queryData['conditions'] = array();
}
if (is_string($queryData['conditions'])) {
if (strlen(trim($queryData['conditions']))) {
$queryData['conditions'] = "({$queryData['conditions']}) AND ";
}
$queryData['conditions'] .= $this->alias.'.'.$this->type.' = '.$this->value($model->alias);
}
elseif (is_array($queryData['conditions'])) {
if (!isset($queryData['conditions'][$model->alias.'.'.$this->typeField])) {
$queryData['conditions'][$model->alias.'.'.$this->typeField] = array();
}
$queryData['conditions'][$model->alias.'.'.$this->typeField] = $model->alias;
}
}
return $queryData;
}
/**
* Set the `type' field before saving the record.
*/
function beforeSave(&$model)
{
if (array_key_exists($this->typeField, $model->_schema) && $model->alias != $this->rootClass) {
if (!isset($model->data[$model->alias])) {
$model->data[$model->alias] = array();
}
$model->data[$model->alias][$this->typeField] = $model->alias;
}
return true;
}
/**
* Get the uppermost parent class name that an extending model inherits from.
* This does not include AppModel, that's where the search stops.
*
* @return string Parent class name
*/
function __getRootClassName(&$model)
{
$parent = $current = get_class($model);
while (strtolower($current) != 'appmodel') {
$parent = $current;
$current = get_parent_class($current);
}
return $parent;
}
}
?>
I hope you find this behavior useful, and as usual, leave feedback in the comments area please! ;)
Comments
Comment
1 Nice idea
Comment
2 Downloading
svn checkout https://anon.svn.ariworks.co.kr/projects/extendable/trunk extendable
svn update extendable # only when you want to update your copy
Comment
3 Snapshots
http://ariworks.co.kr/~kuja/files/snapshots/cake/
There you can download all my latest code.
Question
4 Combining Extendable behavior
But now i have new problem. I want to combining Extendable with Containable. And, this is my models :
Model Class:
<?phpclass User extends AppModel {
public $name = 'User';
public $actsAs = array('Extendable','Containable');
}
?>
Model Class:
<?phpclass Artist extends User {
public $name = 'Artist';
public $actsAs = array('Extendable','Containable');
public $useTable = 'users';
}
?>
Model Class:
<?phpclass Listener extends User {
public $name = 'Listener';
public $actsAs = array('Extendable','Containable');
public $useTable = 'users';
}
?>
and this is my controller :
Controller Class:
<?php
class UsersController extends AppController {
var $name = 'Users';
var $uses = array('User','Listener','Artist','ArtistsFan','Message','Listenerprofile','Testimonial','Artprofile');
var $helpers = array('Html', 'Form','Ajax','Time');
public function index() {
$this->Listener->contain('User.username = "Freeds"');
$this->set('allartists',$this->Listener->find('all'));
}
}
?>
and, i get this error :
Undefined property: Listener::$User [CORE\cake\libs\model\behaviors\containable.php, line 291]
Trying to get property of non-object [CORE\cake\libs\model\behaviors\containable.php, line 291]
Model "Listener" is not associated with model "User" [CORE\cake\libs\model\behaviors\containable.php, line 317]
Please help me
Thank you :)
Comment
5 Using with Groups
Thanks again,
Lane