Models
There are two main classes FUEL provides for creating models. The first, MY_Model, extends the CI_Model class to provide common methods for retrieving and manipulating data from the database usually specific to a table. It was developed to augment the CodeIgniter active record class and DOES NOT change any of those methods. The second, Base_module_model, extends MY_Model and provides simple module specific methods and is the class you will most likely want to extend for your models.
Both classes allow you to create simple to complex form interfaces with the model's MY_Model::form_fields() method, which gets passed to the Form_builder object to generate the forms in the CMS.
There's a lot to cover for model's. Below is an overview of the main features but it is recommended that you look at the API documentation for both table and record model classes (explained below).
- Example
- Table, Result, Record and Field Classes
- Initializing
- Extending
- Custom Record Objects
- Custom Field Objects
- Magic Methods
- Working with Active Record
- Relationships
- Serialized Fields
- Hooks
- Formatters
Example
Below is an example of a quotes model (a simple module in the CMS), and the SQL for the table to house the quotes data.
CREATE TABLE `quotes` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `content` text COLLATE utf8_unicode_ci NOT NULL, `name` varchar(50) COLLATE utf8_unicode_ci NOT NULL, `title` varchar(100) COLLATE utf8_unicode_ci NOT NULL, `precedence` int(11) NOT NULL DEFAULT '0', `published` enum('yes','no') COLLATE utf8_unicode_ci NOT NULL DEFAULT 'yes', PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
<?php if (!defined('BASEPATH')) exit('No direct script access allowed'); require_once(FUEL_PATH.'models/Base_module_model.php'); class Quotes_model extends Base_module_model { public $required = array('content'); function __construct() { parent::__construct('quotes'); // table name } function list_items($limit = NULL, $offset = NULL, $col = 'name', $order = 'asc') { $this->db->select('id, SUBSTRING(content, 1, 200) as content, name, title, precedence, published', FALSE); $data = parent::list_items($limit, $offset, $col, $order); return $data; } function _common_query() { parent::_common_query(); // to do active and published $this->db->order_by('precedence desc'); } } class Quote_model extends Base_module_record { function get_quote_formatted() { return quote($this->_fields['content'], $this->name, $this->title); } } ?>
For a tutorial on creating simple modules, click here.
MY_Model requires the following classes and helpers:
Table, Result, Record and Field Classes
Models are actually made up of 2-3 classes:
- Table Class - in charge of retrieving, validating and saving data to the data source
- Data Set Class - the result object returned by the table class after retrieving data (normally not directly used)
- Record Class (optional) - the custom object(s) returned by a retrieving query that contains at a minimum the column attributes of the table
- Field Class (optional) - the custom object for a specific field. By default, it will simply return the value in the database
Initializing the Class
The Model class is initialized using the $this->load->model function:
$this->load->model('examples_model');
Once loaded, the Model object will be available using: $this->examples_model.
Configuring Model Information
Properties Reference
Property | Default Value | Description |
---|---|---|
public | ||
auto_validate | 1 | Use auto-validation before saving |
return_method | auto | Object, array, query, auto |
auto_validate_fields |
array('email|email_address' => 'valid_email', 'phone|phone_number' => 'valid_phone', ) |
fields to auto validate |
required |
array() |
An array of required fields. If a key => val is provided, the key is name of the field and the value is the error message to display |
default_required_message | Please fill out the required field '%1s' | The default required validator message |
auto_date_add |
array('date_added', 'entry_date') |
Field names to automatically set the date when the value is NULL |
auto_date_update |
array('last_modified', 'last_updated') |
Field names to automatically set the date on updates |
date_use_gmt | none | Determines whether to use GMT time when storing dates and times |
default_date | none | Default date value that get's passed to the model on save. Using 0000-00-00 will not work if it is a required field since it is not seen as an empty value |
auto_trim | 1 | Will trim on clean |
auto_encode_entities | 1 | Determines whether to automatically encode html entities. An array can be set instead of a boolean value for the names of the fields to perform the safe_htmlentities on |
xss_clean | none | Determines whether automatically run the xss_clean. An array can be set instead of a boolean value for the names of the fields to perform the xss_clean on |
readonly | none | Sets the model to readonly mode where you can't save or delete data |
hidden_fields |
array() |
Fields to hide when creating a form |
unique_fields |
array() |
Fields that are not IDs but are unique. Can also be an array of arrays for compound keys |
linked_fields |
array() |
Fields that are linked meaning one value helps to determine another. Key is the field, value is a function name to transform it. (e.g. array('slug' => 'title'), or array('slug' => array('name' => 'strtolower'))); |
serialized_fields |
array() |
Fields that contain serialized data. This will automatically serialize before saving and unserialize data upon retrieving |
default_serialization_method | json | The default serialization method. Options are 'json' and 'serialize' |
boolean_fields |
array() |
Fields that are tinyint and should be treated as boolean |
suffix | _model | The suffix used for the data record class |
foreign_keys |
array() |
Map foreign keys to table models |
has_many |
array() |
Keys are model, which can be a key value pair with the key being the module and the value being the model, module (if not specified in model parameter), relationships_model, foreign_key, candidate_key |
belongs_to |
array() |
Keys are model, which can be a key value pair with the key being the module and the value being the model, module (if not specified in model parameter), relationships_model, foreign_key, candidate_key |
representatives |
array() |
An array of fields that have arrays or regular expression values to match against different field types (e.g. 'number'=>'bigint|smallint|tinyint|int') |
custom_fields |
array() |
An array of field names/types that map to a specific class |
formatters |
array() |
An array of helper formatter functions related to a specific field type (e.g. string, datetime, number), or name (e.g. title, content) that can augment field results |
protected | ||
db | N/A | |
table_name | N/A | |
key_field | N/A | Usually the tables primary key(s)... can be an array if compound key |
normalized_save_data | N/A | The saved data before it is cleaned |
cleaned_data | N/A | Data after it is cleaned |
dsn | N/A | The DSN string to connect to the database... if blank it will pull in from database config file |
has_auto_increment | N/A | Does the table have auto_increment? |
record_class | N/A | The name of the record class (if it can't be determined) |
friendly_name | N/A | A friendlier name of the group of objects |
singular_name | N/A | A friendly singular name of the object |
rules | N/A | Validation rules |
fields | N/A | Fields in the table |
use_common_query | N/A | Include the _common_query method for each query |
validator | N/A | The validator object |
clear_related_on_save | N/A | Clears related records before saving |
_tables | N/A | An array of table names with the key being the alias and the value being the actual table |
_last_saved | N/A | A reference to the last saved object / ID of record; |
_nested_errors | N/A | Used for capturing errors when saving multiple records (an array of records) on a single save method call |
Extending Your Model
When extending MY_Model or Base_module_model, it is recommended to use a plural version of the objects name, in this example Examples, with the suffix of _model (e.g.Examples_model). The plural version is recommended because the singular version is often used for the custom record class (see below for more).
The __construct method can be passed the name of the table to map to as the first argument and then you can optionally set other properties in the second constructor argument (see above for additional properties).
class Examples_model extends MY_Model { function __construct() { parent::__construct('example', array('required' => 'name')); // table name, initialization params } }
Most modules in FUEL extend the Base_module_model class, a child of MY_Model, but has some extended functionality needed for modules.
You can also define any of the class properties listed above like so:
class Examples_model extends MY_Model { public $required = array('name'); public $record_class = 'Example_record'; function __construct() { parent::__construct('example'); //table name } }
Custom Record Objects
MY_Model adds another layer of control for your models by allowing you to define custom return objects from active record. This gives you more flexibility at the record level for your models. With custom record objects you can:
- Create Derived Attributes
- Lazy Load Other Objects
- Manipulate and Save at the Record Level
- Automatically Parse Field Values
- Link and Display Related Data
When creating a custom record class, it is recommended to use the singular version of the parent model class (so parent models should be plural).
class Examples_model extends MY_Model { public $required = array('name'); function __construct() { parent::__construct('example'); //table name } } class Example_model extends MY_Model { Custom record model methods go here.... }
Custom record objects are not required. MY_Model will intelligently try and figure out the class name if one is not defined in the parent table model. If the class is not found it will return an array of arrays or an array of standard objects depending on the return_method property of the parent model class. Custom record objects must be defined in the same file that the parent table model is defined.
Create Derived Attributes
With a custom record object, you can derive attributes which means you can create new values from the existing fields or even other models. For example, you have a table with a text field named content that you need to filter and encode html entities and sometimes strip images before displaying on your webpage. Instead of applying the htmlentities and strip_image_tags function each time it is displayed, you can create a new derived attribute on your custom record object like so:
function get_content_formatted($strip_images = FALSE) { $CI =& get_instance(); if ($strip_images) { $CI->load->helper('security'); $content = strip_image_tags($this->content); } $content = htmlentities($this->content); return $content; }
Lazy Load Other Objects
Lazy loading of object is used when you don't want the overhead of queries to generate sub objects. For example, if you have a books model which has a foreign key to an authors model, you could create a method on the record class to lazy load that object like this:
function get_author() { $author = $this->lazy_load(array('email' => 'hsolo@milleniumfalcon.com'), 'authors_model', FALSE); return $author; }
Manipulate and Save at the Record Level
With custom record objects, you can update attributes and save the record object like so:
$foo = $this->examples_model->find_by_key(1); $foo->bar = 'This is a test'; $foo->save();
Automatically Parse Field Values
In some cases, you may be saving text data that you want to parse the templating syntax upon retrieval. This can be done automatically by setting the $parsed_fields array property on the table class like so:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed'); require_once(FUEL_PATH.'models/Base_module_model.php'); class Quotes_model extends Base_module_model { public $required = array('content'); public $parsed_fields = array('content', 'content_formatted'); function __construct() { parent::__construct('quotes'); // table name }
Note that both "content" and "content_formatted" were added to the $parsed_fields array. This is because they are treated as 2 separate fields even though the latter is magic method property.
Click here to view the function reference for custom record objects
Custom Fields Objects
Sometimes, but not as often, you may want a field to be a specific object instead just a string or integer value. MY_Model provides a custom_fields property that allows you to map specific classes to fields. The value can be an array with the keys being the field name, and the values can either be string values, referencing the class name, or an array specifying the module (key) and the class name (value). You can also pass in initialization parameters to the class with the init parameter. Below are some examples:
// loading from the fuel/application/libraries/custom_fields/My_asset_field.php 'image' => 'My_asset_field', // loading from the fuel/modules/my_module/libraries/custom_fields/My_text_field.php 'name' => array('my_module' => My_text_field'), // loading from the fuel/application/libraries/custom_fields/My_url_field.php and passing ina prefix_path of 'test' 'url' => array('model' => 'My_url_field', 'init' => array('prefix_path' => 'test')),
Once created by a record, you will be able to reference the fields value as normal, but will also be able to call any methods on the custom field's object like so:
echo $record->image; // my_img.jpg echo $record->image->my_func(); // my_func method's output from the My_asset_field object
By default, FUEL will look in the "{module}/libraries/custom_fields" folder for the the classes to load.
Magic Methods
MY_Model uses PHP's magic methods extensively. This allows for dynamically creating methods that aren't originally defined by the class. Magic methods are used both in the table and custom record classes. In custom record classes, any method prefixed with get_ can also be syntactically written to look like an instance property:
$record->get_content() // can also be written as... $record->content
There are also additional formatter variations you can use on a record property — such as {property}_formatted and {property}_stripped.
Using {property}_formatted will apply the auto_typography function if the property is a string value and if it is a date value will format the date by default to whatever is specified in the fuel/application/config/MY_config.php.
// returns content with <p> tags applied echo $record->content_formatted;
Using {property}_stripped will apply the strip_tags PHP function and only applies to string values.
// returns content with HTML tags stripped (only applies to fields that return strings) echo $record->content_stripped;
Additionally, you can use is_{property}() on any property that is a boolean type value or an enum value with 2 options.
if ($record->is_published()) { echo 'Published'; } else { echo 'Not Published'; }
To determine if a value exists for a certain property you can use has_{property}().
if ($record->has_image()) { echo ''; } else { echo 'No Image'; }
In the table class, magic methods are used to find records in interesting ways. For example, you can do something like this (where {} enclose areas where the developer should change to a proper field name):
// to find multiple items $this->examples_model->find_all_by_{column1}_and_{column2}('column1_val', 'column2_val'); // to find one item $this->examples_model->find_one_by_{column1}_or_{column2}('column1_val', 'column2_val');
Working with Active Record
MY_Model works alongside active record like so:
... $this->db->where(array('published' => 'yes')) $this->db->find_all() // Is the same as... $this->db->find_all(array('published' => 'yes'))
Relationships
FUEL CMS 1.0 provides several ways to form model relationships by adding one or more of the following properties to your model:
- foreign_keys: one-to-many relationship. If attached to a CMS module, it is as a dropdown select to the model the foreign key belongs to.
- has_many: many-to-many relationship. If attached to a CMS module, it is a multiple select field.
- belongs_to: many-to-many relationship. If attached to a CMS module, it is a multiple select field.
Note that both the has_many and belongs_to relationships use the on_after_save model hook to store relationship information. So, if you overwrite this method in your model and have one of these relationships assigned to your model, you must call parent::on_after_save($values); to properly save the relationship information.
Foreign Keys
The foreign_key model property will dynamically bind the foreign key's record object to the declared model's record object. So for example, say you have a products model. Each product can belong to only one category. In this case, we can use FUEL's built in Categories module to make the relationship like so:
class Products_model extends Base_module_model { public $foreign_keys = array('category_id' => array(FUEL_FOLDER => 'categories_model'))); function __construct() { parent::__construct('products'); // table name } }
The constant FUEL_FOLDER is used as the array key above to point to the module in which to load the model (e.g. array(FUEL_FOLDER => 'categories_model'))). You can alternatively just use the name of the model as a string if the model exists in your application directory or you can use 'app' instead of FUEL_FOLDER
If your site uses categories within different contexts, you can add a where condition to target that context like so:
class Products_model extends Base_module_model { public $foreign_keys = array('category_id' => array(FUEL_FOLDER => 'categories_model', 'where' => array('context' => 'products'))); function __construct() { parent::__construct('products'); // table name } }
Then in your front end code, you can access the object like so:
$id = uri_segment(3); // assumption here that the 3rd segment has the product id $product = fuel_model('products', 'key', $id); if (isset($product->id)) { echo $product->category->name; }
Foreign Keys are automatically set as required fields when inputting int the CMS.
Has Many Relationship
The has_many model property allows you to assign many-to-many relationships between models without needing to setup a separate lookup table. FUEL will automatically save this relationship in the fuel_relationships table. If we continue with the products example above, a product may also need to be associated with multiple attributes, or tags, like regions your product belongs to, or specific features. For this, you can use FUEL's built in Tags module to create relationships like so:
class Products_model extends Base_module_model { public $has_many = array('attributes' => array(array(FUEL_FOLDER => 'fuel_tags_model')); function __construct() { parent::__construct('products'); // table name } }
The constant FUEL_FOLDER is used as the array key above to point to the module in which to load the model (e.g. array(FUEL_FOLDER => 'categories_model'))). You can alternatively just use the name of the model as a string if the model exists in your application directory or you can use 'app' instead of FUEL_FOLDER
The long way:
class Products_model extends Base_module_model { public $has_many = array('attributes' => array('model' => array(FUEL_FOLDER => 'fuel_tags_model')); function __construct() { parent::__construct('products'); // table name } }
Even longer:
class Products_model extends Base_module_model { public $has_many = array('attributes' => array('model' => 'fuel_tags_model', 'module' => FUEL_FOLDER)); //NOTE THE 'module' key function __construct() { parent::__construct('products'); // table name } }
In this example, if you want to target only a specific set of tags that belong to a particular category, you can use the where condition to further filter the list of tags like so:
class Products_model extends Base_module_model { public $has_many = array('attributes' => array(FUEL_FOLDER => 'fuel_tags_model', 'where' => 'category_id = 1')); function __construct() { parent::__construct('products'); // table name } }
Then in your front end code, you can access the object like so:
$id = uri_segment(3); // assumption here that the 3rd segment has the product id $product = fuel_model('products', 'key', $id); if (isset($product->id)) { foreach($product->attributes as $attribute) { echo $attribute->name; } }
If you would like to do further active record filtering on the relationship before retrieving the data, you can call the property using it's full method form and passing TRUE to it. This will return the model object with the "find within" part of the query already set by active record:
$id = uri_segment(3); // assumption here that the 3rd segment has the product id $product = fuel_model('products', 'key', $id); if (isset($product->id)) { $tags_model = $product->get_attributes(TRUE); // NOTE THE DIFFERENCE HERE $attributes = $tags_model->find_all('category_id = 1'); foreach($attributes as $attribute) { echo $attribute->name; } }
Belongs To Relationship
The belongs_to model attribute is very similar to the has_many, but is done from the opposite perspective, which in this case would be from FUEL's Tags module. FUEL automatically creates the belongs_to relationship for the Tags module. This allows you to see which products belong to a specific tag in this example:
$slug = uri_segment(3); // assumption here that the 3rd segment has the slug $tag = fuel_model('products', 'one', array('slug', $slug)); if (isset($tag->id)) { $products = $tag->products; foreach($products as $product) { echo $product->name; } }
By default, FUEL uses the built-in relationships model to save relationship data. However, if you want to change the model, as well as the field names it uses to store the key information, you can pass the following parameters in your has_many or belongs_to properties array: relationships_model, foreign_key, candidate_key. An Example is below:
class Products_model extends Base_module_model { public $has_many = array('attributes' => array('model' => array(FUEL_FOLDER => 'fuel_tags_model'), 'relationships_model' => 'my_relationship_model', 'foreign_key' => 'my_foreign_key', 'candidate_key' => 'candidate_key')); function __construct() { parent::__construct('products'); // table name } }
Serialized Fields
FUEL CMS provides a way for you to automatically serialize and unserialize data in a field. To do this, you can assign the field name to the serialized_fields array. This will automatically serialize the data upon saving and unserialize it upon retrieval via a "find_*" method on the model. The default encoding is JSON but can be changed to use the PHP serialize/unserialize function by changing the default_serialization_method value to serialize.
class Products_model extends Base_module_model { public $serialized_fields = array('attributes'); function __construct() { parent::__construct('products'); // table name } }
// then in your controller or elsewhere $product = $this->products_model->create(); $product->attributes = array('color' => 'black', 'material' => 'metal'); $product->save(); // saves and serializes the data
Hooks
MY_Model provides hooks that you can overwrite with your own custom code to extend the functionality of your model. In most cases, the hooks accept an array of values to be processed and should return the processed array.
Table class hooks
- on_before_clean - executed right before cleaning of values (before validation and save) and usually passed the raw $_POST data to be saved unless the save values have been specified.
- on_before_validate - executed right before validation of values but after on_before_clean and is passed an array of cleaned data
- on_before_insert - executed before inserting values but after on_before_validate
- on_after_insert - executed after insertion
- on_before_update - executed before updating and passed an array of cleaned data
- on_after_update - executed after updating and passed an array of cleaned data
- on_before_save - executed before saving and passed an array of cleaned data
- on_after_save - executed after saving and passed an array of cleaned data
- on_before_delete - executed before deleting
- on_after_delete - executed after deleting
- on_before_post - to be called from within your own code right before processing post data and passed the $_POST variables. Simple modules have this hook implemented and you can add this hook to manipulate $_POST values before being saved
- on_after_post - to be called from within your own code after posting data. Simple modules have this hook implemented and the values passed to it is the processed $_POST array after placeholder substitutions and Form_builder post_processing hooks have been run. An example of using this hook would be to access images after they've been uploaded for further manipulation.
- on_duplicate - executed when a record class is duplicated
- on_created - executed when a record class is created
Record class hooks
- before_set - executed before setting a value
- after_get - executed after setting a value
- on_insert - executed after inserting
- on_update - executed after updating
- on_init - executed upon initialization
Formatters
Formatters allow you to easily modify a value by mapping a function to a suffix value of a property. An example of this is to use the "_formatted" suffix on a string type field which would apply the auto_typography.
echo $record->content; // A long time ago... echo $record->content_formatted; // <p>A long time ago...</p>
Formatter function that can have additional parameters passed to them can be called as methods. An example would be the "wrap" filter:
$record->content_wrap(50);
Formatters can be applied to specific field types like datetime fields or string type fields, or they can be applied to field names (e.g. image, img, thumb). You can also apply multiple formatters to a field type by using the format function like so:
$record->format('content', 'stripped|formatted'); //OR $record->format('content', 'stripped, formatted'); //OR $record->format('content', array('stripped', 'formatted');
MY_Model has a formatters property in which Base_module_model extends to provide you by default with the following formatters for different field types:
Datetime and Date Field Examples
Below are examples of formatters that can be applied to a date field and links to the functions that they use.
- {datetime}_formatted
- {datetime}_timestamp
- {datetime}_month
- {datetime}_day
- {datetime}_weekday
- {datetime}_year
- {datetime}_hour
- {datetime}_minute
- {datetime}_second
String Field Examples
Below are examples of formatters that can be applied to a string fields (char, varchar, text) and links to the functions that they use.
- {string}_formatted (auto_typography CI function)
- {string}_stripped
- {string}_markdown
- {string}_parsed
- {string}_excerpt (word_limiter CI function)
- {string}_ellipsize (ellipsize CI function)
- {string}_highlight (highlight_phrase CI function)
- {string}_entities
- {string}_specialchars
- {string}_humanize (humanize CI function)
- {string}_underscore (underscore CI function)
- {string}_camelize (camelize CI function)
Number Examples
Below are examples of formatters that can be applied to a number type fields (tinyint, smallint, int, bigint) and links to the functions that they use.
Special Field Name Examples
Below are examples of formatters that can be applied to a number type fields (tinyint, smallint, int, bigint) and links to the functions that they use.
- {url|link}_path (site_url CI function)
- {url|link}_prep (prep_url CI function)
- {img|image|thumb|pdf}_path
- {img|image|thumb|pdf}_serverpath
- {img|image|thumb|pdf}_filesize
- {img|image|thumb|pdf}_exists
Formatter aliases must not contain "_" (underscores). So for example, the function "word_limiter" has an alias of "wordlimiter".