Implements: Serializable
This class is a transparent base class for ORM and should not be accessed directly.
Object Relational Mapping (ORM) is a method of abstracting database access to standard PHP calls. All table rows are represented as model objects, with object properties representing row data. ORM in Kohana generally follows the Active Record pattern.
Class declared in MODPATH/orm/classes/Kohana/ORM.php on line 17.
array
$_belongs_to"Belongs to" relationships
array(0)
array
$_cast_dataData to be loaded into the model from a database call cast
array(0)
array
$_changedarray(0)
array
$_column_cacheStores column information for ORM models
array(0)
string
$_created_columnAuto-update columns for creation
NULL
Database
$_dbDatabase Object
NULL
array
$_db_appliedDatabase methods applied
array(0)
Database_Query_Builder_Select
$_db_builderDatabase query builder
NULL
String
$_db_groupDatabase config group
NULL
array
$_db_pendingDatabase methods pending
array(0)
bool
$_db_resetReset builder
bool TRUE
string
$_errors_filenameThe message filename used for validation errors. Defaults to ORM::$_object_name
NULL
string
$_foreign_key_suffixForeign key suffix
string(3) "_id"
array
$_has_many"Has many" relationships
array(0)
array
$_has_one"Has one" relationships
array(0)
array
$_init_cacheInitialization storage for ORM models
array(0)
array
$_load_withRelationships that should always be joined
array(0)
bool
$_loadedbool FALSE
array
$_objectCurrent object
array(0)
string
$_object_nameModel name
NULL
string
$_object_pluralPlural model name
NULL
array
$_original_valuesarray(0)
string
$_primary_keyTable primary key
string(2) "id"
mixed
$_primary_key_valuePrimary key value
NULL
array
$_relatedarray(0)
bool
$_reload_on_wakeupModel configuration, reload on wakeup?
bool TRUE
bool
$_savedbool FALSE
array
$_serialize_columnsAuto-serialize and unserialize columns on get/set
array(0)
array
$_sortingNULL
array
$_table_columnsTable columns
NULL
string
$_table_nameTable name
NULL
bool
$_table_names_pluralModel configuration, table names plural?
bool TRUE
string
$_updated_columnAuto-update columns for updates
NULL
bool
$_validbool FALSE
Validation
$_validationValidation object created before saving/updating
NULL
array
$_with_appliedWith calls already applied
array(0)
Constructs a new model and loads a record if given
mixed
$id
= NULL - Parameter for find or object to load public function __construct($id = null)
{
$this->_initialize();
if ($id !== null) {
if (is_array($id)) {
foreach ($id as $column => $value) {
// Passing an array of column => values
$this->where($column, '=', $value);
}
$this->find();
} else {
// Passing the primary key
$this->where($this->_object_name . '.' . $this->_primary_key, '=', $id)->find();
}
} elseif (!empty($this->_cast_data)) {
// Load preloaded data from a database call cast
$this->_load_values($this->_cast_data);
$this->_cast_data = [];
}
}
Handles retrieval of all model values, relationships, and metadata. [!!] This should not be overridden.
string
$column
required - Column name mixed
public function __get($column)
{
return $this->get($column);
}
Checks if object data is set.
string
$column
required - Column name boolean
public function __isset($column)
{
return (isset($this->_object[$column]) OR
isset($this->_related[$column]) OR
isset($this->_has_one[$column]) OR
isset($this->_belongs_to[$column]) OR
isset($this->_has_many[$column]));
}
Base set method. [!!] This should not be overridden.
string
$column
required - Column name mixed
$value
required - Column value void
public function __set($column, $value)
{
$this->set($column, $value);
}
Displays the primary key of a model when it is converted to a string.
string
public function __toString()
{
return (string) $this->pk();
}
Unsets object data.
string
$column
required - Column name void
public function __unset($column)
{
unset($this->_object[$column], $this->_changed[$column], $this->_related[$column]);
}
Adds a new relationship to between this model and another.
// Add the login role using a model instance
$model->add('roles', ORM::factory('role', ['name' => 'login']));
// Add the login role if you know the roles.id is 5
$model->add('roles', 5);
// Add multiple roles (for example, from checkboxes on a form)
$model->add('roles', [1, 2, 3, 4]);
string
$alias
required - Alias of the has_many "through" relationship mixed
$far_keys
required - Related model, primary key, or an array of primary keys ORM
public function add($alias, $far_keys)
{
$far_keys = ($far_keys instanceof ORM) ? $far_keys->pk() : $far_keys;
$columns = [$this->_has_many[$alias]['foreign_key'], $this->_has_many[$alias]['far_key']];
$foreign_key = $this->pk();
$query = DB::insert($this->_has_many[$alias]['through'], $columns);
foreach ((array) $far_keys as $key) {
$query->values([$foreign_key, $key]);
}
$query->execute($this->_db);
return $this;
}
Creates a new "AND HAVING" condition for the query.
mixed
$column
required - Column name or [$column, $alias] or object string
$op
required - Logic operator mixed
$value
= NULL - Column value $this
public function and_having($column, $op, $value = null)
{
// Add pending database call which is executed after query type is determined
$this->_db_pending[] = [
'name' => 'and_having',
'args' => [$column, $op, $value],
];
return $this;
}
Closes an open "AND HAVING (...)" grouping.
$this
public function and_having_close()
{
// Add pending database call which is executed after query type is determined
$this->_db_pending[] = [
'name' => 'and_having_close',
'args' => [],
];
return $this;
}
Opens a new "AND HAVING (...)" grouping.
$this
public function and_having_open()
{
// Add pending database call which is executed after query type is determined
$this->_db_pending[] = [
'name' => 'and_having_open',
'args' => [],
];
return $this;
}
Creates a new "AND WHERE" condition for the query.
mixed
$column
required - Column name or [$column, $alias] or object string
$op
required - Logic operator mixed
$value
required - Column value $this
public function and_where($column, $op, $value)
{
// Add pending database call which is executed after query type is determined
$this->_db_pending[] = [
'name' => 'and_where',
'args' => [$column, $op, $value],
];
return $this;
}
Closes an open "AND WHERE (...)" grouping.
$this
public function and_where_close()
{
// Add pending database call which is executed after query type is determined
$this->_db_pending[] = [
'name' => 'and_where_close',
'args' => [],
];
return $this;
}
Opens a new "AND WHERE (...)" grouping.
$this
public function and_where_open()
{
// Add pending database call which is executed after query type is determined
$this->_db_pending[] = [
'name' => 'and_where_open',
'args' => [],
];
return $this;
}
Returns the values of this object as an array, including any related one-one models that have already been loaded using with()
array
public function as_array()
{
$object = [];
foreach ($this->_object as $column => $value) {
// Call __get for any user processing
$object[$column] = $this->__get($column);
}
foreach ($this->_related as $column => $model) {
// Include any related objects that are already loaded
$object[$column] = $model->as_array();
}
return $object;
}
public function belongs_to()
{
return $this->_belongs_to;
}
Enables the query to be cached for a specified amount of time.
integer
$lifetime
= NULL - Number of seconds to cache $this
public function cached($lifetime = null)
{
// Add pending database call which is executed after query type is determined
$this->_db_pending[] = [
'name' => 'cached',
'args' => [$lifetime],
];
return $this;
}
Check whether the model data has been modified. If $field is specified, checks whether that field was modified.
string
$field
= NULL - Field to check for changes bool
- Whether or not the field has changedpublic function changed($field = null)
{
return ($field === null) ? $this->_changed : Arr::get($this->_changed, $field);
}
Validates the current model's data
Validation
$extra_validation
= NULL - Validation object ORM
public function check(Validation $extra_validation = null)
{
// Determine if any external validation failed
$extra_errors = ($extra_validation AND ! $extra_validation->check());
// Always build a new validation object
$this->_validation();
$array = $this->_validation;
if (($this->_valid = $array->check()) === false OR $extra_errors) {
$exception = new ORM_Validation_Exception($this->errors_filename(), $array);
if ($extra_errors) {
// Merge any possible errors from the external object
$exception->add_object('_external', $extra_validation);
}
throw $exception;
}
return $this;
}
Unloads the current object and clears the status.
ORM
public function clear()
{
// Create an array with all the columns set to null
$values = array_combine(array_keys($this->_table_columns), array_fill(0, count($this->_table_columns), null));
// Replace the object and reset the object status
$this->_object = $this->_changed = $this->_related = $this->_original_values = [];
// Replace the current object with an empty one
$this->_load_values($values);
// Reset primary key
$this->_primary_key_value = null;
// Reset the loaded state
$this->_loaded = false;
$this->reset();
return $this;
}
Count the number of records in the table.
integer
public function count_all()
{
$selects = [];
foreach ($this->_db_pending as $key => $method) {
if ($method['name'] == 'select') {
// Ignore any selected columns for now
$selects[$key] = $method;
unset($this->_db_pending[$key]);
}
}
if (!empty($this->_load_with)) {
foreach ($this->_load_with as $alias) {
// Bind relationship
$this->with($alias);
}
}
$this->_build(Database::SELECT);
$records = $this->_db_builder->from([$this->_table_name, $this->_object_name])
->select([DB::expr('COUNT(' . $this->_db->quote_column($this->_object_name . '.' . $this->_primary_key) . ')'), 'records_found'])
->execute($this->_db)
->get('records_found');
// Add back in selected columns
$this->_db_pending += $selects;
$this->reset();
// Return the total number of records in a table
return (int) $records;
}
Returns the number of relationships
// Counts the number of times the login role is attached to $model
$model->count_relations('roles', ORM::factory('role', ['name' => 'login']));
// Counts the number of times role 5 is attached to $model
$model->count_relations('roles', 5);
// Counts the number of times any of roles 1, 2, 3, or 4 are attached to
// $model
$model->count_relations('roles', [1, 2, 3, 4]);
// Counts the number roles attached to $model
$model->count_relations('roles')
string
$alias
required - Alias of the has_many "through" relationship mixed
$far_keys
= NULL - Related model, primary key, or an array of primary keys integer
public function count_relations($alias, $far_keys = null)
{
if ($far_keys === null) {
return (int) DB::select([DB::expr('COUNT(*)'), 'records_found'])
->from($this->_has_many[$alias]['through'])
->where($this->_has_many[$alias]['foreign_key'], '=', $this->pk())
->execute($this->_db)->get('records_found');
}
$far_keys = ($far_keys instanceof ORM) ? $far_keys->pk() : $far_keys;
// We need an array to simplify the logic
$far_keys = (array) $far_keys;
// Nothing to check if the model isn't loaded or we don't have any far_keys
if (!$far_keys OR ! $this->_loaded)
return 0;
$count = (int) DB::select([DB::expr('COUNT(*)'), 'records_found'])
->from($this->_has_many[$alias]['through'])
->where($this->_has_many[$alias]['foreign_key'], '=', $this->pk())
->where($this->_has_many[$alias]['far_key'], 'IN', $far_keys)
->execute($this->_db)->get('records_found');
// Rows found need to match the rows searched
return (int) $count;
}
Insert a new object to the database
Validation
$validation
= NULL - Validation object ORM
public function create(Validation $validation = null)
{
if ($this->_loaded)
throw new Kohana_Exception('Cannot create :model model because it is already loaded.', [':model' => $this->_object_name]);
// Require model validation before saving
if (!$this->_valid OR $validation) {
$this->check($validation);
}
$data = [];
foreach ($this->_changed as $column) {
// Generate list of column => values
$data[$column] = $this->_object[$column];
}
if (is_array($this->_created_column)) {
// Fill the created column
$column = $this->_created_column['column'];
$format = $this->_created_column['format'];
$data[$column] = $this->_object[$column] = ($format === true) ? time() : date($format);
}
$result = DB::insert($this->_table_name)
->columns(array_keys($data))
->values(array_values($data))
->execute($this->_db);
if (!array_key_exists($this->_primary_key, $data)) {
// Load the insert id as the primary key if it was left out
$this->_object[$this->_primary_key] = $this->_primary_key_value = $result[0];
} else {
$this->_primary_key_value = $this->_object[$this->_primary_key];
}
// Object is now loaded and saved
$this->_loaded = $this->_saved = true;
// All changes have been saved
$this->_changed = [];
$this->_original_values = $this->_object;
return $this;
}
public function created_column()
{
return $this->_created_column;
}
Deletes a single record while ignoring relationships.
ORM
public function delete()
{
if (!$this->_loaded)
throw new Kohana_Exception('Cannot delete :model model because it is not loaded.', [':model' => $this->_object_name]);
// Use primary key value
$id = $this->pk();
// Delete the object
DB::delete($this->_table_name)
->where($this->_primary_key, '=', $id)
->execute($this->_db);
return $this->clear();
}
Enables or disables selecting only unique columns using "SELECT DISTINCT"
boolean
$value
required - Enable or disable distinct columns $this
public function distinct($value)
{
// Add pending database call which is executed after query type is determined
$this->_db_pending[] = [
'name' => 'distinct',
'args' => [$value],
];
return $this;
}
public function errors_filename()
{
return $this->_errors_filename;
}
Creates and returns a new model. Model name must be passed with its' original casing, e.g.
$model = ORM::factory('User_Token');
string
$model
required - Model name mixed
$id
= NULL - Parameter for find() ORM
public static function factory($model, $id = null)
{
// Set class name
$model = 'Model_' . $model;
return new $model($id);
}
Filter definitions for validation
array
public function filters()
{
return [];
}
Finds and loads a single database row into the object.
ORM
public function find()
{
if ($this->_loaded)
throw new Kohana_Exception('Method find() cannot be called on loaded objects');
if (!empty($this->_load_with)) {
foreach ($this->_load_with as $alias) {
// Bind auto relationships
$this->with($alias);
}
}
$this->_build(Database::SELECT);
return $this->_load_result(false);
}
Finds multiple database rows and returns an iterator of the rows found.
Database_Result
public function find_all()
{
if ($this->_loaded)
throw new Kohana_Exception('Method find_all() cannot be called on loaded objects');
if (!empty($this->_load_with)) {
foreach ($this->_load_with as $alias) {
// Bind auto relationships
$this->with($alias);
}
}
$this->_build(Database::SELECT);
return $this->_load_result(true);
}
Choose the tables to select "FROM ..."
mixed
$tables
required - Table name or [$table, $alias] or object $this
public function from($tables)
{
$tables = func_get_args();
// Add pending database call which is executed after query type is determined
$this->_db_pending[] = [
'name' => 'from',
'args' => $tables,
];
return $this;
}
Handles getting of column Override this method to add custom get behavior
string
$column
required - Column name mixed
public function get($column)
{
if (array_key_exists($column, $this->_object)) {
return (in_array($column, $this->_serialize_columns)) ? $this->_unserialize_value($this->_object[$column]) : $this->_object[$column];
} elseif (isset($this->_related[$column])) {
// Return related model that has already been fetched
return $this->_related[$column];
} elseif (isset($this->_belongs_to[$column])) {
$model = $this->_related($column);
// Use this model's column and foreign model's primary key
$col = $model->_object_name . '.' . $model->_primary_key;
$val = $this->_object[$this->_belongs_to[$column]['foreign_key']];
// Make sure we don't run WHERE "AUTO_INCREMENT column" = NULL queries. This would
// return the last inserted record instead of an empty result.
// See: http://mysql.localhost.net.ar/doc/refman/5.1/en/server-session-variables.html#sysvar_sql_auto_is_null
if ($val !== null) {
$model->where($col, '=', $val)->find();
}
return $this->_related[$column] = $model;
} elseif (isset($this->_has_one[$column])) {
$model = $this->_related($column);
// Use this model's primary key value and foreign model's column
$col = $model->_object_name . '.' . $this->_has_one[$column]['foreign_key'];
$val = $this->pk();
$model->where($col, '=', $val)->find();
return $this->_related[$column] = $model;
} elseif (isset($this->_has_many[$column])) {
$model = ORM::factory($this->_has_many[$column]['model']);
if (isset($this->_has_many[$column]['through'])) {
// Grab has_many "through" relationship table
$through = $this->_has_many[$column]['through'];
// Join on through model's target foreign key (far_key) and target model's primary key
$join_col1 = $through . '.' . $this->_has_many[$column]['far_key'];
$join_col2 = $model->_object_name . '.' . $model->_primary_key;
$model->join($through)->on($join_col1, '=', $join_col2);
// Through table's source foreign key (foreign_key) should be this model's primary key
$col = $through . '.' . $this->_has_many[$column]['foreign_key'];
$val = $this->pk();
} else {
// Simple has_many relationship, search where target model's foreign key is this model's primary key
$col = $model->_object_name . '.' . $this->_has_many[$column]['foreign_key'];
$val = $this->pk();
}
return $model->where($col, '=', $val);
} else {
throw new Kohana_Exception('The :property property does not exist in the :class class', [':property' => $column, ':class' => get_class($this)]);
}
}
Creates a "GROUP BY ..." filter.
mixed
$columns
required - Column name or [$column, $alias] or object $this
public function group_by($columns)
{
$columns = func_get_args();
// Add pending database call which is executed after query type is determined
$this->_db_pending[] = [
'name' => 'group_by',
'args' => $columns,
];
return $this;
}
Tests if this object has a relationship to a different model, or an array of different models. When providing far keys, the number of relations must equal the number of keys.
// Check if $model has the login role
$model->has('roles', ORM::factory('role', ['name' => 'login']));
// Check for the login role if you know the roles.id is 5
$model->has('roles', 5);
// Check for all of the following roles
$model->has('roles', [1, 2, 3, 4]);
// Check if $model has any roles
$model->has('roles')
string
$alias
required - Alias of the has_many "through" relationship mixed
$far_keys
= NULL - Related model, primary key, or an array of primary keys boolean
public function has($alias, $far_keys = null)
{
$count = $this->count_relations($alias, $far_keys);
if ($far_keys === null) {
return (bool) $count;
} else {
return $count === count($far_keys);
}
}
Tests if this object has a relationship to a different model, or an array of different models. When providing far keys, this function only checks that at least one of the relationships is satisfied.
// Check if $model has the login role
$model->has('roles', ORM::factory('role', ['name' => 'login']));
// Check for the login role if you know the roles.id is 5
$model->has('roles', 5);
// Check for any of the following roles
$model->has('roles', [1, 2, 3, 4]);
// Check if $model has any roles
$model->has('roles')
string
$alias
required - Alias of the has_many "through" relationship mixed
$far_keys
= NULL - Related model, primary key, or an array of primary keys boolean
public function has_any($alias, $far_keys = null)
{
return (bool) $this->count_relations($alias, $far_keys);
}
public function has_many()
{
return $this->_has_many;
}
public function has_one()
{
return $this->_has_one;
}
Alias of and_having()
mixed
$column
required - Column name or [$column, $alias] or object string
$op
required - Logic operator mixed
$value
= NULL - Column value $this
public function having($column, $op, $value = null)
{
return $this->and_having($column, $op, $value);
}
Closes an open "AND HAVING (...)" grouping.
$this
public function having_close()
{
return $this->and_having_close();
}
Alias of and_having_open()
$this
public function having_open()
{
return $this->and_having_open();
}
Adds addition tables to "JOIN ...".
mixed
$table
required - Column name or [$column, $alias] or object string
$type
= NULL - Join type (LEFT, RIGHT, INNER, etc) $this
public function join($table, $type = null)
{
// Add pending database call which is executed after query type is determined
$this->_db_pending[] = [
'name' => 'join',
'args' => [$table, $type],
];
return $this;
}
Label definitions for validation
array
public function labels()
{
return [];
}
Returns last executed query
string
public function last_query()
{
return $this->_db->last_query;
}
Return up to "LIMIT ..." results
integer
$number
required - Maximum results to return $this
public function limit($number)
{
// Add pending database call which is executed after query type is determined
$this->_db_pending[] = [
'name' => 'limit',
'args' => [$number],
];
return $this;
}
Proxy method to Database list_columns.
array
public function list_columns()
{
// Proxy to database
return $this->_db->list_columns($this->_table_name);
}
public function load_with()
{
return $this->_load_with;
}
public function loaded()
{
return $this->_loaded;
}
public function object()
{
return $this->_object;
}
public function object_name()
{
return $this->_object_name;
}
public function object_plural()
{
return $this->_object_plural;
}
Start returning results after "OFFSET ..."
integer
$number
required - Starting result number $this
public function offset($number)
{
// Add pending database call which is executed after query type is determined
$this->_db_pending[] = [
'name' => 'offset',
'args' => [$number],
];
return $this;
}
Adds "ON ..." conditions for the last created JOIN statement.
mixed
$c1
required - Column name or [$column, $alias] or object string
$op
required - Logic operator mixed
$c2
required - Column name or [$column, $alias] or object $this
public function on($c1, $op, $c2)
{
// Add pending database call which is executed after query type is determined
$this->_db_pending[] = [
'name' => 'on',
'args' => [$c1, $op, $c2],
];
return $this;
}
Creates a new "OR HAVING" condition for the query.
mixed
$column
required - Column name or [$column, $alias] or object string
$op
required - Logic operator mixed
$value
= NULL - Column value $this
public function or_having($column, $op, $value = null)
{
// Add pending database call which is executed after query type is determined
$this->_db_pending[] = [
'name' => 'or_having',
'args' => [$column, $op, $value],
];
return $this;
}
Closes an open "OR HAVING (...)" grouping.
$this
public function or_having_close()
{
// Add pending database call which is executed after query type is determined
$this->_db_pending[] = [
'name' => 'or_having_close',
'args' => [],
];
return $this;
}
Opens a new "OR HAVING (...)" grouping.
$this
public function or_having_open()
{
// Add pending database call which is executed after query type is determined
$this->_db_pending[] = [
'name' => 'or_having_open',
'args' => [],
];
return $this;
}
Creates a new "OR WHERE" condition for the query.
mixed
$column
required - Column name or [$column, $alias] or object string
$op
required - Logic operator mixed
$value
required - Column value $this
public function or_where($column, $op, $value)
{
// Add pending database call which is executed after query type is determined
$this->_db_pending[] = [
'name' => 'or_where',
'args' => [$column, $op, $value],
];
return $this;
}
Closes an open "OR WHERE (...)" grouping.
$this
public function or_where_close()
{
// Add pending database call which is executed after query type is determined
$this->_db_pending[] = [
'name' => 'or_where_close',
'args' => [],
];
return $this;
}
Opens a new "OR WHERE (...)" grouping.
$this
public function or_where_open()
{
// Add pending database call which is executed after query type is determined
$this->_db_pending[] = [
'name' => 'or_where_open',
'args' => [],
];
return $this;
}
Applies sorting with "ORDER BY ..."
mixed
$column
required - Column name or [$column, $alias] or object string
$direction
= NULL - Direction of sorting $this
public function order_by($column, $direction = null)
{
// Add pending database call which is executed after query type is determined
$this->_db_pending[] = [
'name' => 'order_by',
'args' => [$column, $direction],
];
return $this;
}
public function original_values()
{
return $this->_original_values;
}
Set the value of a parameter in the query.
string
$param
required - Parameter key to replace mixed
$value
required - Value to use $this
public function param($param, $value)
{
// Add pending database call which is executed after query type is determined
$this->_db_pending[] = [
'name' => 'param',
'args' => [$param, $value],
];
return $this;
}
Returns the value of the primary key
mixed
- Primary keypublic function pk()
{
return $this->_primary_key_value;
}
public function primary_key()
{
return $this->_primary_key;
}
Reloads the current object from the database.
ORM
public function reload()
{
$primary_key = $this->pk();
// Replace the object and reset the object status
$this->_object = $this->_changed = $this->_related = $this->_original_values = [];
// Only reload the object if we have one to reload
if ($this->_loaded)
return $this->clear()
->where($this->_object_name . '.' . $this->_primary_key, '=', $primary_key)
->find();
else
return $this->clear();
}
Reload column definitions.
boolean
$force
= bool FALSE - Force reloading ORM
public function reload_columns($force = false)
{
if ($force === true OR empty($this->_table_columns)) {
if (isset(ORM::$_column_cache[$this->_object_name])) {
// Use cached column information
$this->_table_columns = ORM::$_column_cache[$this->_object_name];
} else {
// Grab column information from database
$this->_table_columns = $this->list_columns();
// Load column cache
ORM::$_column_cache[$this->_object_name] = $this->_table_columns;
}
}
return $this;
}
Removes a relationship between this model and another.
// Remove a role using a model instance
$model->remove('roles', ORM::factory('role', ['name' => 'login']));
// Remove the role knowing the primary key
$model->remove('roles', 5);
// Remove multiple roles (for example, from checkboxes on a form)
$model->remove('roles', [1, 2, 3, 4]);
// Remove all related roles
$model->remove('roles');
string
$alias
required - Alias of the has_many "through" relationship mixed
$far_keys
= NULL - Related model, primary key, or an array of primary keys ORM
public function remove($alias, $far_keys = null)
{
$far_keys = ($far_keys instanceof ORM) ? $far_keys->pk() : $far_keys;
$query = DB::delete($this->_has_many[$alias]['through'])
->where($this->_has_many[$alias]['foreign_key'], '=', $this->pk());
if ($far_keys !== null) {
// Remove all the relationships in the array
$query->where($this->_has_many[$alias]['far_key'], 'IN', (array) $far_keys);
}
$query->execute($this->_db);
return $this;
}
Clears query builder. Passing false is useful to keep the existing query conditions for another query.
bool
$next
= bool TRUE - Pass false to avoid resetting on the next call ORM
public function reset($next = true)
{
if ($next AND $this->_db_reset) {
$this->_db_pending = [];
$this->_db_applied = [];
$this->_db_builder = null;
$this->_with_applied = [];
}
// Reset on the next call?
$this->_db_reset = $next;
return $this;
}
Rule definitions for validation
array
public function rules()
{
return [];
}
Updates or Creates the record depending on loaded()
Validation
$validation
= NULL - Validation object ORM
public function save(Validation $validation = null)
{
return $this->loaded() ? $this->update($validation) : $this->create($validation);
}
public function saved()
{
return $this->_saved;
}
Choose the columns to select from.
mixed
$columns
= NULL - Column name or [$column, $alias] or object $this
public function select($columns = null)
{
$columns = func_get_args();
// Add pending database call which is executed after query type is determined
$this->_db_pending[] = [
'name' => 'select',
'args' => $columns,
];
return $this;
}
Allows serialization of only the object data and state, to prevent "stale" objects being unserialized, which also requires less memory.
string
public function serialize()
{
// Store only information about the object
foreach (['_primary_key_value', '_object', '_changed', '_loaded', '_saved', '_sorting', '_original_values'] as $var) {
$data[$var] = $this->{$var};
}
return serialize($data);
}
Handles setting of columns Override this method to add custom set behavior
string
$column
required - Column name mixed
$value
required - Column value ORM
public function set($column, $value)
{
if (!isset($this->_object_name)) {
// Object not yet constructed, so we're loading data from a database call cast
$this->_cast_data[$column] = $value;
return $this;
}
if (in_array($column, $this->_serialize_columns)) {
$value = $this->_serialize_value($value);
}
if (array_key_exists($column, $this->_object)) {
// Filter the data
$value = $this->run_filter($column, $value);
// See if the data really changed
if ($value !== $this->_object[$column]) {
$this->_object[$column] = $value;
// Data has changed
$this->_changed[$column] = $column;
// Object is no longer saved or valid
$this->_saved = $this->_valid = false;
}
} elseif (isset($this->_belongs_to[$column])) {
// Update related object itself
$this->_related[$column] = $value;
// Update the foreign key of this model
$this->_object[$this->_belongs_to[$column]['foreign_key']] = ($value instanceof ORM) ? $value->pk() : null;
$this->_changed[$column] = $this->_belongs_to[$column]['foreign_key'];
} else {
throw new Kohana_Exception('The :property: property does not exist in the :class: class', [':property:' => $column, ':class:' => get_class($this)]);
}
return $this;
}
public function table_columns()
{
return $this->_table_columns;
}
public function table_name()
{
return $this->_table_name;
}
Checks whether a column value is unique. Excludes itself if loaded.
string
$field
required - The field to check for uniqueness mixed
$value
required - The value to check for uniqueness bool
- Whteher the value is uniquepublic function unique($field, $value)
{
$model = ORM::factory($this->object_name())
->where($field, '=', $value)
->find();
if ($this->loaded()) {
return (!($model->loaded() AND $model->pk() != $this->pk()));
}
return (!$model->loaded());
}
Prepares the database connection and reloads the object.
string
$data
required - String for unserialization void
public function unserialize($data)
{
// Initialize model
$this->_initialize();
foreach (unserialize($data) as $name => $var) {
$this->{$name} = $var;
}
if ($this->_reload_on_wakeup === true) {
// Reload the object
$this->reload();
}
}
Updates a single record or multiple records
Validation
$validation
= NULL - Validation object ORM
public function update(Validation $validation = null)
{
if (!$this->_loaded)
throw new Kohana_Exception('Cannot update :model model because it is not loaded.', [':model' => $this->_object_name]);
// Run validation if the model isn't valid or we have additional validation rules.
if (!$this->_valid OR $validation) {
$this->check($validation);
}
if (empty($this->_changed)) {
// Nothing to update
return $this;
}
$data = [];
foreach ($this->_changed as $column) {
// Compile changed data
$data[$column] = $this->_object[$column];
}
if (is_array($this->_updated_column)) {
// Fill the updated column
$column = $this->_updated_column['column'];
$format = $this->_updated_column['format'];
$data[$column] = $this->_object[$column] = ($format === true) ? time() : date($format);
}
// Use primary key value
$id = $this->pk();
// Update a single record
DB::update($this->_table_name)
->set($data)
->where($this->_primary_key, '=', $id)
->execute($this->_db);
if (isset($data[$this->_primary_key])) {
// Primary key was changed, reflect it
$this->_primary_key_value = $data[$this->_primary_key];
}
// Object has been saved
$this->_saved = true;
// All changes have been saved
$this->_changed = [];
$this->_original_values = $this->_object;
return $this;
}
public function updated_column()
{
return $this->_updated_column;
}
Adds "USING ..." conditions for the last created JOIN statement.
string
$columns
required - Column name $this
public function using($columns)
{
// Add pending database call which is executed after query type is determined
$this->_db_pending[] = [
'name' => 'using',
'args' => [$columns],
];
return $this;
}
public function validation()
{
if (!isset($this->_validation)) {
// Initialize the validation object
$this->_validation();
}
return $this->_validation;
}
Set values from an array with support for one-one relationships. This method should be used for loading in post data, etc.
array
$values
required - Array of column => val array
$expected
= NULL - Array of keys to take from $values ORM
public function values(array $values, array $expected = null)
{
// Default to expecting everything except the primary key
if ($expected === null) {
$expected = array_keys($this->_table_columns);
// Don't set the primary key by default
unset($values[$this->_primary_key]);
}
foreach ($expected as $key => $column) {
if (is_string($key)) {
// isset() fails when the value is null (we want it to pass)
if (!array_key_exists($key, $values))
continue;
// Try to set values to a related model
$this->{$key}->values($values[$key], $column);
}
else {
// isset() fails when the value is null (we want it to pass)
if (!array_key_exists($column, $values))
continue;
// Update the column, respects __set()
$this->$column = $values[$column];
}
}
return $this;
}
Alias of and_where()
mixed
$column
required - Column name or [$column, $alias] or object string
$op
required - Logic operator mixed
$value
required - Column value $this
public function where($column, $op, $value)
{
// Add pending database call which is executed after query type is determined
$this->_db_pending[] = [
'name' => 'where',
'args' => [$column, $op, $value],
];
return $this;
}
Closes an open "AND WHERE (...)" grouping.
$this
public function where_close()
{
return $this->and_where_close();
}
Alias of and_where_open()
$this
public function where_open()
{
return $this->and_where_open();
}
Binds another one-to-one object to this model. One-to-one objects can be nested using 'object1:object2' syntax
string
$target_path
required - Target model to bind to ORM
public function with($target_path)
{
if (isset($this->_with_applied[$target_path])) {
// Don't join anything already joined
return $this;
}
// Split object parts
$aliases = explode(':', $target_path);
$target = $this;
foreach ($aliases as $alias) {
// Go down the line of objects to find the given target
$parent = $target;
$target = $parent->_related($alias);
if (!$target) {
// Can't find related object
return $this;
}
}
// Target alias is at the end
$target_alias = $alias;
// Pop-off top alias to get the parent path (user:photo:tag becomes user:photo - the parent table prefix)
array_pop($aliases);
$parent_path = implode(':', $aliases);
if (empty($parent_path)) {
// Use this table name itself for the parent path
$parent_path = $this->_object_name;
} else {
if (!isset($this->_with_applied[$parent_path])) {
// If the parent path hasn't been joined yet, do it first (otherwise LEFT JOINs fail)
$this->with($parent_path);
}
}
// Add to with_applied to prevent duplicate joins
$this->_with_applied[$target_path] = true;
// Use the keys of the empty object to determine the columns
foreach (array_keys($target->_object) as $column) {
$name = $target_path . '.' . $column;
$alias = $target_path . ':' . $column;
// Add the prefix so that load_result can determine the relationship
$this->select([$name, $alias]);
}
if (isset($parent->_belongs_to[$target_alias])) {
// Parent belongs_to target, use target's primary key and parent's foreign key
$join_col1 = $target_path . '.' . $target->_primary_key;
$join_col2 = $parent_path . '.' . $parent->_belongs_to[$target_alias]['foreign_key'];
} else {
// Parent has_one target, use parent's primary key as target's foreign key
$join_col1 = $parent_path . '.' . $parent->_primary_key;
$join_col2 = $target_path . '.' . $parent->_has_one[$target_alias]['foreign_key'];
}
// Join the related object into the result
$this->join([$target->_table_name, $target_path], 'LEFT')->on($join_col1, '=', $join_col2);
return $this;
}
Initializes the Database Builder to given query type
integer
$type
required - Type of Database query ORM
protected function _build($type)
{
// Construct new builder object based on query type
switch ($type) {
case Database::SELECT:
$this->_db_builder = DB::select();
break;
case Database::UPDATE:
$this->_db_builder = DB::update([$this->_table_name, $this->_object_name]);
break;
case Database::DELETE:
// Cannot use an alias for DELETE queries
$this->_db_builder = DB::delete($this->_table_name);
}
// Process pending database method calls
foreach ($this->_db_pending as $method) {
$name = $method['name'];
$args = $method['args'];
$this->_db_applied[$name] = $name;
call_user_func_array([$this->_db_builder, $name], $args);
}
return $this;
}
Returns an array of columns to include in the select query. This method can be overridden to change the default select behavior.
array
- Columns to selectprotected function _build_select()
{
$columns = [];
foreach ($this->_table_columns as $column => $_) {
$columns[] = [$this->_object_name . '.' . $column, $column];
}
return $columns;
}
Prepares the model database connection, determines the table name, and loads column information.
void
protected function _initialize()
{
// Set the object name if none predefined
if (empty($this->_object_name)) {
$this->_object_name = strtolower(substr(get_class($this), 6));
}
// Check if this model has already been initialized
if (!$init = Arr::get(ORM::$_init_cache, $this->_object_name, false)) {
$init = [
'_belongs_to' => [],
'_has_one' => [],
'_has_many' => [],
];
// Set the object plural name if none predefined
if (!isset($this->_object_plural)) {
$init['_object_plural'] = Inflector::plural($this->_object_name);
}
if (!$this->_errors_filename) {
$init['_errors_filename'] = $this->_object_name;
}
if (!is_object($this->_db)) {
// Get database instance
$init['_db'] = Database::instance($this->_db_group);
}
if (empty($this->_table_name)) {
// Table name is the same as the object name
$init['_table_name'] = $this->_object_name;
if ($this->_table_names_plural === true) {
// Make the table name plural
$init['_table_name'] = Arr::get($init, '_object_plural', $this->_object_plural);
}
}
$defaults = [];
foreach ($this->_belongs_to as $alias => $details) {
if (!isset($details['model'])) {
$defaults['model'] = str_replace(' ', '_', ucwords(str_replace('_', ' ', $alias)));
}
$defaults['foreign_key'] = $alias . $this->_foreign_key_suffix;
$init['_belongs_to'][$alias] = array_merge($defaults, $details);
}
foreach ($this->_has_one as $alias => $details) {
if (!isset($details['model'])) {
$defaults['model'] = str_replace(' ', '_', ucwords(str_replace('_', ' ', $alias)));
}
$defaults['foreign_key'] = $this->_object_name . $this->_foreign_key_suffix;
$init['_has_one'][$alias] = array_merge($defaults, $details);
}
foreach ($this->_has_many as $alias => $details) {
if (!isset($details['model'])) {
$defaults['model'] = str_replace(' ', '_', ucwords(str_replace('_', ' ', Inflector::singular($alias))));
}
$defaults['foreign_key'] = $this->_object_name . $this->_foreign_key_suffix;
$defaults['through'] = null;
if (!isset($details['far_key'])) {
$defaults['far_key'] = Inflector::singular($alias) . $this->_foreign_key_suffix;
}
$init['_has_many'][$alias] = array_merge($defaults, $details);
}
ORM::$_init_cache[$this->_object_name] = $init;
}
// Assign initialized properties to the current object
foreach ($init as $property => $value) {
$this->{$property} = $value;
}
// Load column information
$this->reload_columns();
// Clear initial model state
$this->clear();
}
Loads a database result, either as a new record for this model, or as an iterator for multiple rows.
bool
$multiple
= bool FALSE - Return an iterator or load a single row ORM|Database_Result
protected function _load_result($multiple = false)
{
$this->_db_builder->from([$this->_table_name, $this->_object_name]);
if ($multiple === false) {
// Only fetch 1 record
$this->_db_builder->limit(1);
}
// Select all columns by default
$this->_db_builder->select_array($this->_build_select());
if (!isset($this->_db_applied['order_by']) AND ! empty($this->_sorting)) {
foreach ($this->_sorting as $column => $direction) {
if (strpos($column, '.') === false) {
// Sorting column for use in JOINs
$column = $this->_object_name . '.' . $column;
}
$this->_db_builder->order_by($column, $direction);
}
}
if ($multiple === true) {
// Return database iterator casting to this object type
$result = $this->_db_builder->as_object(get_class($this))->execute($this->_db);
$this->reset();
return $result;
} else {
// Load the result as an associative array
$result = $this->_db_builder->as_assoc()->execute($this->_db);
$this->reset();
if ($result->count() === 1) {
// Load object values
$this->_load_values($result->current());
} else {
// Clear the object, nothing was found
$this->clear();
}
return $this;
}
}
Loads an array of values into into the current object.
array
$values
required - Values to load ORM
protected function _load_values(array $values)
{
if (array_key_exists($this->_primary_key, $values)) {
if ($values[$this->_primary_key] !== null) {
// Flag as loaded and valid
$this->_loaded = $this->_valid = true;
// Store primary key
$this->_primary_key_value = $values[$this->_primary_key];
} else {
// Not loaded or valid
$this->_loaded = $this->_valid = false;
}
}
// Related objects
$related = [];
foreach ($values as $column => $value) {
if (strpos($column, ':') === false) {
// Load the value to this model
$this->_object[$column] = $value;
} else {
// Column belongs to a related model
list ($prefix, $column) = explode(':', $column, 2);
$related[$prefix][$column] = $value;
}
}
if (!empty($related)) {
foreach ($related as $object => $values) {
// Load the related objects with the values in the result
$this->_related($object)->_load_values($values);
}
}
if ($this->_loaded) {
// Store the object in its original state
$this->_original_values = $this->_object;
}
return $this;
}
Returns an ORM model for the given one-one related alias
string
$alias
required - Alias name ORM
protected function _related($alias)
{
if (isset($this->_related[$alias])) {
return $this->_related[$alias];
} elseif (isset($this->_has_one[$alias])) {
return $this->_related[$alias] = ORM::factory($this->_has_one[$alias]['model']);
} elseif (isset($this->_belongs_to[$alias])) {
return $this->_related[$alias] = ORM::factory($this->_belongs_to[$alias]['model']);
} else {
return false;
}
}
protected function _serialize_value($value)
{
return json_encode($value);
}
protected function _unserialize_value($value)
{
return json_decode($value, true);
}
Initializes validation rules, and labels
void
protected function _validation()
{
// Build the validation object with its rules
$this->_validation = Validation::factory($this->_object)
->bind(':model', $this)
->bind(':original_values', $this->_original_values)
->bind(':changed', $this->_changed);
foreach ($this->rules() as $field => $rules) {
$this->_validation->rules($field, $rules);
}
// Use column names by default for labels
$columns = array_keys($this->_table_columns);
// Merge user-defined labels
$labels = array_merge(array_combine($columns, $columns), $this->labels());
foreach ($labels as $field => $label) {
$this->_validation->label($field, $label);
}
}
Filters a value for a specific column
string
$field
required - The column name string
$value
required - The value to filter string
protected function run_filter($field, $value)
{
$filters = $this->filters();
// Get the filters for this column
$wildcards = empty($filters[true]) ? [] : $filters[true];
// Merge in the wildcards
$filters = empty($filters[$field]) ? $wildcards : array_merge($wildcards, $filters[$field]);
// Bind the field name and model so they can be used in the filter method
$_bound = [
':field' => $field,
':model' => $this,
];
foreach ($filters as $array) {
// Value needs to be bound inside the loop so we are always using the
// version that was modified by the filters that already ran
$_bound[':value'] = $value;
// Filters are defined as [$filter, $params]
$filter = $array[0];
$params = Arr::get($array, 1, [':value']);
foreach ($params as $key => $param) {
if (is_string($param) AND array_key_exists($param, $_bound)) {
// Replace with bound value
$params[$key] = $_bound[$param];
}
}
if (is_array($filter) OR ! is_string($filter)) {
// This is either a callback as an array or a lambda
$value = call_user_func_array($filter, $params);
} elseif (strpos($filter, '::') === false) {
// Use a function call
$function = new ReflectionFunction($filter);
// Call $function($this[$field], $param, ...) with Reflection
$value = $function->invokeArgs($params);
} else {
// Split the class and method of the rule
list($class, $method) = explode('::', $filter, 2);
// Use a static method call
$method = new ReflectionMethod($class, $method);
// Call $Class::$method($this[$field], $param, ...) with Reflection
$value = $method->invokeArgs(null, $params);
}
}
return $value;
}