11 KiB
ORM
Kohana 3.0 includes a powerful ORM module that uses the active record pattern and database introspection to determine a model's column information.
The ORM module is included with the Kohana 3.0 install but needs to be enabled before you can use it. In your application/bootstrap.php
file modify the call to [Kohana::modules] and include the ORM module:
Kohana::modules(array(
...
'orm' => MODPATH.'orm',
...
));
Configuration
ORM requires little configuration to get started. Extend your model classes with ORM to begin using the module:
class Model_User extends ORM
{
...
}
In the example above, the model will look for a users
table in the default database.
Model Configuration Properties
The following properties are used to configure each model:
Type | Option | Description | Default value |
---|---|---|---|
string |
_table_name | Table name to use | singular model name |
string |
_db | Name of the database to use | default |
string |
_primary_key | Column to use as primary key | id |
string |
_primary_val | Column to use as primary value | name |
bool |
_table_names_plural | Whether tables names are plural | TRUE |
array |
_sorting | Array of column => direction | primary key => ASC |
string |
_foreign_key_suffix | Suffix to use for foreign keys | _id |
Using ORM
Loading a Record
To create an instance of a model, you can use the [ORM::factory] method or the [ORM::__construct]:
$user = ORM::factory('user');
// or
$user = new Model_User();
The constructor and factory methods also accept a primary key value to load the given model data:
// Load user ID 5
$user = ORM::factory('user', 5);
// See if the user was loaded successfully
if ($user->loaded()) { ... }
You can optionally pass an array of key => value pairs to load a data object matching the given criteria:
// Load user with email joe@example.com
$user = ORM::factory('user', array('email' => 'joe@example.com'));
Searching for a Record or Records
ORM supports most of the [Database] methods for powerful searching of your model's data. See the _db_methods
property for a full list of supported method calls. Records are retrieved using the [ORM::find] and [ORM::find_all] method calls.
// This will grab the first active user with the name Bob
$user = ORM::factory('user')
->where('active', '=', TRUE)
->where('name', '=', 'Bob')
->find();
// This will grab all users with the name Bob
$users = ORM::factory('user')
->where('name', '=', 'Bob')
->find_all();
When you are retrieving a list of models using [ORM::find_all], you can iterate through them as you do with database results:
foreach ($users as $user)
{
...
}
A powerful feature of ORM is the [ORM::as_array] method which will return the given record as an array. If used with [ORM::find_all], an array of all records will be returned. A good example of when this is useful is for a select list:
// Display a select field of usernames (using the id as values)
echo Form::select('user', ORM::factory('user')->find_all()->as_array('id', 'username'));
Counting Records
Use [ORM::count_all] to return the number of records for a given query.
// Number of users
$count = ORM::factory('user')->where('active', '=', TRUE)->count_all();
If you wish to count the total number of users for a given query, while only returning a certain subset of these users, call the [ORM::reset] method with FALSE
before using count_all
:
$user = ORM::factory('user');
// Total number of users (reset FALSE prevents the query object from being cleared)
$count = $user->where('active', '=', TRUE)->reset(FALSE)->count_all();
// Return only the first 10 of these results
$users = $user->limit(10)->find_all();
Accessing Model Properties
All model properties are accessible using the __get
and __set
magic methods.
$user = ORM::factory('user', 5);
// Output user name
echo $user->name;
// Change user name
$user->name = 'Bob';
To store information/properties that don't exist in the model's table, you can use the _ignored_columns
data member. Data will be stored in the internal _object
member, but ignored at the database level.
class Model_User extends ORM
{
...
protected $_ignored_columns = array('field1', 'field2', ...);
...
}
Multiple key => value pairs can be set by using the [ORM::values] method.
$user->values(array('username' => 'Joe', 'password' => 'bob'));
Creating and Saving Records
The [ORM::save] method is used to both create new records and update existing records.
// Creating a record
$user = ORM::factory('user');
$user->name = 'New user';
$user->save();
// Updating a record
$user = ORM::factory('user', 5);
$user->name = 'User 2';
$user->save();
// Check to see if the record has been saved
if ($user->saved()) { ... }
You can update multiple records by using the [ORM::save_all] method:
$user = ORM::factory('user');
$user->name = 'Bob';
// Change all active records to name 'Bob'
$user->where('active', '=', TRUE)->save_all();
Using Updated
and Created
Columns
The _updated_column
and _created_column
members are provided to automatically be updated when a model is updated and created. These are not used by default. To use them:
// date_created is the column used for storing the creation date. Use format => TRUE to store a timestamp.
protected $_created_column = array('column' => 'date_created', 'format' => TRUE);
// date_modified is the column used for storing the modified date. In this case, a string specifying a date() format is used.
protected $_updated_column = array('column' => 'date_modified', 'format' => 'm/d/Y');
Deleting Records
Records are deleted with [ORM::delete] and [ORM::delete_all]. These methods operate in the same fashion as saving described above with the exception that [ORM::delete] takes one optional parameter, the id
of the record to delete. Otherwise, the currently loaded record is deleted.
Relationships
ORM provides for powerful relationship support. Ruby has a great tutorial on relationships.
Belongs-To and Has-Many
We'll assume we're working with a school that has many students. Each student can belong to only one school. You would define the relationships in this manner:
// Inside the school model
protected $_has_many = array('students' => array());
// Inside the student model
protected $_belongs_to = array('school' => array());
To access a student's school you use:
$school = $student->school;
To access a school's students, you would use:
// Note that find_all is required after students
$students = $school->students->find_all();
// To narrow results:
$students = $school->students->where('active', '=', TRUE)->find_all();
By default, ORM will look for a school_id
model in the student table. This can be overriden by using the foreign_key
attribute:
protected $_belongs_to = array('school' => array('foreign_key' => 'schoolID'));
The foreign key should be overridden in both the student and school models.
Has-One
Has-One is a special case of Has-Many, the only difference being that there is one and only one record. In the above example, each school would have one and only one student (although this is a poor example).
// Inside the school model
protected $_has_one = array('student' => array());
Like Belongs-To, you do not need to use the find
method when referencing the Has-One related object - it is done automatically.
Has-Many "Through"
The Has-Many "through" relationship (also known as Has-And-Belongs-To-Many) is used in the case of one object being related to multiple objects of another type, and visa-versa. For instance, a student may have multiple classes and a class may have multiple students. In this case, a third table and model known as a pivot
is used. In this case, we will call the pivot object/model enrollment
.
// Inside the student model
protected $_has_many = array('classes' => array('through' => 'enrollment'));
// Inside the class model
protected $_has_many = array('students' => array('through' => 'enrollment'));
The enrollment table should contain two foreign keys, one for class_id
and the other for student_id
. These can be overriden using foreign_key
and far_key
when defining the relationship. For example:
// Inside the student model (the foreign key refers to this model [student], while the far key refers to the other model [class])
protected $_has_many = array('classes' => array('through' => 'enrollment', 'foreign_key' => 'studentID', 'far_key' => 'classID'));
// Inside the class model
protected $_has_many = array('students' => array('through' => 'enrollment', 'foreign_key' => 'classID', 'far_key' => 'studentID'));
The enrollment model should be defined as such:
// Enrollment model belongs to both a student and a class
protected $_belongs_to = array('student' => array(), 'class' => array());
To access the related objects, use:
// To access classes from a student
$student->classes->find_all();
// To access students from a class
$class->students->find_all();
Validation
ORM is integrated tightly with the [Validate] library. The ORM provides the following members for validation:
- _rules
- _callbacks
- _filters
- _labels
_rules
protected $_rules = array
(
'username' => array('not_empty' => array()),
'email' => array('not_empty' => array(), 'email' => array()),
);
username
will be checked to make sure it's not empty. email
will be checked to also ensure it is a valid email address. The empty arrays passed as values can be used to provide optional additional parameters to these validate method calls.
_callbacks
protected $_callbacks = array
(
'username' => array('username_unique'),
);
username
will be passed to a callback method username_unique
. If the method exists in the current model, it will be used, otherwise a global function will be called. Here is an example of the definition of this method:
public function username_unique(Validate $data, $field)
{
// Logic to make sure a username is unique
...
}
_filters
protected $_filters = array
(
TRUE => array('trim' => array()),
'username' => array('stripslashes' => array()),
);
TRUE
indicates that the trim
filter is to be used on all fields. username
will be filtered through stripslashes
before it is validated. The empty arrays passed as values can be used to provide additional parameters to these filter method calls.
Checking if the Object is Valid
Use [ORM::check] to see if the object is currently valid.
// Setting an object's values, then checking to see if it's valid
if ($user->values($_POST)->check())
{
$user->save();
}
You can use the validate()
method to access the model's validation object.
// Add an additional filter manually
$user->validate()->filter('username', 'trim');