Building your own Models
This section gives a detailed description on how to implement your own models.
Models can also be generated quickly by the Command Line Interface (CLI). Refer to the make:model command for more information.
Note: Please review the basic Model Usage before attempting to write your own. Creating a model does not automatically create the corresponding database tables if they do not exist; you should use DB Forge to create your add-on’s tables.
Registering Models
All models must be registered in your addon.setup.php file. They should be in an array called models
, where the key is their name and the value is the class of the model relative to your namespace. By convention this should be inside a Model
directory:
'models' => array(
'Author' => 'Model\Author',
'MyModel' => 'Model\MyModel'
)
Note: Your add-on prefix will be prepended automatically.
Model Skeleton
All models must declare a primary key, a table name, and a set of properties. The basic skeleton looks as follows:
namespace My\Namespace\Model\MyModel\Model;
use ExpressionEngine\Service\Model\Model;
class MyModel extends Model {
protected static $_primary_key = 'id';
protected static $_table_name = 'my_awesome_table';
protected $id;
protected $name;
protected $email;
// ... more properties
}
Properties
The column names of the table should be reflected in the models properties. These must be declared as protected
. If you require class properties for internal use, you should prefix them with an underscore:
// treated as table columns
protected $title;
protected $description;
// ignored - prefixed with an underscore
protected $_cache;
Metadata
All models have static metadata attached to them. Metadata must be declared as protected static
and must begin with an underscore to avoid any potential confusion with column names. It can be retrieved with the getMetadata()
method:
protected static $_events = array();
// accessing it:
MyModel::getMetaData('events');
// or on an instance
$my_model_instance->getMetaData('events');
Internal Events
As covered in the usage section all models can emit events that can be subscribed to:
$my_model->on('boom', function($model)
{
echo 'boom event happened on '.$model->getName();
});
$my_model->emit('boom');
Unfortunately this local event behavior is not very convenient for internal event handling. To solve this problem, all model objects are subscribers to their own events. This means you can automatically bind to events and receive the callback on a regular method.
To do so, first create a public method with the format on<EventName>
, and then listen to the event by creating an $_events
metadata array that lists the events you want to subscribe to:
protected static $_events = array('beforeSave');
public function onBeforeSave()
{
echo 'about to save!';
}
There are some events that are emitted when handling things in bulk so that you may do things in efficient batches. These event handlers need to be implemented as static methods as your model class will not represent any one record:
public static function onAfterBulkDelete($delete_ids)
{
// Handle deleted items, do extra clean-up, etc.
}
Note: Event names typically start with a lowercase letter, but the method name will have them as uppercase due to the on
prefix.
Default Events
Event Name | When | Parameters |
---|---|---|
afterLoad | After a model is fetched | None |
beforeInsert | Before saving a new model | None |
afterInsert | After saving a new model | None |
beforeUpdate | Before saving an existing model | Array of changed values |
afterUpdate | After saving an existing model | Array of changed values |
beforeSave | Before saving a model | None |
afterSave | After saving a model | None |
beforeValidate | Before validating | None |
afterValidate | After validating | None |
beforeDelete | Before deleting | None |
afterDelete | After deleting | None |
beforeBulkDelete | Before bulk deleting | Array of IDs being deleted |
afterBulkDelete | After deleting | Array of IDs deleted |
Relationships
In-Depth Documentation: Relating Models)
Models can be related to other models using just a little bit of metadata. They are defined on a $_relationships
metadata array. The array keys should be the relationship names, and their values will be a description of the relationship:
protected static $_relationships = array(
'Author' => array(
'model' => 'Member',
'type' => 'BelongsTo'
)
);
$my_model->Author; // fetches member that created this entry
The type name is required, all other fields have predictable defaults. The model
key will default to the relationship name. You may need to modify the key names that are used to match the relationship. To do so, specify a from_key
and a to_key
, where the from_key
is a property name on this model, and the to_key
is a property name on the related model:
protected static $_relationships = array(
'Author' => array(
'model' => 'Member',
'type' => 'BelongsTo',
'from_key' => 'author_id',
'to_key' => 'member_id'
)
);
The following types are available:
Type | Default from_key | Default to_key |
---|---|---|
HasOne | This primary key name | This primary key |
HasMany | This primary key name | This primary key |
BelongsTo | Related primary key name | Related primary key name |
HasAndBelongsToMany | This primary key name | Related primary key name |
HasAndBelongsToMany
The keys for the HasAndBelongsToMany
relationship work slightly differently from the rest. This is because this relationship type requires a pivot table to work. The from_key
and to_key
still specify a property on the models, but there is an additional pivot
key for the pivot table name:
protected static $_relationships = array(
'Editors' => array(
'model' => 'Member',
'type' => 'HasAndBelongsToMany',
'pivot' => 'my_model_editors',
'from_key' => 'editor_id',
'to_key' => 'member_id'
)
);
If your pivot table key names do not match the primary key names, you can specify them as well, by turning the pivot item into an array:
'pivot' => array(
'table' => 'my_model_editors',
'left' => 'editor_id',
'right' => 'member_id'
)
The left
column will be matched to your from_key
property and the right
column will be matched to your to_key
property.
Validation
Validation rules are added using the same format as the Validation Service. They should be added to a metadata item called $_validation_rules
:
protected static $_validation_rules = array(
'name' => 'required',
'email' => 'required|email'
);
You can also create your own local validation rules. These one-off rules can be added directly to the model class by creating a public method whose name starts with validate
. The method will be treated as a custom validation rule. On validation it will receive the property name, current value, rule parameters, and the rule object. The method should return TRUE
on success, and an error string or language key on failure:
protected static $_validation_rules = array(
'even_number' => 'validateMultipleOf[2]'
'decade' => 'validateMultipleOf[10]'
);
public function validateMultipleOf($name, $value, $params, $object)
{
if ($value % $params[0] != 0)
{
return 'This field must be a multiple of '.$params[0];
}
return TRUE;
}
Getters and Setters
By default setting and getting of properties behaves the way it does for any object. However, you can create your own modifying getters and setters by creating methods in the format of get__<property>
or set__<property>
:
protected $first_name;
protected $last_name;
protected function get__name()
{
return $this->first_name.' '.$this->last_name;
}
protected function set__name($value)
{
list($first, $last) = explode(' ', $value);
$this->setRawProperty('first_name', $first);
$this->setRawProperty('last_name', $last);
}
$my_model->name = 'Bob Bobson';
$my_model->first_name; // Bob
Note: These methods break the camelCase naming convention in order to match the snake_case property names. It also serves as a clear indicator that these methods should not be called directly, even internally. When setting properties manually you should take care to use setRawProperty, which will correctly track changes to the property.
Typed Columns
Model properties can have basic type constraints set on them. These constraints allow for simple get/set typecasting of common values. They are defined in a metadata array called $_typed_columns
:
protected static $_typed_columns = array(
'model_id' => 'int',
'created_at' => 'timestamp'
);
$my_model->model_id = '5'; // set to int 5
$my_model->model_id; // always returns an integer
$my_model->created_at; // returns a DateTime object
$my_model->created_at = new DateTime('2015-01-30'); // sets an int timestamp
$my_model->created_at = 1421558529; // also ok
The available options include:
Name | Setting | Getting |
---|---|---|
bool | Cast to boolean | Cast to boolean |
int | Cast to integer | Cast to integer |
float | Cast to float | Cast to float |
double | Cast to float | Cast to float |
string | Cast to string | Cast to string |
boolString | Cast to y/n | Cast to boolean |
yesNo | Cast to y/n | Cast to boolean |
timestamp | Cast to timestamp | Cast to DateTime |
Composite Types
Sometimes a database column may contain serialized data. Instead of treating this data merely as a string or using getters and setters, you can automatically turn it into an array or object. These are also defined in the $_typed_columns
metadata array:
protected static $_typed_columns = array(
'settings' => 'json'
);
$my_model->settings = array('name' => 'Bob'); // stores: {"name": "Bob"}
$my_model->settings['name']; // 'Bob'
The following composite types are included:
Name | Serialization |
---|---|
base64 | base64_encode($data) |
json | json_encode($data) |
serialized | serialize($data) |
base64Serialized | base64_encode(serialize($data)) |
Model Methods
class ExpressionEngine\Service\Model\Model
getId()
Get the primary key value
Parameter | Type | Description |
---|---|---|
Returns | Int (or null if not yet saved) |
The primary key value |
getName()
Get the model alias that was registered with ExpressionEngine.
Parameter | Type | Description |
---|---|---|
Returns | String |
The model alias |
getPrimaryKey()
Get the primary key name
Parameter | Type | Description |
---|---|---|
Returns | String |
The primary key name |
getProperty($name)
Get a model property, calling any getters that were defined by the model.
Parameter | Type | Description |
---|---|---|
$name | String |
The name of the property |
Returns | Mixed |
The value of the property |
setProperty($name, $value)
Set a model property and track its dirty state. Calls any setters that were defined by the model.
Parameter | Type | Description |
---|---|---|
$name | String |
The name of the property |
$value | Mixed |
The value to set the property to |
Returns | Model |
$this |
getRawProperty($name)
Get a model property, without calling any getters that were defined by the model.
Parameter | Type | Description |
---|---|---|
$name | String |
The name of the property |
Returns | Mixed |
The value of the property |
setRawProperty($name, $value)
Set a model property and track its dirty state. Does not call any setters that were defined by the model.
Parameter | Type | Description |
---|---|---|
$name | String |
The name of the property |
$value | Mixed |
The value to set the property to |
Returns | Model |
$this |